Skip to content

Commit

Permalink
journalctl: add --list-invocations command and -I/--invocation options
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
yuwata committed Apr 26, 2024
1 parent 6c61e86 commit 21018a5
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 23 deletions.
53 changes: 53 additions & 0 deletions man/journalctl.xml
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,42 @@
<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="v256"/>
</listitem>
</varlistentry>

<varlistentry>
<term><option>-t</option></term>
<term><option>--identifier=<replaceable>SYSLOG_IDENTIFIER</replaceable></option></term>
Expand Down Expand Up @@ -863,6 +899,23 @@
</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="v256"/>
</listitem>
</varlistentry>

<varlistentry>
<term><option>--disk-usage</option></term>

Expand Down
4 changes: 2 additions & 2 deletions shell-completion/bash/journalctl
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
44 changes: 36 additions & 8 deletions src/journal/journalctl-filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,23 @@
#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;

Expand Down Expand Up @@ -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");
Expand Down
37 changes: 37 additions & 0 deletions src/journal/journalctl-misc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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_ JournalId *ids = NULL;
size_t n_ids;
JournalIdType 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_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_ids(ids, n_ids, "invocation id");
}

int action_list_namespaces(void) {
_cleanup_(table_unrefp) Table *table = NULL;
sd_id128_t machine;
Expand Down
1 change: 1 addition & 0 deletions src/journal/journalctl-misc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
74 changes: 74 additions & 0 deletions src/journal/journalctl-util.c
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,77 @@ int journal_acquire_boot(sd_journal *j) {

return 1;
}

int acquire_unit(const char *option_name, const char **ret_unit, JournalIdType *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 = JOURNAL_SYSTEM_UNIT_INVOCATION_ID;
*ret_unit = arg_system_units[0];
} else {
assert(!strv_isempty(arg_user_units));
*ret_type = JOURNAL_USER_UNIT_INVOCATION_ID;
*ret_unit = arg_user_units[0];
}

return 0;
}

int journal_acquire_invocation(sd_journal *j) {
JournalIdType type = JOURNAL_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. */
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_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 from 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;
}
3 changes: 3 additions & 0 deletions src/journal/journalctl-util.h
Original file line number Diff line number Diff line change
Expand Up @@ -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, JournalIdType *ret_type);
int journal_acquire_invocation(sd_journal *j);

0 comments on commit 21018a5

Please sign in to comment.