To demonstrate this, we’ll create a new Python package imaginatively named py_sysd_pkg (Python systemd package). Inside that we’ll add some functions for reading/writing journald entries, and hook them up to entry points using Poetry.
Next we’ll update our write() command to write to some custom fields. As quoted from the journald man page (emphasis added):
[…] Primarily, field values are formatted UTF-8 text strings — binary encoding is used only where formatting as UTF-8 text strings makes little sense. New fields may freely be defined by applications, but a few fields have special meanings, which are listed below. […]
So we can define custom fields in our log entries, but some fields have special meanings, and so we should avoid those.
The journald.send() command lets our application define custom fields just by using uppercase keyword arguments. So to add the following custom fields:
FIELD2 (UTF-8)
FIELD3 (UTF-8)
BINARY (binary)
We can do:
Note the we use the b prefix to pass in bytes to the BINARY field, rather than a str. Later we’ll see the BINARY field comes back out again as bytes.
Then run poetry run test-write to execute the write() command, and run journalctl again to check the output:
We can see the new log entries have been added, but our custom fields are not displayed. They have been logged (as we will see later), but are not shown in the default journalctl output.
So far we’ve been reading all the journald entries. It will be more useful if we can filter to read only the entries we care about. One way is to filter by executable, e.g. find all logs generated by a specific instance of Python.
To filter by the executable, first we need to find the full path to the executable. To do so, view the log in JSON format (pretty-printed so that it is easier to read).
Then find the _EXC field for our log entry. This contains the full path to the executable, which we need for filtering:
Copy the value of the _EXE field, and add a new read_only_python() command which filters upon that value:
Update pyproject.toml with a new entry point:
Test the new command:
Much better - it only prints out messages from our executable.
Assuming your Python code is being run as a systemd service, a more useful approach is to filter by systemd service (or “unit”).
If you don’t already have a Python systemd service, follow these steps to create a service called example-service. For the next section we’ll assume this service has been started, run for a few seconds and logged to journald, then stopped.
Assuming our service is called example-service, add a read_only_service() function which filters by the systemd unit example-service.service. In our case, some of the journal entries from this service also use the custom field MY_COUNTER:
Update pyproject.toml to add an entry point for the read_only_service() function with a test-read-only-service command:
Run the command to test if we can read journald entries from that service:
That worked! We’re printing out only the entries for the example-service.service systemd unit. Additionally, we’re printing the MY_COUNTER field if it exists.