diff options
-rw-r--r-- | NEWS | 4 | ||||
-rw-r--r-- | man/journalctl.xml | 7 | ||||
-rw-r--r-- | shell-completion/zsh/_sd_outputmodes | 2 | ||||
-rw-r--r-- | src/journal-remote/journal-gatewayd.c | 4 | ||||
-rw-r--r-- | src/journal/journalctl.c | 6 | ||||
-rw-r--r-- | src/login/loginctl.c | 4 | ||||
-rw-r--r-- | src/machine/machinectl.c | 4 | ||||
-rw-r--r-- | src/shared/logs-show.c | 291 | ||||
-rw-r--r-- | src/shared/logs-show.h | 4 | ||||
-rw-r--r-- | src/shared/output-mode.c | 1 | ||||
-rw-r--r-- | src/shared/output-mode.h | 1 | ||||
-rw-r--r-- | src/systemctl/systemctl.c | 2 |
12 files changed, 225 insertions, 105 deletions
@@ -165,6 +165,10 @@ CHANGES WITH 252 in spe: * openssl is the default crypto backend for systemd-resolved. (gnutls is still supported.) + * journalctl -o (and similar commands) now understands a new output mode + "short-delta". It is similar to "short-monotonic" but also shows the + time delta between two messages. + Experimental features: * BPF programs can now be compiled with bpf-gcc. diff --git a/man/journalctl.xml b/man/journalctl.xml index fb7da5446e..75427bc632 100644 --- a/man/journalctl.xml +++ b/man/journalctl.xml @@ -420,6 +420,13 @@ </varlistentry> <varlistentry> + <term><option>short-delta</option></term> + <listitem><para>as for <option>short-monotonic</option> but includes the time difference + to the previous entry. + Maybe unreliable time differences are marked by a <literal>*</literal>.</para></listitem> + </varlistentry> + + <varlistentry> <term><option>short-unix</option></term> <listitem><para>is very similar, but shows seconds passed since January 1st 1970 UTC instead of wallclock timestamps ("UNIX time"). The time is shown with microsecond accuracy.</para></listitem> diff --git a/shell-completion/zsh/_sd_outputmodes b/shell-completion/zsh/_sd_outputmodes index 267a2e7bd3..68b11871f2 100644 --- a/shell-completion/zsh/_sd_outputmodes +++ b/shell-completion/zsh/_sd_outputmodes @@ -2,5 +2,5 @@ # SPDX-License-Identifier: LGPL-2.1-or-later local -a _output_opts -_output_opts=(short short-full short-iso short-iso-precise short-precise short-monotonic short-unix verbose export json json-pretty json-sse json-seq cat with-unit) +_output_opts=(short short-full short-iso short-iso-precise short-precise short-monotonic short-unix short-delta verbose export json json-pretty json-sse json-seq cat with-unit) _describe -t output 'output mode' _output_opts || compadd "$@" diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index cdfeb1fc80..3e2a85ce29 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -148,6 +148,8 @@ static ssize_t request_reader_entries( size_t max) { RequestMeta *m = ASSERT_PTR(cls); + dual_timestamp previous_ts = DUAL_TIMESTAMP_NULL; + sd_id128_t previous_boot_id = SD_ID128_NULL; int r; size_t n, k; @@ -222,7 +224,7 @@ static ssize_t request_reader_entries( } r = show_journal_entry(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, - NULL, NULL, NULL); + NULL, NULL, NULL, &previous_ts, &previous_boot_id); if (r < 0) { log_error_errno(r, "Failed to serialize item: %m"); return MHD_CONTENT_READER_END_WITH_ERROR; diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index f3d3e2eca3..f0d28fd48b 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -2099,7 +2099,8 @@ int main(int argc, char *argv[]) { bool previous_boot_id_valid = false, first_line = true, ellipsized = false, need_seek = false; bool use_cursor = false, after_cursor = false; _cleanup_(sd_journal_closep) sd_journal *j = NULL; - sd_id128_t previous_boot_id = {}; /* Unnecessary initialization to appease gcc */ + sd_id128_t previous_boot_id = SD_ID128_NULL, previous_boot_id_output = SD_ID128_NULL; + dual_timestamp previous_ts_output = DUAL_TIMESTAMP_NULL; int n_shown = 0, r, poll_fd = -1; setlocale(LC_ALL, ""); @@ -2673,7 +2674,8 @@ int main(int argc, char *argv[]) { arg_no_hostname * OUTPUT_NO_HOSTNAME; r = show_journal_entry(stdout, j, arg_output, 0, flags, - arg_output_fields, highlight, &ellipsized); + arg_output_fields, highlight, &ellipsized, + &previous_ts_output, &previous_boot_id_output); need_seek = true; if (r == -EADDRNOTAVAIL) break; diff --git a/src/login/loginctl.c b/src/login/loginctl.c index ed24473baa..4dbfa0db44 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -1267,9 +1267,9 @@ static int help(int argc, char *argv[], void *userdata) { " -n --lines=INTEGER Number of journal entries to show\n" " -o --output=STRING Change journal output mode (short, short-precise,\n" " short-iso, short-iso-precise, short-full,\n" - " short-monotonic, short-unix, verbose, export,\n" + " short-monotonic, short-unix, short-delta,\n" " json, json-pretty, json-sse, json-seq, cat,\n" - " with-unit)\n" + " verbose, export, with-unit)\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 39e6f18606..db29e3092b 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -2462,9 +2462,9 @@ static int help(int argc, char *argv[], void *userdata) { " --max-addresses=INTEGER Number of internet addresses to show at most\n" " -o --output=STRING Change journal output mode (short, short-precise,\n" " short-iso, short-iso-precise, short-full,\n" - " short-monotonic, short-unix, verbose, export,\n" + " short-monotonic, short-unix, short-delta,\n" " json, json-pretty, json-sse, json-seq, cat,\n" - " with-unit)\n" + " verbose, export, with-unit)\n" " --verify=MODE Verification mode for downloaded images (no,\n" " checksum, signature)\n" " --force Download image even if already exists\n" diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index 98219e14fa..0ebc66597d 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -317,62 +317,87 @@ static bool print_multiline( return ellipsized; } -static int output_timestamp_monotonic(FILE *f, sd_journal *j, const char *monotonic) { - sd_id128_t boot_id; - uint64_t t; - int r; +static int output_timestamp_monotonic( + FILE *f, OutputMode mode, + const dual_timestamp *ts, + const sd_id128_t *boot_id, + const dual_timestamp *previous_ts, + const sd_id128_t *previous_boot_id) { + + int written_chars = 0; assert(f); - assert(j); + assert(ts); + assert(boot_id); + assert(previous_ts); + assert(previous_boot_id); - r = -ENXIO; - if (monotonic) - r = safe_atou64(monotonic, &t); - if (r < 0) - r = sd_journal_get_monotonic_usec(j, &t, &boot_id); - if (r < 0) - return log_error_errno(r, "Failed to get monotonic timestamp: %m"); + if (!VALID_MONOTONIC(ts->monotonic)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid monotonic timestamp available"); + + written_chars += fprintf(f, "[%5"PRI_USEC".%06"PRI_USEC, ts->monotonic / USEC_PER_SEC, ts->monotonic % USEC_PER_SEC); + + if (mode == OUTPUT_SHORT_DELTA) { + uint64_t delta; + bool reliable_ts = true; + + if (VALID_MONOTONIC(previous_ts->monotonic) && sd_id128_equal(*boot_id, *previous_boot_id)) + delta = usec_sub_unsigned(ts->monotonic, previous_ts->monotonic); + else if (VALID_REALTIME(ts->realtime) && VALID_REALTIME(previous_ts->realtime)) { + delta = usec_sub_unsigned(ts->realtime, previous_ts->realtime); + reliable_ts = false; + } else { + written_chars += fprintf(f, "%16s", ""); + goto finish; + } + + written_chars += fprintf(f, " <%5"PRI_USEC".%06"PRI_USEC"%s>", delta / USEC_PER_SEC, delta % USEC_PER_SEC, reliable_ts ? " " : "*"); + } + +finish: + written_chars += fprintf(f, "%s", "]"); - fprintf(f, "[%5"PRI_USEC".%06"PRI_USEC"]", t / USEC_PER_SEC, t % USEC_PER_SEC); - return 1 + 5 + 1 + 6 + 1; + return written_chars; } -static int output_timestamp_realtime(FILE *f, sd_journal *j, OutputMode mode, OutputFlags flags, const char *realtime) { +static int output_timestamp_realtime( + FILE *f, + sd_journal *j, + OutputMode mode, + OutputFlags flags, + const dual_timestamp *ts) { + char buf[CONST_MAX(FORMAT_TIMESTAMP_MAX, 64U)]; - uint64_t x; int r; assert(f); assert(j); + assert(ts); - if (realtime) - r = safe_atou64(realtime, &x); - if (!realtime || r < 0 || !VALID_REALTIME(x)) - r = sd_journal_get_realtime_usec(j, &x); - if (r < 0) - return log_error_errno(r, "Failed to get realtime timestamp: %m"); + if (!VALID_REALTIME(ts->realtime)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid realtime timestamp available"); if (IN_SET(mode, OUTPUT_SHORT_FULL, OUTPUT_WITH_UNIT)) { const char *k; if (flags & OUTPUT_UTC) - k = format_timestamp_style(buf, sizeof(buf), x, TIMESTAMP_UTC); + k = format_timestamp_style(buf, sizeof(buf), ts->realtime, TIMESTAMP_UTC); else - k = format_timestamp(buf, sizeof(buf), x); + k = format_timestamp(buf, sizeof(buf), ts->realtime); if (!k) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to format timestamp: %" PRIu64, x); + "Failed to format timestamp: %" PRIu64, ts->realtime); } else { struct tm tm; time_t t; - t = (time_t) (x / USEC_PER_SEC); + t = (time_t) (ts->realtime / USEC_PER_SEC); switch (mode) { case OUTPUT_SHORT_UNIX: - xsprintf(buf, "%10"PRI_TIME".%06"PRIu64, t, x % USEC_PER_SEC); + xsprintf(buf, "%10"PRI_TIME".%06"PRIu64, t, ts->realtime % USEC_PER_SEC); break; case OUTPUT_SHORT_ISO: @@ -390,7 +415,7 @@ static int output_timestamp_realtime(FILE *f, sd_journal *j, OutputMode mode, Ou localtime_or_gmtime_r(&t, &tm, flags & OUTPUT_UTC)) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to format ISO-precise time"); - xsprintf(usec, "%06"PRI_USEC, x % USEC_PER_SEC); + xsprintf(usec, "%06"PRI_USEC, ts->realtime % USEC_PER_SEC); memcpy(buf + 20, usec, 6); break; } @@ -408,7 +433,7 @@ static int output_timestamp_realtime(FILE *f, sd_journal *j, OutputMode mode, Ou assert(sizeof(buf) > strlen(buf)); k = sizeof(buf) - strlen(buf); - r = snprintf(buf + strlen(buf), k, ".%06"PRIu64, x % USEC_PER_SEC); + r = snprintf(buf + strlen(buf), k, ".%06"PRIu64, ts->realtime % USEC_PER_SEC); if (r <= 0 || (size_t) r >= k) /* too long? */ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to format precise time"); @@ -431,16 +456,20 @@ static int output_short( unsigned n_columns, OutputFlags flags, const Set *output_fields, - const size_t highlight[2]) { + const size_t highlight[2], + const dual_timestamp *ts, + const sd_id128_t *boot_id, + const dual_timestamp *previous_ts, + const sd_id128_t *previous_boot_id) { int r; const void *data; size_t length, n = 0; _cleanup_free_ char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL, - *message = NULL, *realtime = NULL, *monotonic = NULL, *priority = NULL, *transport = NULL, + *message = NULL, *priority = NULL, *transport = NULL, *config_file = NULL, *unit = NULL, *user_unit = NULL, *documentation_url = NULL; size_t hostname_len = 0, identifier_len = 0, comm_len = 0, pid_len = 0, fake_pid_len = 0, message_len = 0, - realtime_len = 0, monotonic_len = 0, priority_len = 0, transport_len = 0, config_file_len = 0, + priority_len = 0, transport_len = 0, config_file_len = 0, unit_len = 0, user_unit_len = 0, documentation_url_len = 0; int p = LOG_INFO; bool ellipsized = false, audit; @@ -453,8 +482,6 @@ static int output_short( PARSE_FIELD_VEC_ENTRY("_HOSTNAME=", &hostname, &hostname_len), PARSE_FIELD_VEC_ENTRY("SYSLOG_PID=", &fake_pid, &fake_pid_len), PARSE_FIELD_VEC_ENTRY("SYSLOG_IDENTIFIER=", &identifier, &identifier_len), - PARSE_FIELD_VEC_ENTRY("_SOURCE_REALTIME_TIMESTAMP=", &realtime, &realtime_len), - PARSE_FIELD_VEC_ENTRY("_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, &monotonic_len), PARSE_FIELD_VEC_ENTRY("CONFIG_FILE=", &config_file, &config_file_len), PARSE_FIELD_VEC_ENTRY("_SYSTEMD_UNIT=", &unit, &unit_len), PARSE_FIELD_VEC_ENTRY("_SYSTEMD_USER_UNIT=", &user_unit, &user_unit_len), @@ -464,6 +491,10 @@ static int output_short( assert(f); assert(j); + assert(ts); + assert(boot_id); + assert(previous_ts); + assert(previous_boot_id); /* Set the threshold to one bigger than the actual print * threshold, so that if the line is actually longer than what @@ -498,10 +529,10 @@ static int output_short( audit = streq_ptr(transport, "audit"); - if (mode == OUTPUT_SHORT_MONOTONIC) - r = output_timestamp_monotonic(f, j, monotonic); + if (IN_SET(mode, OUTPUT_SHORT_MONOTONIC, OUTPUT_SHORT_DELTA)) + r = output_timestamp_monotonic(f, mode, ts, boot_id, previous_ts, previous_boot_id); else - r = output_timestamp_realtime(f, j, mode, flags, realtime); + r = output_timestamp_realtime(f, j, mode, flags, ts); if (r < 0) return r; n += r; @@ -632,52 +663,36 @@ static int output_verbose( unsigned n_columns, OutputFlags flags, const Set *output_fields, - const size_t highlight[2]) { + const size_t highlight[2], + const dual_timestamp *ts, + const sd_id128_t *boot_id, + const dual_timestamp *previous_ts, + const sd_id128_t *previous_boot_id) { const void *data; size_t length; _cleanup_free_ char *cursor = NULL; - uint64_t realtime = 0; - char ts[FORMAT_TIMESTAMP_MAX + 7]; + char buf[FORMAT_TIMESTAMP_MAX + 7]; const char *timestamp; int r; assert(f); assert(j); + assert(ts); + assert(boot_id); + assert(previous_ts); + assert(previous_boot_id); sd_journal_set_data_threshold(j, 0); - r = sd_journal_get_data(j, "_SOURCE_REALTIME_TIMESTAMP", &data, &length); - if (r == -ENOENT) - log_debug("Source realtime timestamp not found"); - else if (r < 0) - return log_full_errno(r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_ERR, r, "Failed to get source realtime timestamp: %m"); - else { - _cleanup_free_ char *value = NULL; - - r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", - STRLEN("_SOURCE_REALTIME_TIMESTAMP="), &value, - NULL); - if (r < 0) - return r; - assert(r > 0); - - r = safe_atou64(value, &realtime); - if (r < 0) - log_debug_errno(r, "Failed to parse realtime timestamp: %m"); - } - - if (r < 0) { - r = sd_journal_get_realtime_usec(j, &realtime); - if (r < 0) - return log_full_errno(r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_ERR, r, "Failed to get realtime timestamp: %m"); - } + if (!VALID_REALTIME(ts->realtime)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid realtime timestamp available"); r = sd_journal_get_cursor(j, &cursor); if (r < 0) return log_error_errno(r, "Failed to get cursor: %m"); - timestamp = format_timestamp_style(ts, sizeof ts, realtime, + timestamp = format_timestamp_style(buf, sizeof buf, ts->realtime, flags & OUTPUT_UTC ? TIMESTAMP_US_UTC : TIMESTAMP_US); fprintf(f, "%s [%s]\n", timestamp ?: "(no timestamp)", @@ -750,26 +765,30 @@ static int output_export( unsigned n_columns, OutputFlags flags, const Set *output_fields, - const size_t highlight[2]) { + const size_t highlight[2], + const dual_timestamp *ts, + const sd_id128_t *boot_id, + const dual_timestamp *previous_ts, + const sd_id128_t *previous_boot_id) { _cleanup_free_ char *cursor = NULL; - usec_t realtime, monotonic; - sd_id128_t boot_id; const void *data; size_t length; int r; assert(j); + assert(ts); + assert(boot_id); + assert(previous_ts); + assert(previous_boot_id); sd_journal_set_data_threshold(j, 0); - r = sd_journal_get_realtime_usec(j, &realtime); - if (r < 0) - return log_error_errno(r, "Failed to get realtime timestamp: %m"); + if (!VALID_REALTIME(ts->realtime)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid realtime timestamp available"); - r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id); - if (r < 0) - return log_error_errno(r, "Failed to get monotonic timestamp: %m"); + if (!VALID_MONOTONIC(ts->monotonic)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid monotonic timestamp available"); r = sd_journal_get_cursor(j, &cursor); if (r < 0) @@ -781,9 +800,9 @@ static int output_export( "__MONOTONIC_TIMESTAMP="USEC_FMT"\n" "_BOOT_ID=%s\n", cursor, - realtime, - monotonic, - SD_ID128_TO_STRING(boot_id)); + ts->realtime, + ts->monotonic, + SD_ID128_TO_STRING(*boot_id)); JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { size_t fieldlen; @@ -985,30 +1004,34 @@ static int output_json( unsigned n_columns, OutputFlags flags, const Set *output_fields, - const size_t highlight[2]) { + const size_t highlight[2], + const dual_timestamp *ts, + const sd_id128_t *boot_id, + const dual_timestamp *previous_ts, + const sd_id128_t *previous_boot_id) { char sid[SD_ID128_STRING_MAX], usecbuf[DECIMAL_STR_MAX(usec_t)]; _cleanup_(json_variant_unrefp) JsonVariant *object = NULL; _cleanup_free_ char *cursor = NULL; - uint64_t realtime, monotonic; JsonVariant **array = NULL; struct json_data *d; - sd_id128_t boot_id; Hashmap *h = NULL; size_t n = 0; int r; assert(j); + assert(ts); + assert(boot_id); + assert(previous_ts); + assert(previous_boot_id); (void) sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : JSON_THRESHOLD); - r = sd_journal_get_realtime_usec(j, &realtime); - if (r < 0) - return log_error_errno(r, "Failed to get realtime timestamp: %m"); + if (!VALID_REALTIME(ts->realtime)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid realtime timestamp available"); - r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id); - if (r < 0) - return log_error_errno(r, "Failed to get monotonic timestamp: %m"); + if (!VALID_MONOTONIC(ts->monotonic)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid monotonic timestamp available"); r = sd_journal_get_cursor(j, &cursor); if (r < 0) @@ -1022,17 +1045,17 @@ static int output_json( if (r < 0) goto finish; - xsprintf(usecbuf, USEC_FMT, realtime); + xsprintf(usecbuf, USEC_FMT, ts->realtime); r = update_json_data(h, flags, "__REALTIME_TIMESTAMP", usecbuf, strlen(usecbuf)); if (r < 0) goto finish; - xsprintf(usecbuf, USEC_FMT, monotonic); + xsprintf(usecbuf, USEC_FMT, ts->monotonic); r = update_json_data(h, flags, "__MONOTONIC_TIMESTAMP", usecbuf, strlen(usecbuf)); if (r < 0) goto finish; - sd_id128_to_string(boot_id, sid); + sd_id128_to_string(*boot_id, sid); r = update_json_data(h, flags, "_BOOT_ID", sid, strlen(sid)); if (r < 0) goto finish; @@ -1181,13 +1204,21 @@ static int output_cat( unsigned n_columns, OutputFlags flags, const Set *output_fields, - const size_t highlight[2]) { + const size_t highlight[2], + const dual_timestamp *ts, + const sd_id128_t *boot_id, + const dual_timestamp *previous_ts, + const sd_id128_t *previous_boot_id) { int r, prio = LOG_INFO; const char *field; assert(j); assert(f); + assert(ts); + assert(boot_id); + assert(previous_ts); + assert(previous_boot_id); (void) sd_journal_set_data_threshold(j, 0); @@ -1227,6 +1258,50 @@ static int output_cat( return 0; } +static int get_dual_timestamp(sd_journal *j, dual_timestamp *ret_ts, sd_id128_t *ret_boot_id) { + const void *data; + _cleanup_free_ char *realtime = NULL, *monotonic = NULL; + size_t length = 0, realtime_len = 0, monotonic_len = 0; + const ParseFieldVec message_fields[] = { + PARSE_FIELD_VEC_ENTRY("_SOURCE_REALTIME_TIMESTAMP=", &realtime, &realtime_len), + PARSE_FIELD_VEC_ENTRY("_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, &monotonic_len), + }; + int r; + + assert(j); + assert(ret_ts); + assert(ret_boot_id); + + JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { + r = parse_fieldv(data, length, message_fields, ELEMENTSOF(message_fields)); + if (r < 0) + return r; + } + if (r < 0) + return r; + + if (realtime) + r = safe_atou64(realtime, &ret_ts->realtime); + if (!realtime || r < 0 || !VALID_REALTIME(ret_ts->realtime)) + r = sd_journal_get_realtime_usec(j, &ret_ts->realtime); + if (r < 0) + ret_ts->realtime = USEC_INFINITY; + + if (monotonic) + r = safe_atou64(monotonic, &ret_ts->monotonic); + if (!monotonic || r < 0 || !VALID_MONOTONIC(ret_ts->monotonic)) + r = sd_journal_get_monotonic_usec(j, &ret_ts->monotonic, ret_boot_id); + if (r < 0) + ret_ts->monotonic = USEC_INFINITY; + + /* Restart all data before */ + sd_journal_restart_data(j); + sd_journal_restart_unique(j); + sd_journal_restart_fields(j); + + return 0; +} + static int (*output_funcs[_OUTPUT_MODE_MAX])( FILE *f, sd_journal *j, @@ -1234,13 +1309,18 @@ static int (*output_funcs[_OUTPUT_MODE_MAX])( unsigned n_columns, OutputFlags flags, const Set *output_fields, - const size_t highlight[2]) = { + const size_t highlight[2], + const dual_timestamp *ts, + const sd_id128_t *boot_id, + const dual_timestamp *previous_ts, + const sd_id128_t *previous_boot_id) = { [OUTPUT_SHORT] = output_short, [OUTPUT_SHORT_ISO] = output_short, [OUTPUT_SHORT_ISO_PRECISE] = output_short, [OUTPUT_SHORT_PRECISE] = output_short, [OUTPUT_SHORT_MONOTONIC] = output_short, + [OUTPUT_SHORT_DELTA] = output_short, [OUTPUT_SHORT_UNIX] = output_short, [OUTPUT_SHORT_FULL] = output_short, [OUTPUT_VERBOSE] = output_verbose, @@ -1261,13 +1341,19 @@ int show_journal_entry( OutputFlags flags, char **output_fields, const size_t highlight[2], - bool *ellipsized) { + bool *ellipsized, + dual_timestamp *previous_ts, + sd_id128_t *previous_boot_id) { _cleanup_set_free_ Set *fields = NULL; + dual_timestamp ts = DUAL_TIMESTAMP_NULL; + sd_id128_t boot_id = SD_ID128_NULL; int r; assert(mode >= 0); assert(mode < _OUTPUT_MODE_MAX); + assert(previous_ts); + assert(previous_boot_id); if (n_columns <= 0) n_columns = columns(); @@ -1276,7 +1362,19 @@ int show_journal_entry( if (r < 0) return r; - r = output_funcs[mode](f, j, mode, n_columns, flags, fields, highlight); + r = get_dual_timestamp(j, &ts, &boot_id); + if (r == -EBADMSG) { + log_debug_errno(r, "Skipping message we can't read: %m"); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to get journal fields: %m"); + + r = output_funcs[mode](f, j, mode, n_columns, flags, fields, highlight, &ts, &boot_id, previous_ts, previous_boot_id); + + /* Store timestamp and boot ID for next iteration */ + *previous_ts = ts; + *previous_boot_id = boot_id; if (ellipsized && r > 0) *ellipsized = true; @@ -1313,6 +1411,8 @@ int show_journal( unsigned line = 0; bool need_seek = false; int warn_cutoff = flags & OUTPUT_WARN_CUTOFF; + dual_timestamp previous_ts = DUAL_TIMESTAMP_NULL; + sd_id128_t previous_boot_id = SD_ID128_NULL; assert(j); assert(mode >= 0); @@ -1361,7 +1461,8 @@ int show_journal( line++; maybe_print_begin_newline(f, &flags); - r = show_journal_entry(f, j, mode, n_columns, flags, NULL, NULL, ellipsized); + r = show_journal_entry(f, j, mode, n_columns, flags, NULL, NULL, ellipsized, + &previous_ts, &previous_boot_id); if (r < 0) return r; } diff --git a/src/shared/logs-show.h b/src/shared/logs-show.h index 71ebe13573..71e39ebb1f 100644 --- a/src/shared/logs-show.h +++ b/src/shared/logs-show.h @@ -21,7 +21,9 @@ int show_journal_entry( OutputFlags flags, char **output_fields, const size_t highlight[2], - bool *ellipsized); + bool *ellipsized, + dual_timestamp *previous_ts, + sd_id128_t *previous_boot_id); int show_journal( FILE *f, sd_journal *j, diff --git a/src/shared/output-mode.c b/src/shared/output-mode.c index 1645b756df..026bf19a59 100644 --- a/src/shared/output-mode.c +++ b/src/shared/output-mode.c @@ -28,6 +28,7 @@ static const char *const output_mode_table[_OUTPUT_MODE_MAX] = { [OUTPUT_SHORT_ISO_PRECISE] = "short-iso-precise", [OUTPUT_SHORT_PRECISE] = "short-precise", [OUTPUT_SHORT_MONOTONIC] = "short-monotonic", + [OUTPUT_SHORT_DELTA] = "short-delta", [OUTPUT_SHORT_UNIX] = "short-unix", [OUTPUT_VERBOSE] = "verbose", [OUTPUT_EXPORT] = "export", diff --git a/src/shared/output-mode.h b/src/shared/output-mode.h index 4b4abfe75f..26351c9fdb 100644 --- a/src/shared/output-mode.h +++ b/src/shared/output-mode.h @@ -11,6 +11,7 @@ typedef enum OutputMode { OUTPUT_SHORT_ISO_PRECISE, OUTPUT_SHORT_PRECISE, OUTPUT_SHORT_MONOTONIC, + OUTPUT_SHORT_DELTA, OUTPUT_SHORT_UNIX, OUTPUT_VERBOSE, OUTPUT_EXPORT, diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index ae046e73a3..56d26c43a0 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -291,7 +291,7 @@ static int systemctl_help(void) { " -n --lines=INTEGER Number of journal entries to show\n" " -o --output=STRING Change journal output mode (short, short-precise,\n" " short-iso, short-iso-precise, short-full,\n" - " short-monotonic, short-unix,\n" + " short-monotonic, short-unix, short-delta,\n" " verbose, export, json, json-pretty, json-sse, cat)\n" " --firmware-setup Tell the firmware to show the setup menu on next boot\n" " --boot-loader-menu=TIME\n" |