]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
journalctl: add --list-invocations command and -I/--invocation options
authorYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 25 Apr 2024 04:38:24 +0000 (13:38 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 31 Jul 2024 22:31:44 +0000 (07:31 +0900)
The --list-invocations command is similar to --list-boots, but shows
invocation IDs of specified unit. This should be useful when showing
a specific invocation of a unit.

The --invocation option is similar to --boot, but takes a invocation ID
or an offset. The -I option is equivalent to --invocation=0.

man/journalctl.xml
shell-completion/bash/journalctl
src/journal/journalctl-filter.c
src/journal/journalctl-misc.c
src/journal/journalctl-misc.h
src/journal/journalctl-util.c
src/journal/journalctl-util.h
src/journal/journalctl.c
src/journal/journalctl.h

index 5fd92631169c8862aa87dc374e4d13403d717f27..0cc2b72acc5149a5f7b9034ffb47565936df8715 100644 (file)
         <xi:include href="version-info.xml" xpointer="v198"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>-I</option></term>
+        <term><option>--invocation=<replaceable>ID</replaceable><optional><replaceable>±offset</replaceable></optional>|<replaceable>offset</replaceable></option></term>
+
+        <listitem>
+          <para>Show messages from a specific invocation of unit. This will add a match for
+          <literal>_SYSTEMD_INVOCATION_ID=</literal>, <literal>OBJECT_SYSTEMD_INVOCATION_ID=</literal>,
+          <literal>INVOCATION_ID=</literal>, <literal>USER_INVOCATION_ID=</literal>.</para>
+
+          <para>A positive <replaceable>offset</replaceable> will look up the invocations of a systemd unit
+          from the beginning of the journal, and zero or a negative offset will look up invocations starting
+          from the end of the journal. Thus, <constant>1</constant> means the first invocation found in the
+          journal in chronological order, <constant>2</constant> the second and so on; while
+          <constant>0</constant> is the latest invocation, <constant>-1</constant> the invocation before the
+          latest, and so on.</para>
+
+          <para>If the 32-character <replaceable>ID</replaceable> is specified, it may optionally be followed
+          by <replaceable>±offset</replaceable> which identifies the invocation relative to the one given by
+          invocation <replaceable>ID</replaceable>. Negative values mean earlier invocations and positive
+          values mean later invocations. If <replaceable>±offset</replaceable> is not specified, a value of
+          zero is assumed, and the logs for the invocation given by <replaceable>ID</replaceable> will be
+          shown.</para>
+
+          <para><option>-I</option> is equivalent to <option>--invocation=0</option>, and logs for the latest
+          invocation will be shown.</para>
+
+          <para>When an offset is specified, a unit name must be specified with <option>-u/--unit=</option>
+          or <option>--user-unit=</option> option.</para>
+
+          <para>When specified with <option>-b/--boot=</option>, then invocations are searched within the
+          specified boot.</para>
+
+          <xi:include href="version-info.xml" xpointer="v257"/>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>-t</option></term>
         <term><option>--identifier=<replaceable>SYSLOG_IDENTIFIER</replaceable></option></term>
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--list-invocations</option></term>
+
+        <listitem>
+          <para>List invocation IDs of a unit. Requires a unit name with <option>-u/--unit=</option> or
+          <option>--user-unit=</option>. Show a tabular list of invocation numbers (relative to the current
+          or latest invocation), their IDs, and the timestamps of the first and last message pertaining to
+          the invocation. When <option>-b/-boot</option> is specified, invocations in the boot will be shown.
+          When specified with <option>-n/--lines=<optional>+</optional><replaceable>N</replaceable></option>
+          option, only the first (when the number prefixed with <literal>+</literal>) or the last (without
+          prefix) <replaceable>N</replaceable> entries will be shown. When specified with
+          <option>-r/--reverse</option>, the list will be shown in the reverse order.</para>
+
+          <xi:include href="version-info.xml" xpointer="v257"/>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--disk-usage</option></term>
 
index c79a38c3527a975f343746ddd6b3345eb6e0c469..5d1cc174c928d6e66ecfc0e2db08ae4ff69124df 100644 (file)
@@ -47,11 +47,11 @@ _journalctl() {
                       --show-cursor --dmesg -k --pager-end -e -r --reverse
                       --utc -x --catalog --no-full --force --dump-catalog
                       --flush --rotate --sync --no-hostname -N --fields
-                      --list-namespaces'
+                      --list-namespaces --list-invocations -I'
         [ARG]='-b --boot -D --directory --file -F --field -t --identifier
                       -T --exclude-identifier --facility -M --machine -o --output
                       -u --unit --user-unit -p --priority --root --case-sensitive
-                      --namespace'
+                      --namespace --invocation'
         [ARGUNKNOWN]='-c --cursor --interval -n --lines -S --since -U --until
                       --after-cursor --cursor-file --verify-key -g --grep
                       --vacuum-size --vacuum-time --vacuum-files --output-fields'
index f9eb9f8c58a38e7e622bc228a0732b93a98aebd2..ab65a51451ea0f2d7ea27cf64396e7fa4ccd8eef 100644 (file)
 #include "path-util.h"
 #include "unit-name.h"
 
+static int add_invocation(sd_journal *j) {
+        int r;
+
+        assert(j);
+
+        if (!arg_invocation)
+                return 0;
+
+        assert(!sd_id128_is_null(arg_invocation_id));
+
+        r = add_matches_for_invocation_id(j, arg_invocation_id);
+        if (r < 0)
+                return r;
+
+        return sd_journal_add_conjunction(j);
+}
+
 static int add_boot(sd_journal *j) {
         int r;
 
@@ -429,27 +446,38 @@ int add_filters(sd_journal *j, char **matches) {
 
         assert(j);
 
-        /* First, search boot ID, as that may set and flush matches and seek journal. */
+        /* First, search boot or invocation ID, as that may set and flush matches and seek journal. */
         r = journal_acquire_boot(j);
         if (r < 0)
                 return r;
 
+        r = journal_acquire_invocation(j);
+        if (r < 0)
+                return r;
+
         /* Clear unexpected matches for safety. */
         sd_journal_flush_matches(j);
 
         /* Then, add filters in the below. */
-        r = add_boot(j);
-        if (r < 0)
-                return log_error_errno(r, "Failed to add filter for boot: %m");
+        if (arg_invocation) {
+                /* If an invocation ID is found, then it is not necessary to add matches for boot and units. */
+                r = add_invocation(j);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add filter for invocation: %m");
+        } else {
+                r = add_boot(j);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add filter for boot: %m");
+
+                r = add_units(j);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add filter for units: %m");
+        }
 
         r = add_dmesg(j);
         if (r < 0)
                 return log_error_errno(r, "Failed to add filter for dmesg: %m");
 
-        r = add_units(j);
-        if (r < 0)
-                return log_error_errno(r, "Failed to add filter for units: %m");
-
         r = add_syslog_identifier(j);
         if (r < 0)
                 return log_error_errno(r, "Failed to add filter for syslog identifiers: %m");
index ca0c9ec924900403203246974f250da78f46d516..01fb83807a9c1b3a22de93903ec8cb51a6216959 100644 (file)
@@ -236,6 +236,43 @@ int action_list_field_names(void) {
         return 0;
 }
 
+int action_list_invocations(void) {
+        _cleanup_(sd_journal_closep) sd_journal *j = NULL;
+        _cleanup_free_ LogId *ids = NULL;
+        size_t n_ids;
+        LogIdType type;
+        const char *unit;
+        int r;
+
+        assert(arg_action == ACTION_LIST_INVOCATIONS);
+
+        r = acquire_unit("--list-invocations", &unit, &type);
+        if (r < 0)
+                return r;
+
+        r = acquire_journal(&j);
+        if (r < 0)
+                return r;
+
+        r = journal_acquire_boot(j);
+        if (r < 0)
+                return r;
+
+        r = journal_get_log_ids(
+                        j, type,
+                        /* boot_id = */ arg_boot_id, /* unit = */ unit,
+                        /* advance_older = */ arg_lines_needs_seek_end(),
+                        /* max_ids = */ arg_lines >= 0 ? (size_t) arg_lines : SIZE_MAX,
+                        &ids, &n_ids);
+        if (r < 0)
+                return log_error_errno(r, "Failed to list invocation id for %s: %m", unit);
+        if (r == 0)
+                return log_full_errno(arg_quiet ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(ENODATA),
+                                      "No invocation ID for %s found.", unit);
+
+        return show_log_ids(ids, n_ids, "invocation id");
+}
+
 int action_list_namespaces(void) {
         _cleanup_(table_unrefp) Table *table = NULL;
         sd_id128_t machine;
index 70f851bc14176c9bdd778c25e0c5a42ccefee5cc..72036890a81f7fef04ae05304bd9fba0896e7f24 100644 (file)
@@ -9,4 +9,5 @@ int action_disk_usage(void);
 int action_list_boots(void);
 int action_list_fields(void);
 int action_list_field_names(void);
+int action_list_invocations(void);
 int action_list_namespaces(void);
index 5ff02baeb9df896961713a333f6e40d8461e13d0..b554f4d78a50dcebb560da364c89872d428e794d 100644 (file)
@@ -9,6 +9,7 @@
 #include "logs-show.h"
 #include "rlimit-util.h"
 #include "sigbus.h"
+#include "strv.h"
 #include "terminal-util.h"
 
 char* format_timestamp_maybe_utc(char *buf, size_t l, usec_t t) {
@@ -118,3 +119,78 @@ int journal_acquire_boot(sd_journal *j) {
 
         return 1;
 }
+
+int acquire_unit(const char *option_name, const char **ret_unit, LogIdType *ret_type) {
+        size_t n;
+
+        assert(option_name);
+        assert(ret_unit);
+        assert(ret_type);
+
+        n = strv_length(arg_system_units) + strv_length(arg_user_units);
+        if (n <= 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "Using %s requires a unit. Please specify a unit name with -u/--unit=/--user-unit=.",
+                                       option_name);
+        if (n > 1)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "Using %s with multiple units is not supported.",
+                                       option_name);
+
+        if (!strv_isempty(arg_system_units)) {
+                *ret_type = LOG_SYSTEM_UNIT_INVOCATION_ID;
+                *ret_unit = arg_system_units[0];
+        } else {
+                assert(!strv_isempty(arg_user_units));
+                *ret_type = LOG_USER_UNIT_INVOCATION_ID;
+                *ret_unit = arg_user_units[0];
+        }
+
+        return 0;
+}
+
+int journal_acquire_invocation(sd_journal *j) {
+        LogIdType type = LOG_SYSTEM_UNIT_INVOCATION_ID;
+        const char *unit = NULL;
+        sd_id128_t id;
+        int r;
+
+        assert(j);
+
+        /* journal_acquire_boot() must be called before this. */
+
+        if (!arg_invocation) {
+                /* Clear relevant field for safety. */
+                arg_invocation_id = SD_ID128_NULL;
+                arg_invocation_offset = 0;
+                return 0;
+        }
+
+        /* When an invocation ID is explicitly specified without an offset, we do not care the ID is about
+         * system unit or user unit, and calling without unit name is allowed. Otherwise, a unit name must
+         * be specified. */
+        if (arg_invocation_offset != 0 || sd_id128_is_null(arg_invocation_id)) {
+                r = acquire_unit("-I/--invocation= with an offset", &unit, &type);
+                if (r < 0)
+                        return r;
+        }
+
+        r = journal_find_log_id(j, type, arg_boot_id, unit, arg_invocation_id, arg_invocation_offset, &id);
+        if (r < 0)
+                return log_error_errno(r, "Failed to find journal entry for the invocation (%s%+i): %m",
+                                       sd_id128_is_null(arg_invocation_id) ? "" : SD_ID128_TO_STRING(arg_invocation_id),
+                                       arg_invocation_offset);
+        if (r == 0)
+                return log_error_errno(SYNTHETIC_ERRNO(ENODATA),
+                                       "No journal entry found for the invocation (%s%+i).",
+                                       sd_id128_is_null(arg_invocation_id) ? "" : SD_ID128_TO_STRING(arg_invocation_id),
+                                       arg_invocation_offset);
+
+        log_debug("Found invocation ID %s for %s%+i",
+                  SD_ID128_TO_STRING(id),
+                  sd_id128_is_null(arg_invocation_id) ? "" : SD_ID128_TO_STRING(arg_invocation_id),
+                  arg_invocation_offset);
+
+        arg_invocation_id = id;
+        return 1;
+}
index ea3e56838d3dbe33fd93bc625ef19d803c81aae9..14e3d569a6464c0d0f81c829407b166ffc4ba4a2 100644 (file)
@@ -3,9 +3,12 @@
 
 #include "sd-journal.h"
 
+#include "logs-show.h"
 #include "time-util.h"
 
 char* format_timestamp_maybe_utc(char *buf, size_t l, usec_t t);
 int acquire_journal(sd_journal **ret);
 bool journal_boot_has_effect(sd_journal *j);
 int journal_acquire_boot(sd_journal *j);
+int acquire_unit(const char *option_name, const char **ret_unit, LogIdType *ret_type);
+int journal_acquire_invocation(sd_journal *j);
index 369280a009a71d24fd06a4469281a7ea4dde89dd..8ed5d98675a22db471b16066139515e7cd709a36 100644 (file)
@@ -72,6 +72,9 @@ char **arg_syslog_identifier = NULL;
 char **arg_exclude_identifier = NULL;
 char **arg_system_units = NULL;
 char **arg_user_units = NULL;
+bool arg_invocation = false;
+sd_id128_t arg_invocation_id = SD_ID128_NULL;
+int arg_invocation_offset = 0;
 const char *arg_field = NULL;
 bool arg_catalog = false;
 bool arg_reverse = false;
@@ -227,6 +230,8 @@ static int help(void) {
                "  -b --boot[=ID]             Show current boot or the specified boot\n"
                "  -u --unit=UNIT             Show logs from the specified unit\n"
                "     --user-unit=UNIT        Show logs from the specified user unit\n"
+               "     --invocation=ID         Show logs from the matching invocation ID\n"
+               "  -I                         Show logs from the latest invocation of unit\n"
                "  -t --identifier=STRING     Show entries with the specified syslog identifier\n"
                "  -T --exclude-identifier=STRING\n"
                "                             Hide entries with the specified syslog identifier\n"
@@ -267,6 +272,7 @@ static int help(void) {
                "  -N --fields                List all field names currently used\n"
                "  -F --field=FIELD           List all values that a specified field takes\n"
                "     --list-boots            Show terse information about recorded boots\n"
+               "     --list-invocations      Show invocation IDs of specified unit\n"
                "     --list-namespaces       Show list of journal namespaces\n"
                "     --disk-usage            Show total disk usage of all journal files\n"
                "     --vacuum-size=BYTES     Reduce disk usage below specified size\n"
@@ -304,6 +310,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_NEW_ID128,
                 ARG_THIS_BOOT,
                 ARG_LIST_BOOTS,
+                ARG_LIST_INVOCATIONS,
                 ARG_USER,
                 ARG_SYSTEM,
                 ARG_ROOT,
@@ -320,6 +327,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_CURSOR_FILE,
                 ARG_SHOW_CURSOR,
                 ARG_USER_UNIT,
+                ARG_INVOCATION,
                 ARG_LIST_CATALOG,
                 ARG_DUMP_CATALOG,
                 ARG_UPDATE_CATALOG,
@@ -361,6 +369,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "this-boot",            no_argument,       NULL, ARG_THIS_BOOT            }, /* deprecated */
                 { "boot",                 optional_argument, NULL, 'b'                      },
                 { "list-boots",           no_argument,       NULL, ARG_LIST_BOOTS           },
+                { "list-invocations",     no_argument,       NULL, ARG_LIST_INVOCATIONS     },
                 { "dmesg",                no_argument,       NULL, 'k'                      },
                 { "system",               no_argument,       NULL, ARG_SYSTEM               },
                 { "user",                 no_argument,       NULL, ARG_USER                 },
@@ -389,6 +398,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "until",                required_argument, NULL, 'U'                      },
                 { "unit",                 required_argument, NULL, 'u'                      },
                 { "user-unit",            required_argument, NULL, ARG_USER_UNIT            },
+                { "invocation",           required_argument, NULL, ARG_INVOCATION           },
                 { "field",                required_argument, NULL, 'F'                      },
                 { "fields",               no_argument,       NULL, 'N'                      },
                 { "catalog",              no_argument,       NULL, 'x'                      },
@@ -418,7 +428,7 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
-        while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:g:c:S:U:t:T:u:NF:xrM:i:", options, NULL)) >= 0)
+        while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:g:c:S:U:t:T:u:INF:xrM:i:", options, NULL)) >= 0)
 
                 switch (c) {
 
@@ -540,6 +550,10 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_action = ACTION_LIST_BOOTS;
                         break;
 
+                case ARG_LIST_INVOCATIONS:
+                        arg_action = ACTION_LIST_INVOCATIONS;
+                        break;
+
                 case 'k':
                         arg_boot = arg_dmesg = true;
                         break;
@@ -831,6 +845,20 @@ static int parse_argv(int argc, char *argv[]) {
                                 return log_oom();
                         break;
 
+                case ARG_INVOCATION:
+                        r = parse_id_descriptor(optarg, &arg_invocation_id, &arg_invocation_offset);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse invocation descriptor: %s", optarg);
+                        arg_invocation = r;
+                        break;
+
+                case 'I':
+                        /* Equivalent to --invocation=0 */
+                        arg_invocation = true;
+                        arg_invocation_id = SD_ID128_NULL;
+                        arg_invocation_offset = 0;
+                        break;
+
                 case 'F':
                         arg_action = ACTION_LIST_FIELDS;
                         arg_field = optarg;
@@ -1074,6 +1102,9 @@ static int run(int argc, char *argv[]) {
         case ACTION_LIST_FIELD_NAMES:
                 return action_list_field_names();
 
+        case ACTION_LIST_INVOCATIONS:
+                return action_list_invocations();
+
         case ACTION_LIST_NAMESPACES:
                 return action_list_namespaces();
 
index 2d86c169176abdc7ac93ed7a546d1d0df9f87446..409cfe2bcaacc2a29322d8ec47118a9114a74a33 100644 (file)
@@ -26,6 +26,7 @@ typedef enum JournalctlAction {
         ACTION_LIST_BOOTS,
         ACTION_LIST_FIELDS,
         ACTION_LIST_FIELD_NAMES,
+        ACTION_LIST_INVOCATIONS,
         ACTION_LIST_NAMESPACES,
         ACTION_FLUSH,
         ACTION_RELINQUISH_VAR,
@@ -76,6 +77,9 @@ extern char **arg_syslog_identifier;
 extern char **arg_exclude_identifier;
 extern char **arg_system_units;
 extern char **arg_user_units;
+extern bool arg_invocation;
+extern sd_id128_t arg_invocation_id;
+extern int arg_invocation_offset;
 extern const char *arg_field;
 extern bool arg_catalog;
 extern bool arg_reverse;