journalctl -p err -b
That is the command. The rest of this guide is about the times it returns nothing and the journal is visibly full of errors, because that happens more often than the command failing.
journalctl uses the syslog priorities. Lower numbers are more severe.
| Number | Name | Meaning |
|---|---|---|
| 0 | emerg |
The system is unusable |
| 1 | alert |
Someone must act immediately |
| 2 | crit |
A critical condition |
| 3 | err |
An error |
| 4 | warning |
A warning |
| 5 | notice |
Normal but significant |
| 6 | info |
Informational |
| 7 | debug |
Debugging detail |
You can use either the name or the number. -p err and -p 3 are the same
command.
This is the part people misread. -p err does not show errors only. It shows
priority 3 and lower numbers — err, crit, alert and emerg.
journalctl -p err # 0, 1, 2 and 3
journalctl -p warning # 0 through 4
journalctl -p debug # everything
That is almost always what you want. A single priority acts as a ceiling on the number, which is a floor on the severity.
Pass a range in the form FROM..TO. Both ends are included.
journalctl -p 3..3 # errors only, nothing more severe
journalctl -p 0..3 # the same set as -p err, written out
journalctl -p 4..6 # warning, notice and info, no errors
-p 3..3 is the one worth remembering. It answers “what errors did we log”,
without the kernel’s panic-adjacent messages on top.
Priority is one match among several, so it ANDs with everything else:
journalctl -p err -u nginx # one unit
journalctl -p err -b # this boot
journalctl -p err -b -1 # the boot that ended badly
journalctl -p err --since "-1h" # the last hour
journalctl -p err --since today -o short-iso # today, with sortable timestamps
Two that are worth having in your shell history:
journalctl -p err -b --no-pager | tail -50
journalctl -p err -f
The first is the summary you want after a reboot. The second follows new errors
as they arrive — see
following logs in real time with journalctl -f
for what -f does and does not guarantee.
Here is the thing that costs people an afternoon.
The priority is set by whatever sent the message, not by what the message
says. journalctl filters on a PRIORITY field. It does not read the text. A
line saying ERROR: payment gateway timeout carries whatever priority its
sender attached, and for most applications that is not err.
When a service writes to stdout or stderr, systemd captures the stream and
tags every line with the unit’s SyslogLevel=. That setting defaults to
info, and it applies to stderr exactly as it applies to stdout. So a
program that dutifully writes its errors to stderr still lands in the journal
at priority 6, and journalctl -p err -u yourapp returns nothing.
Confirm it by looking at what priorities the unit actually emits:
journalctl -u yourapp -o json --output-fields=PRIORITY,MESSAGE -n 20
If every line says "PRIORITY" : "6", this is your problem.
Search the text instead. The fastest answer, and it works today:
journalctl -u yourapp -g "error|fatal|panic" --since "-1h"
-g takes a regular expression and matches against the message text. See
searching journalctl logs for a specific string
for the details, including a case-sensitivity rule that catches people out.
Raise the level for the whole unit. If a service only ever writes things worth noticing, tag the lot:
[Service]
SyslogLevel=warning
Blunt, but honest — it stops the unit from claiming that everything is fine.
Prefix the lines at the source. systemd reads a <N> prefix on each line
and uses it as that line’s priority, stripping the prefix before it stores the
message. The behaviour is controlled by SyslogLevelPrefix=, which defaults to
true, so it works without configuring anything:
echo "<3>database connection refused" # arrives as priority 3, err
echo "<6>listening on :8080" # arrives as priority 6, info
Most logging libraries can be told to emit that prefix. It is the only one of the three that gets the priorities genuinely right, because the application is the only thing that knows which lines are errors.
-p err reads the journal. Some failures never reach it.
A unit that fails to start writes little or nothing itself. The failure lives in systemd’s own view:
systemctl --failed
systemctl status yourapp
And a process killed by the OOM killer leaves its evidence in the kernel ring buffer, at priority 2 rather than 3:
journalctl -k -p err -b
journalctl -k -g "Out of memory" -b
Worth checking both before concluding that nothing went wrong.
The commands above answer “what went wrong on this machine”. During an incident that is the wrong question. You want the errors from all of them, in one list, sorted by time.
Ten hosts is ten SSH sessions and ten separate answers, and you have to hold the ordering in your head. It is also the wrong tool for anything recurring: to notice an error you have to already be looking.
Shipping the journals somewhere central fixes both. The
CL Agent reads journald and forwards it, keeping the fields
intact, so PRIORITY and _SYSTEMD_UNIT are still there on the other side.
Central Logging stores each line in SQLite. -p err becomes a WHERE clause:
SELECT datetime(CAST(json_extract(msg, '$.__REALTIME_TIMESTAMP') AS INTEGER) / 1000000, 'unixepoch') AS at,
json_extract(msg, '$._HOSTNAME') AS host,
json_extract(msg, '$._SYSTEMD_UNIT') AS unit,
json_extract(msg, '$.MESSAGE') AS message
FROM logs
WHERE json_valid(msg)
AND CAST(json_extract(msg, '$.PRIORITY') AS INTEGER) <= 3
ORDER BY at DESC
LIMIT 50;
Two details in there are load-bearing.
CAST is not optional. journald stores PRIORITY as a string, so
json_extract(msg, '$.PRIORITY') <= 3 compares text against a number. SQLite
sorts every text value above every integer, so that test matches zero rows
and raises no error. You get an empty result and no hint why.
The same rule points the other way for thresholds. Without a cast, a >=
comparison matches every text value rather than none — a disk-usage rule
written as json_extract(msg, '$.used_pct') >= 85 fires on a disk at 3% as
soon as one producer quotes the number. >= over-matches, <= under-matches,
and neither says a word.
Keep the json_valid(msg) guard. json_extract errors on the first
non-JSON line it meets. The guard in WHERE protects the terms after it,
because SQLite short-circuits AND. It does not protect the SELECT list, so
the guard has to remove the bad rows rather than merely precede them.
The query above has exactly the blind spot the command line has: an application logging errors at priority 6 will not appear. Search the text as well, using the full-text index:
SELECT datetime(logs.timestamp, 'unixepoch') AS received, logs.msg
FROM logs_fts
JOIN logs ON logs.id = logs_fts.rowid
WHERE logs_fts MATCH 'error OR fatal OR panic'
ORDER BY rank
LIMIT 50;
In practice you want both. Priority catches what the system considers serious; text catches what your application considers serious. Neither is a superset of the other.
Once the query is right, the alert rule is the same query with the columns
thrown away. Central Logging runs each rule and only checks whether the result
set had anything in it, so SELECT 1 ... LIMIT 1 is the whole job:
SELECT 1 FROM logs
WHERE json_valid(msg)
AND CAST(json_extract(msg, '$.PRIORITY') AS INTEGER) <= 3
AND timestamp > CAST(strftime('%s', 'now', '-10 minutes') AS INTEGER)
LIMIT 1;
Set it to alert on results. Rules are evaluated every five minutes and the notification goes out within a minute of firing, so pick a window a little wider than the interval — ten minutes here — or an error landing between two evaluations slips through the gap.
The text-matching counterpart, for the applications that log at info:
SELECT 1 FROM logs_fts WHERE msg MATCH 'panic OR fatal' LIMIT 1;
Two things worth knowing before you rely on either. There is no all-clear message — a rule that stops matching simply stops firing. And the Max Frequency setting on the rule is what stops a broken deploy from sending you four hundred notifications; the shortest option is five minutes, matching the evaluation interval.
Alerting on errors in your logs covers the rule shapes in more depth, including the one almost nobody writes: alert when a source goes quiet. An application that has crashed logs no errors at all, and every rule on this page stays silent for it.
-g, and finding the errors that -p cannot see.--since and --until, and the midnight trap.-u in depth, and what to check when a unit prints nothing.-b -1 and reading the errors from the boot that ended.💌 Get notified on new features and updates