]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
journalctl: optionally delay --follow exit for a journal synchronization
authorLennart Poettering <lennart@poettering.net>
Tue, 25 Mar 2025 12:01:50 +0000 (08:01 -0400)
committerLennart Poettering <lennart@poettering.net>
Tue, 13 May 2025 13:39:57 +0000 (15:39 +0200)
Let's optionally issue a Varlink Synchronize() call in --follow mode
when asked to terminate. This is useful so that the tool can be called
and it is guaranteed it processed all messages generated before the
request to exit before it exits.

We want this in "systemd-run -v" in particular, so that we can be sure
we are not missing any log output from the invoked service before it
exits

man/journalctl.xml
src/journal/journalctl-show.c
src/journal/journalctl-varlink.c
src/journal/journalctl-varlink.h
src/journal/journalctl.c
src/journal/journalctl.h
src/shared/fork-journal.c

index 7146c9400f850daea6570a201589957bc0434f5e..2a3af870fd83520e299e7fd904d421fcd33d59aa 100644 (file)
         --"), any warning messages regarding inaccessible system journals when run as a normal
         user.</para></listitem>
       </varlistentry>
+
+      <varlistentry>
+        <term><option>--synchronize-on-exit=</option></term>
+
+        <listitem><para>Takes a boolean argument. If true and operating in <option>--follow</option> mode, a
+        journal synchronization request (equivalent to <command>journalctl --sync</command>) is issued when
+        <constant>SIGTERM</constant>/<constant>SIGINT</constant> is received, and log output continues until
+        this request completes. This is useful for synchronizing journal log output to the runtime of
+        services or external events, ensuring that any log data enqueued to the logging subsystem by
+        the time <constant>SIGTERM</constant>/<constant>SIGINT</constant> is issued is guaranteed to be
+        processed and displayed by the time log output ends. Defaults to false.</para>
+
+        <xi:include href="version-info.xml" xpointer="v258"/></listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
 
index 3519aa209ede044c6c5a935e94c128036526b3b8..3c69e6ec5589ef8fadc2d3fbb0b7c22e61d27fa7 100644 (file)
@@ -4,6 +4,7 @@
 
 #include "sd-daemon.h"
 #include "sd-event.h"
+#include "sd-varlink.h"
 
 #include "ansi-color.h"
 #include "fileio.h"
@@ -11,7 +12,7 @@
 #include "journalctl-filter.h"
 #include "journalctl-show.h"
 #include "journalctl-util.h"
-#include "log.h"
+#include "journalctl-varlink.h"
 #include "logs-show.h"
 #include "terminal-util.h"
 
@@ -27,11 +28,15 @@ typedef struct Context {
         sd_id128_t previous_boot_id;
         sd_id128_t previous_boot_id_output;
         dual_timestamp previous_ts_output;
+        sd_event *event;
+        sd_varlink *synchronize_varlink;
 } Context;
 
 static void context_done(Context *c) {
         assert(c);
 
+        c->synchronize_varlink = sd_varlink_flush_close_unref(c->synchronize_varlink);
+        c->event = sd_event_unref(c->event);
         sd_journal_close(c->journal);
 }
 
@@ -270,15 +275,14 @@ static int show(Context *c) {
         return n_shown;
 }
 
