Last updated: 10 May 2021
Systems that use systemd write log entries for the kernel and various services to a journal. These log entries are stored in a binary format and can be inspected via the journalctl
utility. This article looks at the utility. We also got an article about logging on Linux, which covers how to configure systemd-journald
.
journalctl
doesn’t replace all log files. Some services, such as Apache, have their own logs. Also, rsyslog
is still a thing on servers that run systemd. It works differently – rsyslog
nowadays gets its log entries from systemd-journald
. However, the end result is largely the same. You still got log files such as /var/log/messages and /var/log/secure.
As said, journal entries are stored in a binary format. Each entry has a large number of fields that can be displayed in several output formats. By default, log entries look much like entries you find in traditional syslog log files, such as /var/log/messages. Here is an example of a journalctl
entry:
Sep 24 00:59:37 server.example.net sshd[17255]: Invalid user admin from 59.148.43.97 port 54128
And here is the same entry displayed in JSON format:
{ "__CURSOR" : "s=a285c16fef31c7;i=b82;b=8d04cfd2dd066c;m=c5e8f;t=59341f0;x=11a8f0", "__REALTIME_TIMESTAMP" : "1569283177914864", "__MONOTONIC_TIMESTAMP" : "53113740943", "_BOOT_ID" : "8d04cfd2dd9744b888u8d3f9cb52066c", "PRIORITY" : "6", "_UID" : "0", "_GID" : "0", "_SYSTEMD_SLICE" : "system.slice", "_MACHINE_ID" : "21a223f3763983e62f89dc6a5b90c0d1", "_HOSTNAME" : "server.example.net", "_CAP_EFFECTIVE" : "1fffffffff", "_TRANSPORT" : "syslog", "SYSLOG_FACILITY" : "10", "SYSLOG_IDENTIFIER" : "sshd", "_COMM" : "sshd", "_EXE" : "/usr/sbin/sshd", "_SYSTEMD_CGROUP" : "/system.slice/sshd.service", "_SYSTEMD_UNIT" : "sshd.service", "_SELINUX_CONTEXT" : "system_u:system_r:sshd_t:s0-s0:c0.c1023", "_CMDLINE" : "sshd: unknown [priv]", "SYSLOG_PID" : "17255", "MESSAGE" : "Invalid user admin from 59.148.43.97 port 54128", "_PID" : "17255", "_SOURCE_REALTIME_TIMESTAMP" : "1569283177913968" }
As you can see, the entry contains quite a few fields, including a boot ID (_BOOT_ID
) and priority (PRIORITY
). You can use these fields to filter the data, which is what we will look at now.
The --since
and --until
options let you specify a timestamp in the format YYYY-MM-DD HH:MM:SS
. You don’t have to include the time, and if you do include the time you may leave out the seconds (i.e. you can use HH:MM
):
# journalctl --since="2019-09-26" --until="2019-09-26 11:00"
You can also use strings such as “yesterday”, “today” and “10m ago”:
# journalctl --since "10m ago"
The -u
(--unit
option is used to show entries related to a systemd unit. So, to return log entries for sshd you can use the following:
# journalctl -u sshd.service -- Logs begin at Mon 2021-02-08 14:59:39 GMT, end at Mon 2021-03-29 22:20:00 BST. -- Feb 08 20:26:43 centos8 systemd[1]: Stopping OpenSSH server daemon... Feb 08 20:26:46 centos8 sshd[1053]: Received signal 15; terminating. Feb 08 20:26:49 centos8 systemd[1]: sshd.service: Succeeded. Feb 08 20:26:49 centos8 systemd[1]: Stopped OpenSSH server daemon. ...
Alternatively, you can also use the -t
(--identifier
) option to filter by the SYSLOG_IDENTIFIER
field. You can use this to retrieve entries that are not related to a systemd unit. For instance, you can filter out kernel messages or entries about the use of the su
and sudo
commands:
# journalctl -t su -- Logs begin at Mon 2021-02-08 14:59:39 GMT, end at Mon 2021-03-29 22:30:16 BST. -- ... Mar 29 19:30:39 centos8 su[2339]: (to root) example on pts/0 Mar 29 19:30:39 centos8 su[2339]: pam_unix(su:session): session opened for user root by example(uid=1000)
The JSON output I showed includes a priority field. The priority number indicates the importance of the message. A priority of 0 means we got an emergency, while 7 is a humble debug message. In other words, the lower the number, the higher the priority:
The -p
option lets you filter messages by priority. If, say, you want to see all errors you can use either of these commands:
# journalctl -p 3 # journalctl -p err
Either command prints entries with a priority of 3 or lower (i.e. 3, 2, 1 and 0). You can also specify a range. For instance, to limit the output to levels 2 and 3 you can use journalctl -p 2..3
.
The output of journalctl
includes the name of the service followed by the process ID (in square brackets). You can use the _PID
option to get all entries for a specific process ID:
# journalctl _PID=17255 -- Logs begin at Mon 2021-02-08 14:59:39 GMT, end at Mon 2021-03-29 22:30:16 BST. -- Mar 24 00:59:37 server.example.net sshd[17255]: Invalid user admin from 59.148.43.97 port 54128 Mar 24 00:59:37 server.example.net sshd[17255]: input_userauth_request: invalid user admin [preauth] Mar 24 00:59:39 server.example.net sshd[17255]: error: maximum authentication attempts exceeded for invalid user admin from 59.148.43.97 port 54128 ssh2 [preauth] Mar 24 00:59:39 server.example.net sshd[17255]: Disconnecting: Too many authentication failures [preauth]
You can monitor logs as entries are being written using the -f
(--follow
) option. This works exactly like tail -f
. Of course, you can filter a specific unit at the same time. For example, here I’m following entries for Dovecot:
# journalctl -f -u dovecot.service
And now that I’ve mentioned tail
I should also highlight the -n
option. As you might have guessed, this option works exactly like tail -n
– it displays the last x entries. For instance, to view the last three entries for the Dovecot service you can use:
# journalctl -u dovecot.service -n 3
Although the filtering options covered so far are quite flexible they don’t necessarily return the data you want. For instance, when you are looking into SSH login attempts you might want to filter entries based on the IP address shown in the MESSAGE
field. On Centos 7 and CloudLinux 7 servers you can do that by piping the output of journalctl
to grep
:
# journalctl -u sshd.service | grep 59.148.43.97 Mar 24 00:59:37 server.example.net sshd[17255]: Invalid user admin from 59.148.43.97 port 54128 Mar 24 00:59:39 server.example.net sshd[17255]: error: maximum authentication attempts exceeded for invalid user admin from 59.148.43.97 port 54128 ssh2 [preauth]
RHEL8-based systems ship with a version of journalctl
that includes the -g
(--grep
) option, which lets you grep the message field. That means that you can instead use this command:
# journalctl -u sshd.service -g 59.148.43.97
This article aimed to give a brief overview of journalctl
. There are many topics I didn’t cover. If you want to learn more, the official documentation can be found at freedesktop.org.