From cc25a67e2af21833f9fef5aba6e16beafa03b0c7 Mon Sep 17 00:00:00 2001 From: Lars Karlitski Date: Fri, 27 Oct 2017 05:10:47 +0200 Subject: [PATCH] journalctl: add --output-fields= (#7181) This option allows restricting the shown fields in the output modes that would normally show all fields. It allows clients that are only interested in a subset of the fields to access those more efficiently. Also, it makes the resulting size of the output more predictable. It has no effect on the various `short` output modes, because those already only show a subset of the fields. --- man/journalctl.xml | 14 ++++ src/journal-remote/journal-gatewayd.c | 2 +- src/journal/journalctl.c | 24 ++++++- src/shared/logs-show.c | 93 ++++++++++++++++++++------- src/shared/logs-show.h | 1 + test/TEST-04-JOURNAL/test-journal.sh | 12 ++++ 6 files changed, 122 insertions(+), 24 deletions(-) diff --git a/man/journalctl.xml b/man/journalctl.xml index 444b916073..c80e0ed1c2 100644 --- a/man/journalctl.xml +++ b/man/journalctl.xml @@ -384,6 +384,20 @@ + + + + A comma separated list of the fields which should + be included in the output. This only has an effect for the output modes + which would normally show all fields (, + , , + , and ). The + __CURSOR, __REALTIME_TIMESTAMP, + __MONOTONIC_TIMESTAMP, and + _BOOT_ID fields are always + printed. + + diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index a0f1f3e926..fec16f7c7c 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -225,7 +225,7 @@ static ssize_t request_reader_entries( return MHD_CONTENT_READER_END_WITH_ERROR; } - r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL); + r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL, NULL); 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 e826fa631e..88c1ac6dd0 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -123,6 +123,7 @@ static const char *arg_machine = NULL; static uint64_t arg_vacuum_size = 0; static uint64_t arg_vacuum_n_files = 0; static usec_t arg_vacuum_time = 0; +static char **arg_output_fields = NULL; static enum { ACTION_SHOW, @@ -377,6 +378,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_VACUUM_FILES, ARG_VACUUM_TIME, ARG_NO_HOSTNAME, + ARG_OUTPUT_FIELDS, }; static const struct option options[] = { @@ -435,6 +437,7 @@ static int parse_argv(int argc, char *argv[]) { { "vacuum-files", required_argument, NULL, ARG_VACUUM_FILES }, { "vacuum-time", required_argument, NULL, ARG_VACUUM_TIME }, { "no-hostname", no_argument, NULL, ARG_NO_HOSTNAME }, + { "output-fields", required_argument, NULL, ARG_OUTPUT_FIELDS }, {} }; @@ -842,6 +845,24 @@ static int parse_argv(int argc, char *argv[]) { arg_action = ACTION_SYNC; break; + case ARG_OUTPUT_FIELDS: { + _cleanup_strv_free_ char **v = NULL; + + v = strv_split(optarg, ","); + if (!v) + return log_oom(); + + if (!arg_output_fields) { + arg_output_fields = v; + v = NULL; + } else { + r = strv_extend_strv(&arg_output_fields, v, true); + if (r < 0) + return log_oom(); + } + break; + } + case '?': return -EINVAL; @@ -2452,7 +2473,7 @@ int main(int argc, char *argv[]) { arg_utc * OUTPUT_UTC | arg_no_hostname * OUTPUT_NO_HOSTNAME; - r = output_journal(stdout, j, arg_output, 0, flags, &ellipsized); + r = output_journal(stdout, j, arg_output, 0, flags, arg_output_fields, &ellipsized); need_seek = true; if (r == -EADDRNOTAVAIL) break; @@ -2498,6 +2519,7 @@ finish: strv_free(arg_syslog_identifier); strv_free(arg_system_units); strv_free(arg_user_units); + strv_free(arg_output_fields); free(arg_root); free(arg_verify_key); diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index a6b6859582..5e0d59f5a2 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -48,6 +48,7 @@ #include "stdio-util.h" #include "string-table.h" #include "string-util.h" +#include "strv.h" #include "terminal-util.h" #include "time-util.h" #include "utf8.h" @@ -136,6 +137,19 @@ static int parse_fieldv(const void *data, size_t length, const ParseFieldVec *fi return 0; } +static int field_set_test(Set *fields, const char *name, size_t n) { + char *s = NULL; + + if (!fields) + return 1; + + s = strndupa(name, n); + if (!s) + return log_oom(); + + return set_get(fields, s) ? 1 : 0; +} + static bool shall_print(const char *p, size_t l, OutputFlags flags) { assert(p); @@ -353,7 +367,8 @@ static int output_short( sd_journal *j, OutputMode mode, unsigned n_columns, - OutputFlags flags) { + OutputFlags flags, + Set *output_fields) { int r; const void *data; @@ -466,7 +481,8 @@ static int output_verbose( sd_journal *j, OutputMode mode, unsigned n_columns, - OutputFlags flags) { + OutputFlags flags, + Set *output_fields) { const void *data; size_t length; @@ -527,6 +543,12 @@ static int output_verbose( } fieldlen = c - (const char*) data; + r = field_set_test(output_fields, data, fieldlen); + if (r < 0) + return r; + if (!r) + continue; + if (flags & OUTPUT_COLOR && startswith(data, "MESSAGE=")) { on = ANSI_HIGHLIGHT; off = ANSI_NORMAL; @@ -564,7 +586,8 @@ static int output_export( sd_journal *j, OutputMode mode, unsigned n_columns, - OutputFlags flags) { + OutputFlags flags, + Set *output_fields) { sd_id128_t boot_id; char sid[33]; @@ -601,6 +624,7 @@ static int output_export( sd_id128_to_string(boot_id, sid)); JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { + const char *c; /* We already printed the boot id, from the data in * the header, hence let's suppress it here */ @@ -608,18 +632,23 @@ static int output_export( startswith(data, "_BOOT_ID=")) continue; + c = memchr(data, '=', length); + if (!c) { + log_error("Invalid field."); + return -EINVAL; + } + + r = field_set_test(output_fields, data, c - (const char *) data); + if (r < 0) + return r; + if (!r) + continue; + if (utf8_is_printable_newline(data, length, false)) fwrite(data, length, 1, f); else { - const char *c; uint64_t le64; - c = memchr(data, '=', length); - if (!c) { - log_error("Invalid field."); - return -EINVAL; - } - fwrite(data, c - (const char*) data, 1, f); fputc('\n', f); le64 = htole64(length - (c - (const char*) data) - 1); @@ -695,7 +724,8 @@ static int output_json( sd_journal *j, OutputMode mode, unsigned n_columns, - OutputFlags flags) { + OutputFlags flags, + Set *output_fields) { uint64_t realtime, monotonic; _cleanup_free_ char *cursor = NULL; @@ -814,13 +844,6 @@ static int output_json( if (!eq) continue; - if (separator) { - if (mode == OUTPUT_JSON_PRETTY) - fputs(",\n\t", f); - else - fputs(", ", f); - } - m = eq - (const char*) data; n = strndup(data, m); @@ -829,6 +852,18 @@ static int output_json( goto finish; } + if (output_fields && !set_get(output_fields, n)) { + free(n); + continue; + } + + if (separator) { + if (mode == OUTPUT_JSON_PRETTY) + fputs(",\n\t", f); + else + fputs(", ", f); + } + u = PTR_TO_UINT(hashmap_get2(h, n, (void**) &kk)); if (u == 0) { /* We already printed this, let's jump to the next */ @@ -913,7 +948,8 @@ static int output_cat( sd_journal *j, OutputMode mode, unsigned n_columns, - OutputFlags flags) { + OutputFlags flags, + Set *output_fields) { const void *data; size_t l; @@ -946,7 +982,8 @@ static int (*output_funcs[_OUTPUT_MODE_MAX])( sd_journal*j, OutputMode mode, unsigned n_columns, - OutputFlags flags) = { + OutputFlags flags, + Set *output_fields) = { [OUTPUT_SHORT] = output_short, [OUTPUT_SHORT_ISO] = output_short, @@ -969,16 +1006,28 @@ int output_journal( OutputMode mode, unsigned n_columns, OutputFlags flags, + char **output_fields, bool *ellipsized) { int ret; + _cleanup_set_free_free_ Set *fields = NULL; assert(mode >= 0); assert(mode < _OUTPUT_MODE_MAX); if (n_columns <= 0) n_columns = columns(); - ret = output_funcs[mode](f, j, mode, n_columns, flags); + if (output_fields) { + fields = set_new(&string_hash_ops); + if (!fields) + return log_oom(); + + ret = set_put_strdupv(fields, output_fields); + if (ret < 0) + return ret; + } + + ret = output_funcs[mode](f, j, mode, n_columns, flags, fields); if (ellipsized && ret > 0) *ellipsized = true; @@ -1060,7 +1109,7 @@ static int show_journal(FILE *f, line++; maybe_print_begin_newline(f, &flags); - r = output_journal(f, j, mode, n_columns, flags, ellipsized); + r = output_journal(f, j, mode, n_columns, flags, NULL, ellipsized); if (r < 0) return r; } diff --git a/src/shared/logs-show.h b/src/shared/logs-show.h index 6643440881..3d583b79ef 100644 --- a/src/shared/logs-show.h +++ b/src/shared/logs-show.h @@ -37,6 +37,7 @@ int output_journal( OutputMode mode, unsigned n_columns, OutputFlags flags, + char **output_fields, bool *ellipsized); int add_match_this_boot(sd_journal *j, const char *machine); diff --git a/test/TEST-04-JOURNAL/test-journal.sh b/test/TEST-04-JOURNAL/test-journal.sh index 493ff00ce0..260cae09ab 100755 --- a/test/TEST-04-JOURNAL/test-journal.sh +++ b/test/TEST-04-JOURNAL/test-journal.sh @@ -51,6 +51,18 @@ journalctl --sync journalctl -b -o cat -t "$ID" >/output cmp /expected /output +# --output-fields restricts output +ID=$(journalctl --new-id128 | sed -n 2p) +printf $'foo' | systemd-cat -t "$ID" --level-prefix false +journalctl --sync +journalctl -b -o export --output-fields=MESSAGE,FOO --output-fields=PRIORITY,MESSAGE -t "$ID" >/output +[[ `grep -c . /output` -eq 6 ]] +grep -q '^__CURSOR=' /output +grep -q '^MESSAGE=foo$' /output +grep -q '^PRIORITY=6$' /output +! grep -q '^FOO=' /output +! grep -q '^SYSLOG_FACILITY=' /output + # Don't lose streams on restart systemctl start forever-print-hola sleep 3