The problem
journalctl can combine matches with AND and OR, but it has no NOT. There is no
option to hide a noisy service, a syslog identifier or a recurring message. The
common workaround is journalctl | grep -v. It loses the colors and the pager
unless SYSTEMD_COLORS is set, it matches lines inside multi-line messages,
and it makes -n 20 show fewer than 20 lines, because grep removes lines after
journalctl has counted them. The other workaround is
journalctl -o json | jq 'select(...)', which filters correctly but prints raw
JSON.
The demand is documented:
- systemd/systemd#2720, "RFE: add NOT operator to journal matching", has been open since 2016 and has 131 reactions and 22 comments. The maintainer's answer was that NOT is on the TODO list but hard to support with the journal indexes. The request goes back to freedesktop bug 55868 from 2012.
- The Super User question "How to exclude journalctl entries using a filter?" has a score of 26 and about 39,000 views.
- systemd 256 added
-T/--exclude-identifier, which covers syslog identifiers only. Ubuntu 24.04 (systemd 255), Debian 12 (252) and RHEL 9 (252) do not have it.
How it works
journalctl-not passes every
journalctl option through, runs the command with -o json, drops each entry
that matches one of the exclusions and prints the rest. The exclusions are
FIELD!=VALUE, --exclude-unit and --exclude-user-unit (with the same
fields that journalctl -u uses), -T/--exclude-identifier as in systemd 256,
and --exclude-grep for a regular expression on the message. An entry is
hidden when any exclusion matches.
The text output is a port of systemd's own formatting code (logs-show.c and
strip_tab_ansi()), so for the supported output modes the result is the same
as journalctl's, byte for byte. -n N counts the entries that remain after the
exclusions: the tool reads the journal backwards until it has N of them. With
-f it prints those and then follows from the newest entry.
In this example, seven entries were written with systemd-cat. The "health
check ok" lines have debug priority (7):
$ journalctl -t demo-app -t demo-sync --no-hostname
Nov 01 06:45:23 demo-app[97742]: request took 812 ms
Nov 01 06:45:23 demo-app[97746]: health check ok
Nov 01 06:45:23 demo-app[97750]: health check ok
Nov 01 06:45:23 demo-app[97754]: upstream timed out
Nov 01 06:45:23 demo-sync[97758]: Starting nightly sync
Nov 01 06:45:23 demo-app[97762]: disk full, retrying
Nov 01 06:45:23 demo-app[97766]: health check ok
$ journalctl-not -t demo-app -t demo-sync -n 3 PRIORITY!=7 -T demo-sync --no-hostname
Nov 01 06:45:23 demo-app[97742]: request took 812 ms
Nov 01 06:45:23 demo-app[97754]: upstream timed out
Nov 01 06:45:23 demo-app[97762]: disk full, retrying
-n 3 returns the last three entries that are left. The same -n 3 followed
by grep -v would have printed one line. The tool needs Python 3.9 or newer and
has no dependencies outside the standard library.
Testing
The unit tests cover argument parsing, the exclusion rules, the rendering of
timestamps, multi-line messages, binary fields and colors, and complete runs
against a stand-in journalctl that replays a JSON fixture. These runs check that
-n counts the remaining entries and that -f continues after the newest
cursor.
tests/golden.sh writes test entries into the real journal and compares
journalctl-not without exclusions against journalctl byte for byte: in eight
output modes, with colors, in two time zones, and over the whole journal of the
last six hours and the kernel log. It also compares exclusions with journalctl
filters that mean the same, for example PRIORITY!=7 with -p info. CI runs
the unit tests and the golden test on Ubuntu 24.04 (systemd 255) with Python
3.9, 3.12 and 3.13. While writing the golden test, two journalctl 255 quirks
turned up that journalctl-not does not reproduce: with -n N --until T and fewer than N
matches, journalctl also prints entries after T, and with --grep and -n it
prints them newest first.
Limitations
- Only the output modes
short,short-precise,short-iso,short-iso-precise,short-full,short-unix,short-monotonic,catandjsonare supported. Others are rejected with an error. -x/--catalogis ignored with a warning. With colors on, journalctl adds clickable links and highlights--grepmatches; journalctl-not does not.- Month and weekday names are always English.
- The exclusions run in Python after journalctl has read the entries. Printing a
whole journal of 600k lines takes about 3.5 times as long as with journalctl.
For
-for-n 100the difference is not noticeable. FIELD!=VALUEcompares the whole value and keeps entries that do not have the field.- The formatting follows systemd 255. Newer versions may change details such as colors.
The code and installation instructions are at github.com/Jwrede/journalctl-not.