journalctl-not: NOT matches for journalctl

A wrapper around journalctl that hides entries by unit, identifier, field value or message pattern and keeps the normal journalctl output, including colors, pager, -n and -f.

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, cat and json are supported. Others are rejected with an error.
  • -x/--catalog is ignored with a warning. With colors on, journalctl adds clickable links and highlights --grep matches; 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 -f or -n 100 the difference is not noticeable.
  • FIELD!=VALUE compares 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.