-static int show_and_fflush(Context *c, sd_event_source *s) {
+static int show_and_fflush(Context *c) {
         int r;
 
         assert(c);
-        assert(s);
 
         r = show(c);
         if (r < 0)
-                return sd_event_exit(sd_event_source_get_event(s), r);
+                return sd_event_exit(c->event, r);
 
         fflush(stdout);
         return 0;
@@ -293,10 +297,10 @@ static int on_journal_event(sd_event_source *s, int fd, uint32_t revents, void *
         r = sd_journal_process(c->journal);
         if (r < 0) {
                 log_error_errno(r, "Failed to process journal events: %m");
-                return sd_event_exit(sd_event_source_get_event(s), r);
+                return sd_event_exit(c->event, r);
         }
 
-        return show_and_fflush(c, s);
+        return show_and_fflush(c);
 }
 
 static int on_first_event(sd_event_source *s, void *userdata) {
@@ -305,7 +309,7 @@ static int on_first_event(sd_event_source *s, void *userdata) {
 
         assert(s);
 
-        r = show_and_fflush(c, s);
+        r = show_and_fflush(c);
         if (r < 0)
                 return r;
 
@@ -334,29 +338,92 @@ static int on_first_event(sd_event_source *s, void *userdata) {
         return 0;
 }
 
+static int on_synchronize_reply(
+                sd_varlink *vl,
+                sd_json_variant *parameters,
+                const char *error_id,
+                sd_varlink_reply_flags_t flags,
+                void *userdata) {
+
+        Context *c = ASSERT_PTR(userdata);
+        int r;
+
+        assert(vl);
+
+        if (error_id)
+                log_warning("Failed to synchronize on Journal, ignoring: %s", error_id);
+
+        r = show_and_fflush(c);
+        if (r < 0)
+                return r;
+
+        return sd_event_exit(c->event, EXIT_SUCCESS);
+}
+
 static int on_signal(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
+        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL;
+        Context *c = ASSERT_PTR(userdata);
+        int r;
+
         assert(s);
         assert(si);
         assert(IN_SET(si->ssi_signo, SIGTERM, SIGINT));
 
-        return sd_event_exit(sd_event_source_get_event(s), si->ssi_signo);
+        if (!arg_synchronize_on_exit)
+                goto finish;
+
+        if (c->synchronize_varlink) /* Already pending? Then exit immediately, so that user can cancel the sync */
+                return sd_event_exit(c->event, EXIT_SUCCESS);
+
+        r = varlink_connect_journal(&vl);
+        if (r < 0) {
+                log_error_errno(r, "Failed to connect to Journal Varlink IPC interface, ignoring: %m");
+                goto finish;
+        }
+
+        /* Set a low priority on the idle event handler, so that we show any log messages first */
+        r = sd_varlink_attach_event(vl, c->event, SD_EVENT_PRIORITY_IDLE);
+        if (r < 0) {
+                log_warning_errno(r, "Failed to attach Varlink connectio to event loop: %m");
+                goto finish;
+        }
+
+        r = sd_varlink_bind_reply(vl, on_synchronize_reply);
+        if (r < 0) {
+                log_warning_errno(r, "Failed to bind synchronization reply: %m");
+                goto finish;
+        }
+
+        (void) sd_varlink_set_userdata(vl, c);
+
+        r = sd_varlink_invoke(vl, "io.systemd.Journal.Synchronize", /* parameters= */ NULL);
+        if (r < 0) {
+                log_warning_errno(r, "Failed to issue synchronization request: %m");
+                goto finish;
+        }
+
+        c->synchronize_varlink = TAKE_PTR(vl);
+        return 0;
+
+finish:
+        return sd_event_exit(c->event, si->ssi_signo);
 }
 
-static int setup_event(Context *c, int fd, sd_event **ret) {
-        _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+static int setup_event(Context *c, int fd) {
         int r;
 
         assert(arg_follow);
         assert(c);
         assert(fd >= 0);
-        assert(ret);
+        assert(!c->event);
 
+        _cleanup_(sd_event_unrefp) sd_event *e = NULL;
         r = sd_event_default(&e);
         if (r < 0)
                 return log_error_errno(r, "Failed to allocate sd_event object: %m");
 
-        (void) sd_event_add_signal(e, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, on_signal, NULL);
-        (void) sd_event_add_signal(e, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, on_signal, NULL);
+        (void) sd_event_add_signal(e, /* ret_event_source= */ NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, on_signal, c);
+        (void) sd_event_add_signal(e, /* ret_event_source= */ NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, on_signal, c);
 
         r = sd_event_add_io(e, NULL, fd, EPOLLIN, &on_journal_event, c);
         if (r < 0)
@@ -378,7 +445,7 @@ static int setup_event(Context *c, int fd, sd_event **ret) {
                         return log_error_errno(r, "Failed to add defer event source: %m");
         }
 
-        *ret = TAKE_PTR(e);
+        c->event = TAKE_PTR(e);
         return 0;
 }
 
@@ -466,16 +533,15 @@ int action_show(char **matches) {
         }
 
         if (arg_follow) {
-                _cleanup_(sd_event_unrefp) sd_event *e = NULL;
                 int sig;
 
                 assert(poll_fd >= 0);
 
-                r = setup_event(&c, poll_fd, &e);
+                r = setup_event(&c, poll_fd);
                 if (r < 0)
                         return r;
 
-                r = sd_event_loop(e);
+                r = sd_event_loop(c.event);
                 if (r < 0)
                         return r;
                 sig = r;
index 1f6671c01d507e1a104b2b24f28598979613cb70..5c5f5827ba9c85913f69418729b803d02b8caa7f 100644 (file)
@@ -11,7 +11,7 @@
 #include "log.h"
 #include "varlink-util.h"
 
-static int varlink_connect_journal(sd_varlink **ret) {
+int varlink_connect_journal(sd_varlink **ret) {
         _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL;
         const char *address;
         int r;
index e10983a0482cf8d3d9fa0b763ab697afb24d187d..8b35a30d1670a09d001deed2b4775568c80577d0 100644 (file)
@@ -1,6 +1,10 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include "sd-varlink.h"
+
+int varlink_connect_journal(sd_varlink **ret);
+
 int action_flush_to_var(void);
 int action_relinquish_var(void);
 int action_rotate(void);
index 2c6c919d42e08e2659514d5b1b0f4a398f88a61f..13ebfda579c815188cfbba36db032202ded15053 100644 (file)
@@ -95,6 +95,7 @@ char *arg_pattern = NULL;
 pcre2_code *arg_compiled_pattern = NULL;
 PatternCompileCase arg_case = PATTERN_COMPILE_CASE_AUTO;
 ImagePolicy *arg_image_policy = NULL;
+bool arg_synchronize_on_exit = false;
 
 STATIC_DESTRUCTOR_REGISTER(arg_cursor, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_cursor_file, freep);
@@ -269,6 +270,8 @@ static int help(void) {
                "     --no-tail               Show all lines, even in follow mode\n"
                "     --truncate-newline      Truncate entries by first newline character\n"
                "  -q --quiet                 Do not show info messages and privilege warning\n"
+               "     --synchronize-on-exit=BOOL\n"
+               "                             Wait for Journal synchronization before exiting\n"
                "\n%3$sPager Control Options:%4$s\n"
                "     --no-pager              Do not pipe output into a pager\n"
                "  -e --pager-end             Immediately jump to the end in the pager\n"
@@ -357,6 +360,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_OUTPUT_FIELDS,
                 ARG_NAMESPACE,
                 ARG_LIST_NAMESPACES,
+                ARG_SYNCHRONIZE_ON_EXIT,
         };
 
         static const struct option options[] = {
@@ -430,6 +434,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "output-fields",        required_argument, NULL, ARG_OUTPUT_FIELDS        },
                 { "namespace",            required_argument, NULL, ARG_NAMESPACE            },
                 { "list-namespaces",      no_argument,       NULL, ARG_LIST_NAMESPACES      },
+                { "synchronize-on-exit",  required_argument, NULL, ARG_SYNCHRONIZE_ON_EXIT  },
                 {}
         };
 
@@ -973,6 +978,14 @@ static int parse_argv(int argc, char *argv[]) {
 
                         break;
                 }
+
+                case ARG_SYNCHRONIZE_ON_EXIT:
+                        r = parse_boolean_argument("--synchronize-on-exit", optarg, &arg_synchronize_on_exit);
+                        if (r < 0)
+                                return r;
+
+                        break;
+
                 case '?':
                         return -EINVAL;
 
index 24be1d2f9be80b82fd4583412181799775c5cdbd..25b9c9c3eb45dbc5c41e1dd72200d37a04c5d12e 100644 (file)
@@ -99,6 +99,7 @@ extern char *arg_pattern;
 extern pcre2_code *arg_compiled_pattern;
 extern PatternCompileCase arg_case;
 extern ImagePolicy *arg_image_policy;
+extern bool arg_synchronize_on_exit;
 
 static inline bool arg_lines_needs_seek_end(void) {
         return arg_lines >= 0 && !arg_lines_oldest;
index 6b314134f563265e163209fe8e15743202b9c5b8..ccefc74df039a0032a73a954b0444ab6764de880 100644 (file)
@@ -127,7 +127,8 @@ int journal_fork(RuntimeScope scope, const char * const *units, PidRef *ret_pidr
                         "-q",
                         "--follow",
                         "--no-pager",
-                        "--lines=1");
+                        "--lines=1",
+                        "--synchronize-on-exit=yes");
         if (!argv)
                 return log_oom_debug();