Status, Reloading and Journalling in Systemd
This is the second post on how to create a service in systemd
. In the first post we created an autotools project and were able to start, stop and get basic status on the service. In this post we’ll build on the autotools project to add logging to syslog
and journald
. We will also give an example on how we could reload the service with a systemctl reload foo
. We will also use the sd_notify(3) API to notify the service manager the status of what the service is doing.
We Need Logging!
To properly develop, maintain, troubleshoot, maintain and administer any application you need logging. Good logging is one of the things that makes applications great. Our previous service had printf
statements. This partially works if we print to stderr
with our log level - more on this later. To log to both journald
and syslog
we can employ the sd_journal(3) API or we can use stderr
-based logging as implemented by systemd. See sd-daemon(3).
Refresher on our Service
Here is the basic service as a starting point - this is before any changes we’re going to make. We have one printf
, but it doesn’t go to a log anywhere. In fact the printf
doesn’t go to the journal.
// file main.c which we will compile to foo
#include "../config.h" // needed for autotools
#include <stdio.h>
#include <unistd.h>
int
main(int argc, char *argv[])
{
while(1)
{
printf("going to sleep\n");
sleep(10);
}
return 0;
}
We can’t proceed unless we have these header files!
We’re going to use libsystemd
. To do this you’re going to need to make sure this command returns an installed library:
$ pkg-config --cflags --libs libsystemd
-lsystemd
With this library we can add the linker flag -lsystemd
and the following C headers:
#include <systemd/sd-daemon.h>
#include <systemd/sd-journal.h>
These files would typically be found in /usr/include/systemd
. This was done on a Raspberry Pi running Rasbian 10 (buster). In order install the library I had to do an apt install libsystemd-dev
. On another machine with Arch Linux the directories already exist. This is system dependent.
The Updated Service
Let’s add some journalling, status updates, and ability to reload the service. Effectively, we’ve peppered through the code calls to sd_notify(3)
and fprintf(stderr, LOG_X ...)
. We will also use the sd_journal(3)
API which are less portable but are used for example purposes. These can be seen in the code comments.
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <systemd/sd-daemon.h>
#include <systemd/sd-journal.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
// a global variable for our journal
sd_journal *journal;
// A signal handler for reloading
static void
reload(int sig)
{
fprintf(stderr, SD_NOTICE "foo service is reloading\n");
sd_notifyf(0, "RELOADING=1\n"
"STATUS=Reloading Configuration\n"
"MAINPID=%lu",
(unsigned long) getpid());
sleep(10);
sd_notify(0, "READY=1\nSTATUS=Ready\n");
}
// A signal handler for stopping
static void
stop(int sig)
{
fprintf(stderr, SD_NOTICE "foo service is stopping\n");
sd_notify(0, "STOPPING=1");
sd_journal_close(journal);
exit(0);
}
int
main(int argc, char *argv[])
{
// Install our signal handlers
if(signal(SIGHUP, reload) == SIG_ERR)
{
sd_notifyf(0, "STATUS=Failed to install signal handler for service reload %s\n"
"ERRNO=%i",
strerror(errno),
errno);
}
if(signal(SIGTERM, stop) == SIG_ERR)
{
sd_notifyf(0, "STATUS=Failed to install signal handler for stopping service %s\n"
"ERRNO=%i",
strerror(errno),
errno);
}
// open the journal
sd_journal_open(&journal, 0);
fprintf(stderr, SD_NOTICE "foo service started\n");
fprintf(stdout, "This won't be seen in the journal or syslog!\n");
fprintf(stdout, SD_NOTICE "This ALSO will not be seen in the journal or syslog!\n");
sd_journal_print(LOG_NOTICE, "This will only be seen when run by systemd, never on the terminal\n");
// tell the service manager we're in the ready state
sd_notify(0, "READY=1");
while(1)
{
fprintf(stderr, SD_NOTICE "Hello World!\n");
sleep(10);
}
return 0;
}
The service is now notifying with state transistions, catching signals for reload and printing to journald. Note, the way we catch SIGHUP
isn’t ideal. I’ll talk about this in the next section.
To find information here see sd-daemon(3)
for stderr
logging.
Modify the Service Unit for Reload and Journal
See systemd.exec(5) for the StandardOutput
and StandardError
options.
|
|
By having Type=notify
we tell systemd
we are following the sd_notify(3)
API. See how the reload looks below.
Observing the results
Now when can see this:
$ sudo systemctl start foo
$ sudo systemctl status foo
● foo.service - A Example Systemd Service
Loaded: loaded (/lib/systemd/system/foo.service; static; vendor preset: enabled)
Active: active (running) since Thu 2020-06-25 16:47:07 BST; 7s ago
Main PID: 15166 (foo)
Tasks: 1 (limit: 2077)
Memory: 216.0K
CGroup: /system.slice/foo.service
└─15166 /usr/local/bin/foo
Jun 25 16:47:07 pi2 systemd[1]: Started A Example Systemd Service.
Jun 25 16:47:07 pi2 foo[15166]: foo service started
Jun 25 16:47:07 pi2 foo[15166]: Hello World!
Jun 25 16:47:07 pi2 foo[15166]: This will only be seen when run by systemd, never on the terminal
$ journalctl -u foo
...
Jun 25 16:47:07 pi2 foo[15166]: foo service started
Jun 25 16:47:07 pi2 foo[15166]: Hello World!
Jun 25 16:47:07 pi2 foo[15166]: This will only be seen when run by systemd, never on the terminal
Jun 25 16:47:17 pi2 foo[15166]: Hello World!
Jun 25 16:47:27 pi2 foo[15166]: Hello World!
Testing the Reload
We can reload the service and see our signal handler and sd_notify(3)
calls being made:
$ sudo systemctl reload foo # this takes 10 seconds because of the sleep
$ sudo systemctl status foo # done in a different terminal
● foo.service - A Example Systemd Service
Loaded: loaded (/lib/systemd/system/foo.service; static; vendor preset: enabl
Active: reloading (reload) since Fri 2020-06-26 23:18:26 BST; 55s ago
Process: 29242 ExecReload=/bin/kill -HUP $MAINPID (code=exited, status=0/SUCCE
Main PID: 29092 (foo)
Status: "Reloading Configuration"
Tasks: 1 (limit: 2077)
Memory: 392.0K
CGroup: /system.slice/foo.service
└─29092 /usr/local/bin/foo
Jun 26 23:18:44 pi2 foo[29092]: foo service is reloading
$ sudo systemctl status foo # reload is complete
● foo.service - A Example Systemd Service
Loaded: loaded (/lib/systemd/system/foo.service; static; vendor preset: enabl
Active: active (running) since Fri 2020-06-26 23:18:26 BST; 1min 2s ago
Process: 29242 ExecReload=/bin/kill -HUP $MAINPID (code=exited, status=0/SUCCE
Main PID: 29092 (foo)
Status: "Ready"
Tasks: 1 (limit: 2077)
Memory: 392.0K
CGroup: /system.slice/foo.service
└─29092 /usr/local/bin/foo
Jun 26 23:18:54 pi2 systemd[1]: Reloaded A Example Systemd Service.
One note on the reload we send a SIGHUP
. This isn’t recommended. Here is the text from systemctl.service(5).
Note however that reloading a daemon by sending a signal (as with the example line above)
is usually not a good choice, because this is an asynchronous operation and hence not
suitable to order reloads of multiple services against each other. It is strongly
recommended to set ExecReload= to a command that not only triggers a configuration reload
of the daemon, but also synchronously waits for it to complete.
Changes to Autotools
We had to link in the systemd library.
configure.ac
We need to check that the systemd
library is on the system and throw an error if not. Also, we’ll check for the systemd
header files.
|
|
src/Makefile.am
We need to link in the systemd
library.
# src/Makefile.am
AM_LDFLAGS=-lsystemd
bin_PROGRAMS = foo
foo_SOURCES = main.c
Downloading and Using
A new version of foo-1.1 was created.
wget http://lloydrochester.com/code/foo-1.1.tar.gz
tar zxf foo-1.1.tar.gz
cd foo
./configure
make
sudo make install
sudo systemctl start foo