mirror of
https://github.com/systemd/systemd.git
synced 2024-12-22 17:35:35 +03:00
journalctl: add --list-invocations command and -I/--invocation options
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.
This commit is contained in:
parent
82721be7d6
commit
7d3ae6b213
@ -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="v257"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>-t</option></term>
|
||||
<term><option>--identifier=<replaceable>SYSLOG_IDENTIFIER</replaceable></option></term>
|
||||
@ -862,6 +898,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="v257"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--disk-usage</option></term>
|
||||
|
||||
|
@ -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'
|
||||
|
@ -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;
|
||||
|
||||
@ -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");
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user