diff --git a/man/journalctl.xml b/man/journalctl.xml index 257ff5a816..37fb0d67fd 100644 --- a/man/journalctl.xml +++ b/man/journalctl.xml @@ -578,6 +578,29 @@ priorities. + + + + + Filter output to entries where the MESSAGE= + field matches the specified regular expression. PERL-compatible regular expressions + are used, see + pcre2pattern3 + for a detailed description of the syntax. + + If the pattern is all lowercase, matching is case insensitive. + Otherwise, matching is case sensitive. This can be overridden with the + option, see below. + + + + + + + Make pattern matching case sensitive or case insenstive. + + + diff --git a/meson.build b/meson.build index 4805ac4949..36a62d280d 100644 --- a/meson.build +++ b/meson.build @@ -1104,6 +1104,17 @@ else endif conf.set10('HAVE_XKBCOMMON', have) +want_pcre2 = get_option('pcre2') +if want_pcre2 != 'false' + libpcre2 = dependency('libpcre2-8', + required : want_pcre2 == 'true') + have = libpcre2.found() +else + have = false + libpcre2 = [] +endif +conf.set10('HAVE_PCRE2', have) + want_glib = get_option('glib') if want_glib != 'false' and not fuzzer_build libglib = dependency('glib-2.0', @@ -1465,7 +1476,8 @@ exe = executable('journalctl', dependencies : [threads, libqrencode, libxz, - liblz4], + liblz4, + libpcre2], install_rpath : rootlibexecdir, install : true, install_dir : rootbindir) @@ -2817,6 +2829,7 @@ foreach tuple : [ ['gnu-efi', have_gnu_efi], ['kmod'], ['xkbcommon'], + ['pcre2'], ['blkid'], ['dbus'], ['glib'], diff --git a/meson_options.txt b/meson_options.txt index eda5a579f2..39822d6cdc 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -260,6 +260,8 @@ option('lz4', type : 'combo', choices : ['auto', 'true', 'false'], description : 'lz4 compression support') option('xkbcommon', type : 'combo', choices : ['auto', 'true', 'false'], description : 'xkbcommon keymap support') +option('pcre2', type : 'combo', choices : ['auto', 'true', 'false'], + description : 'regexp matching support using pcre2') option('glib', type : 'combo', choices : ['auto', 'true', 'false'], description : 'libglib support (for tests only)') option('dbus', type : 'combo', choices : ['auto', 'true', 'false'], diff --git a/src/basic/build.h b/src/basic/build.h index ab2a838ce9..0d078efe6f 100644 --- a/src/basic/build.h +++ b/src/basic/build.h @@ -140,6 +140,12 @@ #define _IDN_FEATURE_ "-IDN" #endif +#if HAVE_PCRE2 +#define _PCRE2_FEATURE_ "+PCRE2" +#else +#define _PCRE2_FEATURE_ "-PCRE2" +#endif + #define _CGROUP_HIEARCHY_ "default-hierarchy=" DEFAULT_HIERARCHY_NAME #define SYSTEMD_FEATURES \ @@ -163,4 +169,5 @@ _KMOD_FEATURE_ " " \ _IDN2_FEATURE_ " " \ _IDN_FEATURE_ " " \ + _PCRE2_FEATURE_ " " \ _CGROUP_HIEARCHY_ diff --git a/src/basic/string-util.c b/src/basic/string-util.c index 7e2f596edc..9f2c01d864 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -30,6 +30,7 @@ #include "gunicode.h" #include "macro.h" #include "string-util.h" +#include "terminal-util.h" #include "utf8.h" #include "util.h" @@ -648,7 +649,17 @@ char *strreplace(const char *text, const char *old_string, const char *new_strin return ret; } -char *strip_tab_ansi(char **ibuf, size_t *_isz) { +static void advance_offsets(ssize_t diff, size_t offsets[2], size_t shift[2], size_t size) { + if (!offsets) + return; + + if ((size_t) diff < offsets[0]) + shift[0] += size; + if ((size_t) diff < offsets[1]) + shift[1] += size; +} + +char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { const char *i, *begin = NULL; enum { STATE_OTHER, @@ -656,7 +667,7 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) { STATE_BRACKET } state = STATE_OTHER; char *obuf = NULL; - size_t osz = 0, isz; + size_t osz = 0, isz, shift[2] = {}; FILE *f; assert(ibuf); @@ -684,15 +695,18 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) { break; else if (*i == '\x1B') state = STATE_ESCAPE; - else if (*i == '\t') + else if (*i == '\t') { fputs(" ", f); - else + advance_offsets(i - *ibuf, highlight, shift, 7); + } else fputc(*i, f); + break; case STATE_ESCAPE: if (i >= *ibuf + isz) { /* EOT */ fputc('\x1B', f); + advance_offsets(i - *ibuf, highlight, shift, 1); break; } else if (*i == '[') { state = STATE_BRACKET; @@ -700,6 +714,7 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) { } else { fputc('\x1B', f); fputc(*i, f); + advance_offsets(i - *ibuf, highlight, shift, 1); state = STATE_OTHER; } @@ -711,6 +726,7 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) { (!(*i >= '0' && *i <= '9') && !IN_SET(*i, ';', 'm'))) { fputc('\x1B', f); fputc('[', f); + advance_offsets(i - *ibuf, highlight, shift, 2); state = STATE_OTHER; i = begin-1; } else if (*i == 'm') @@ -732,6 +748,11 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) { if (_isz) *_isz = osz; + if (highlight) { + highlight[0] += shift[0]; + highlight[1] += shift[1]; + } + return obuf; } diff --git a/src/basic/string-util.h b/src/basic/string-util.h index d8f97e697b..08eda4fce0 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -177,7 +177,7 @@ char* strshorten(char *s, size_t l); char *strreplace(const char *text, const char *old_string, const char *new_string); -char *strip_tab_ansi(char **p, size_t *l); +char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]); char *strextend_with_separator(char **x, const char *separator, ...) _sentinel_; diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index 82c70cfbe3..5a437ce031 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -226,7 +226,8 @@ 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, NULL); + r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, + NULL, 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 7078f11b0d..17782688d9 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -34,6 +34,11 @@ #include #include +#if HAVE_PCRE2 +# define PCRE2_CODE_UNIT_WIDTH 8 +# include +#endif + #include "sd-bus.h" #include "sd-journal.h" @@ -76,6 +81,34 @@ #define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE) +#if HAVE_PCRE2 +DEFINE_TRIVIAL_CLEANUP_FUNC(pcre2_match_data*, pcre2_match_data_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(pcre2_code*, pcre2_code_free); + +static int pattern_compile(const char *pattern, unsigned flags, pcre2_code **out) { + int errorcode, r; + PCRE2_SIZE erroroffset; + pcre2_code *p; + + p = pcre2_compile((PCRE2_SPTR8) pattern, + PCRE2_ZERO_TERMINATED, flags, &errorcode, &erroroffset, NULL); + if (!p) { + unsigned char buf[LINE_MAX]; + + r = pcre2_get_error_message(errorcode, buf, sizeof buf); + + log_error("Bad pattern \"%s\": %s", + pattern, + r < 0 ? "unknown error" : (char*) buf); + return -EINVAL; + } + + *out = p; + return 0; +} + +#endif + enum { /* Special values for arg_lines */ ARG_LINES_DEFAULT = -2, @@ -126,6 +159,12 @@ static uint64_t arg_vacuum_n_files = 0; static usec_t arg_vacuum_time = 0; static char **arg_output_fields = NULL; +#if HAVE_PCRE2 +static const char *arg_pattern = NULL; +static pcre2_code *arg_compiled_pattern = NULL; +static int arg_case_sensitive = -1; /* -1 means be smart */ +#endif + static enum { ACTION_SHOW, ACTION_NEW_ID128, @@ -280,67 +319,69 @@ static void help(void) { printf("%s [OPTIONS...] [MATCHES...]\n\n" "Query the journal.\n\n" "Options:\n" - " --system Show the system journal\n" - " --user Show the user journal for the current user\n" - " -M --machine=CONTAINER Operate on local container\n" - " -S --since=DATE Show entries not older than the specified date\n" - " -U --until=DATE Show entries not newer than the specified date\n" - " -c --cursor=CURSOR Show entries starting at the specified cursor\n" - " --after-cursor=CURSOR Show entries after the specified cursor\n" - " --show-cursor Print the cursor after all the entries\n" - " -b --boot[=ID] Show current boot or the specified boot\n" - " --list-boots Show terse information about recorded boots\n" - " -k --dmesg Show kernel message log from the current boot\n" - " -u --unit=UNIT Show logs from the specified unit\n" - " --user-unit=UNIT Show logs from the specified user unit\n" - " -t --identifier=STRING Show entries with the specified syslog identifier\n" - " -p --priority=RANGE Show entries with the specified priority\n" - " -e --pager-end Immediately jump to the end in the pager\n" - " -f --follow Follow the journal\n" - " -n --lines[=INTEGER] Number of journal entries to show\n" - " --no-tail Show all lines, even in follow mode\n" - " -r --reverse Show the newest entries first\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" - " json, json-pretty, json-sse, cat)\n" - " --output-fields=LIST Select fields to print in verbose/export/json modes\n" - " --utc Express time in Coordinated Universal Time (UTC)\n" - " -x --catalog Add message explanations where available\n" - " --no-full Ellipsize fields\n" - " -a --all Show all fields, including long and unprintable\n" - " -q --quiet Do not show info messages and privilege warning\n" - " --no-pager Do not pipe output into a pager\n" - " --no-hostname Suppress output of hostname field\n" - " -m --merge Show entries from all available journals\n" - " -D --directory=PATH Show journal files from directory\n" - " --file=PATH Show journal file\n" - " --root=ROOT Operate on files below a root directory\n" + " --system Show the system journal\n" + " --user Show the user journal for the current user\n" + " -M --machine=CONTAINER Operate on local container\n" + " -S --since=DATE Show entries not older than the specified date\n" + " -U --until=DATE Show entries not newer than the specified date\n" + " -c --cursor=CURSOR Show entries starting at the specified cursor\n" + " --after-cursor=CURSOR Show entries after the specified cursor\n" + " --show-cursor Print the cursor after all the entries\n" + " -b --boot[=ID] Show current boot or the specified boot\n" + " --list-boots Show terse information about recorded boots\n" + " -k --dmesg Show kernel message log from the current boot\n" + " -u --unit=UNIT Show logs from the specified unit\n" + " --user-unit=UNIT Show logs from the specified user unit\n" + " -t --identifier=STRING Show entries with the specified syslog identifier\n" + " -p --priority=RANGE Show entries with the specified priority\n" + " -g --grep=PATTERN Show entries with MESSSAGE matching PATTERN\n" + " --case-sensitive[=BOOL] Force case sensitive or insenstive matching\n" + " -e --pager-end Immediately jump to the end in the pager\n" + " -f --follow Follow the journal\n" + " -n --lines[=INTEGER] Number of journal entries to show\n" + " --no-tail Show all lines, even in follow mode\n" + " -r --reverse Show the newest entries first\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" + " json, json-pretty, json-sse, cat)\n" + " --output-fields=LIST Select fields to print in verbose/export/json modes\n" + " --utc Express time in Coordinated Universal Time (UTC)\n" + " -x --catalog Add message explanations where available\n" + " --no-full Ellipsize fields\n" + " -a --all Show all fields, including long and unprintable\n" + " -q --quiet Do not show info messages and privilege warning\n" + " --no-pager Do not pipe output into a pager\n" + " --no-hostname Suppress output of hostname field\n" + " -m --merge Show entries from all available journals\n" + " -D --directory=PATH Show journal files from directory\n" + " --file=PATH Show journal file\n" + " --root=ROOT Operate on files below a root directory\n" #if HAVE_GCRYPT - " --interval=TIME Time interval for changing the FSS sealing key\n" - " --verify-key=KEY Specify FSS verification key\n" - " --force Override of the FSS key pair with --setup-keys\n" + " --interval=TIME Time interval for changing the FSS sealing key\n" + " --verify-key=KEY Specify FSS verification key\n" + " --force Override of the FSS key pair with --setup-keys\n" #endif "\nCommands:\n" - " -h --help Show this help text\n" - " --version Show package version\n" - " -N --fields List all field names currently used\n" - " -F --field=FIELD List all values that a specified field takes\n" - " --disk-usage Show total disk usage of all journal files\n" - " --vacuum-size=BYTES Reduce disk usage below specified size\n" - " --vacuum-files=INT Leave only the specified number of journal files\n" - " --vacuum-time=TIME Remove journal files older than specified time\n" - " --verify Verify journal file consistency\n" - " --sync Synchronize unwritten journal messages to disk\n" - " --flush Flush all journal data from /run into /var\n" - " --rotate Request immediate rotation of the journal files\n" - " --header Show journal header information\n" - " --list-catalog Show all message IDs in the catalog\n" - " --dump-catalog Show entries in the message catalog\n" - " --update-catalog Update the message catalog database\n" - " --new-id128 Generate a new 128-bit ID\n" + " -h --help Show this help text\n" + " --version Show package version\n" + " -N --fields List all field names currently used\n" + " -F --field=FIELD List all values that a specified field takes\n" + " --disk-usage Show total disk usage of all journal files\n" + " --vacuum-size=BYTES Reduce disk usage below specified size\n" + " --vacuum-files=INT Leave only the specified number of journal files\n" + " --vacuum-time=TIME Remove journal files older than specified time\n" + " --verify Verify journal file consistency\n" + " --sync Synchronize unwritten journal messages to disk\n" + " --flush Flush all journal data from /run into /var\n" + " --rotate Request immediate rotation of the journal files\n" + " --header Show journal header information\n" + " --list-catalog Show all message IDs in the catalog\n" + " --dump-catalog Show entries in the message catalog\n" + " --update-catalog Update the message catalog database\n" + " --new-id128 Generate a new 128-bit ID\n" #if HAVE_GCRYPT - " --setup-keys Generate a new FSS key pair\n" + " --setup-keys Generate a new FSS key pair\n" #endif , program_invocation_short_name); } @@ -372,6 +413,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_DUMP_CATALOG, ARG_UPDATE_CATALOG, ARG_FORCE, + ARG_CASE_SENSITIVE, ARG_UTC, ARG_SYNC, ARG_FLUSH, @@ -411,6 +453,8 @@ static int parse_argv(int argc, char *argv[]) { { "header", no_argument, NULL, ARG_HEADER }, { "identifier", required_argument, NULL, 't' }, { "priority", required_argument, NULL, 'p' }, + { "grep", required_argument, NULL, 'g' }, + { "case-sensitive", optional_argument, NULL, ARG_CASE_SENSITIVE }, { "setup-keys", no_argument, NULL, ARG_SETUP_KEYS }, { "interval", required_argument, NULL, ARG_INTERVAL }, { "verify", no_argument, NULL, ARG_VERIFY }, @@ -762,6 +806,27 @@ static int parse_argv(int argc, char *argv[]) { break; } +#if HAVE_PCRE2 + case 'g': + arg_pattern = optarg; + break; + + case ARG_CASE_SENSITIVE: + if (optarg) { + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Bad --case-sensitive= argument \"%s\": %m", optarg); + arg_case_sensitive = r; + } else + arg_case_sensitive = true; + + break; +#else + case 'g': + case ARG_CASE_SENSITIVE: + return log_error("Compiled without pattern matching support"); +#endif + case 'S': r = parse_timestamp(optarg, &arg_since); if (r < 0) { @@ -917,6 +982,42 @@ static int parse_argv(int argc, char *argv[]) { arg_system_units = strv_free(arg_system_units); } + +#if HAVE_PCRE2 + if (arg_pattern) { + unsigned flags; + + if (arg_case_sensitive >= 0) + flags = !arg_case_sensitive * PCRE2_CASELESS; + else { + _cleanup_(pcre2_match_data_freep) pcre2_match_data *md = NULL; + bool has_case; + _cleanup_(pcre2_code_freep) pcre2_code *cs = NULL; + + md = pcre2_match_data_create(1, NULL); + if (!md) + return log_oom(); + + r = pattern_compile("[[:upper:]]", 0, &cs); + if (r < 0) + return r; + + r = pcre2_match(cs, (PCRE2_SPTR8) arg_pattern, PCRE2_ZERO_TERMINATED, 0, 0, md, NULL); + has_case = r >= 0; + + flags = !has_case * PCRE2_CASELESS; + } + + log_debug("Doing case %s matching based on %s", + flags & PCRE2_CASELESS ? "insensitive" : "sensitive", + arg_case_sensitive >= 0 ? "request" : "pattern casing"); + + r = pattern_compile(arg_pattern, flags, &arg_compiled_pattern); + if (r < 0) + return r; + } +#endif + return 1; } @@ -2415,6 +2516,7 @@ int main(int argc, char *argv[]) { for (;;) { while (arg_lines < 0 || n_shown < arg_lines || (arg_follow && !first_line)) { int flags; + size_t highlight[2] = {}; if (need_seek) { if (!arg_reverse) @@ -2468,6 +2570,58 @@ int main(int argc, char *argv[]) { } } +#if HAVE_PCRE2 + if (arg_compiled_pattern) { + _cleanup_(pcre2_match_data_freep) pcre2_match_data *md = NULL; + const void *message; + size_t len; + PCRE2_SIZE *ovec; + + md = pcre2_match_data_create(1, NULL); + if (!md) + return log_oom(); + + r = sd_journal_get_data(j, "MESSAGE", &message, &len); + if (r < 0) { + if (r == -ENOENT) { + need_seek = true; + continue; + } + + log_error_errno(r, "Failed to get MESSAGE field: %m"); + goto finish; + } + + assert_se(message = startswith(message, "MESSAGE=")); + + r = pcre2_match(arg_compiled_pattern, + message, + len - strlen("MESSAGE="), + 0, /* start at offset 0 in the subject */ + 0, /* default options */ + md, + NULL); + if (r == PCRE2_ERROR_NOMATCH) { + need_seek = true; + continue; + } + if (r < 0) { + unsigned char buf[LINE_MAX]; + int r2; + + r2 = pcre2_get_error_message(r, buf, sizeof buf); + log_error("Pattern matching failed: %s", + r2 < 0 ? "unknown error" : (char*) buf); + r = -EINVAL; + goto finish; + } + + ovec = pcre2_get_ovector_pointer(md); + highlight[0] = ovec[0]; + highlight[1] = ovec[1]; + } +#endif + flags = arg_all * OUTPUT_SHOW_ALL | arg_full * OUTPUT_FULL_WIDTH | @@ -2476,7 +2630,8 @@ 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, arg_output_fields, &ellipsized); + r = output_journal(stdout, j, arg_output, 0, flags, + arg_output_fields, highlight, &ellipsized); need_seek = true; if (r == -EADDRNOTAVAIL) break; @@ -2527,5 +2682,10 @@ finish: free(arg_root); free(arg_verify_key); +#if HAVE_PCRE2 + if (arg_compiled_pattern) + pcre2_code_free(arg_compiled_pattern); +#endif + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index 8a1b26629a..5609e42feb 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -166,8 +166,17 @@ static bool shall_print(const char *p, size_t l, OutputFlags flags) { return true; } -static bool print_multiline(FILE *f, unsigned prefix, unsigned n_columns, OutputFlags flags, int priority, const char* message, size_t message_len) { - const char *color_on = "", *color_off = ""; +static bool print_multiline( + FILE *f, + unsigned prefix, + unsigned n_columns, + OutputFlags flags, + int priority, + const char* message, + size_t message_len, + size_t highlight[2]) { + + const char *color_on = "", *color_off = "", *highlight_on = ""; const char *pos, *end; bool ellipsized = false; int line = 0; @@ -176,9 +185,11 @@ static bool print_multiline(FILE *f, unsigned prefix, unsigned n_columns, Output if (priority <= LOG_ERR) { color_on = ANSI_HIGHLIGHT_RED; color_off = ANSI_NORMAL; + highlight_on = ANSI_HIGHLIGHT; } else if (priority <= LOG_NOTICE) { color_on = ANSI_HIGHLIGHT; color_off = ANSI_NORMAL; + highlight_on = ANSI_HIGHLIGHT_RED; } } @@ -209,9 +220,28 @@ static bool print_multiline(FILE *f, unsigned prefix, unsigned n_columns, Output if (flags & (OUTPUT_FULL_WIDTH | OUTPUT_SHOW_ALL) || (prefix + len + 1 < n_columns && !tail_line)) { - fprintf(f, "%*s%s%.*s%s\n", - continuation * prefix, "", - color_on, len, pos, color_off); + if (highlight && + (size_t) (pos - message) <= highlight[0] && + highlight[0] < (size_t) len) { + + fprintf(f, "%*s%s%.*s", + continuation * prefix, "", + color_on, (int) highlight[0], pos); + fprintf(f, "%s%.*s", + highlight_on, + (int) (MIN((size_t) len, highlight[1]) - highlight[0]), + pos + highlight[0]); + if ((size_t) len > highlight[1]) + fprintf(f, "%s%.*s", + color_on, + (int) (len - highlight[1]), + pos + highlight[1]); + fprintf(f, "%s\n", color_off); + + } else + fprintf(f, "%*s%s%.*s%s\n", + continuation * prefix, "", + color_on, len, pos, color_off); continue; } @@ -369,7 +399,8 @@ static int output_short( OutputMode mode, unsigned n_columns, OutputFlags flags, - Set *output_fields) { + Set *output_fields, + size_t highlight[2]) { int r; const void *data; @@ -390,6 +421,7 @@ static int output_short( PARSE_FIELD_VEC_ENTRY("_SOURCE_REALTIME_TIMESTAMP=", &realtime, &realtime_len), PARSE_FIELD_VEC_ENTRY("_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, &monotonic_len), }; + size_t highlight_shifted[] = {highlight ? highlight[0] : 0, highlight ? highlight[1] : 0}; assert(f); assert(j); @@ -421,7 +453,7 @@ static int output_short( } if (!(flags & OUTPUT_SHOW_ALL)) - strip_tab_ansi(&message, &message_len); + strip_tab_ansi(&message, &message_len, highlight_shifted); if (priority_len == 1 && *priority >= '0' && *priority <= '7') p = *priority - '0'; @@ -468,7 +500,9 @@ static int output_short( } else { fputs(": ", f); ellipsized |= - print_multiline(f, n + 2, n_columns, flags, p, message, message_len); + print_multiline(f, n + 2, n_columns, flags, p, + message, message_len, + highlight_shifted); } if (flags & OUTPUT_CATALOG) @@ -483,7 +517,8 @@ static int output_verbose( OutputMode mode, unsigned n_columns, OutputFlags flags, - Set *output_fields) { + Set *output_fields, + size_t highlight[2]) { const void *data; size_t length; @@ -561,7 +596,7 @@ static int output_verbose( (((length < PRINT_CHAR_THRESHOLD) || flags & OUTPUT_FULL_WIDTH) && utf8_is_printable(data, length))) { fprintf(f, " %s%.*s=", on, fieldlen, (const char*)data); - print_multiline(f, 4 + fieldlen + 1, 0, OUTPUT_FULL_WIDTH, 0, c + 1, length - fieldlen - 1); + print_multiline(f, 4 + fieldlen + 1, 0, OUTPUT_FULL_WIDTH, 0, c + 1, length - fieldlen - 1, NULL); fputs(off, f); } else { char bytes[FORMAT_BYTES_MAX]; @@ -590,7 +625,8 @@ static int output_export( OutputMode mode, unsigned n_columns, OutputFlags flags, - Set *output_fields) { + Set *output_fields, + size_t highlight[2]) { sd_id128_t boot_id; char sid[33]; @@ -732,7 +768,8 @@ static int output_json( OutputMode mode, unsigned n_columns, OutputFlags flags, - Set *output_fields) { + Set *output_fields, + size_t highlight[2]) { uint64_t realtime, monotonic; _cleanup_free_ char *cursor = NULL; @@ -961,15 +998,22 @@ static int output_cat( OutputMode mode, unsigned n_columns, OutputFlags flags, - Set *output_fields) { + Set *output_fields, + size_t highlight[2]) { const void *data; size_t l; int r; + const char *highlight_on = "", *highlight_off = ""; assert(j); assert(f); + if (flags & OUTPUT_COLOR) { + highlight_on = ANSI_HIGHLIGHT_RED; + highlight_off = ANSI_NORMAL; + } + sd_journal_set_data_threshold(j, 0); r = sd_journal_get_data(j, "MESSAGE", &data, &l); @@ -987,7 +1031,17 @@ static int output_cat( assert(l >= 8); - fwrite((const char*) data + 8, 1, l - 8, f); + if (highlight && (flags & OUTPUT_COLOR)) { + assert(highlight[0] <= highlight[1]); + assert(highlight[1] <= l - 8); + + fwrite((const char*) data + 8, 1, highlight[0], f); + fwrite(highlight_on, 1, strlen(highlight_on), f); + fwrite((const char*) data + 8 + highlight[0], 1, highlight[1] - highlight[0], f); + fwrite(highlight_off, 1, strlen(highlight_off), f); + fwrite((const char*) data + 8 + highlight[1], 1, l - 8 - highlight[1], f); + } else + fwrite((const char*) data + 8, 1, l - 8, f); fputc('\n', f); return 0; @@ -999,7 +1053,8 @@ static int (*output_funcs[_OUTPUT_MODE_MAX])( OutputMode mode, unsigned n_columns, OutputFlags flags, - Set *output_fields) = { + Set *output_fields, + size_t highlight[2]) = { [OUTPUT_SHORT] = output_short, [OUTPUT_SHORT_ISO] = output_short, @@ -1023,6 +1078,7 @@ int output_journal( unsigned n_columns, OutputFlags flags, char **output_fields, + size_t highlight[2], bool *ellipsized) { int ret; @@ -1043,7 +1099,7 @@ int output_journal( return ret; } - ret = output_funcs[mode](f, j, mode, n_columns, flags, fields); + ret = output_funcs[mode](f, j, mode, n_columns, flags, fields, highlight); if (ellipsized && ret > 0) *ellipsized = true; @@ -1125,7 +1181,7 @@ static int show_journal(FILE *f, line++; maybe_print_begin_newline(f, &flags); - r = output_journal(f, j, mode, n_columns, flags, NULL, ellipsized); + r = output_journal(f, j, mode, n_columns, flags, NULL, NULL, ellipsized); if (r < 0) return r; } diff --git a/src/shared/logs-show.h b/src/shared/logs-show.h index eaa69b6e90..c876dcc46a 100644 --- a/src/shared/logs-show.h +++ b/src/shared/logs-show.h @@ -39,6 +39,7 @@ int output_journal( unsigned n_columns, OutputFlags flags, char **output_fields, + size_t highlight[2], bool *ellipsized); int add_match_this_boot(sd_journal *j, const char *machine); diff --git a/src/test/meson.build b/src/test/meson.build index e2a604d286..1db8aa107d 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -515,7 +515,6 @@ tests += [ [], '', 'manual'], - [['src/test/test-cgroup-mask.c', 'src/test/test-helper.c'], [libcore, diff --git a/src/test/test-strip-tab-ansi.c b/src/test/test-strip-tab-ansi.c index aabb7c40e7..838a6e4db6 100644 --- a/src/test/test-strip-tab-ansi.c +++ b/src/test/test-strip-tab-ansi.c @@ -28,24 +28,24 @@ int main(int argc, char *argv[]) { char *p; assert_se(p = strdup("\tFoobar\tbar\twaldo\t")); - assert_se(strip_tab_ansi(&p, NULL)); + assert_se(strip_tab_ansi(&p, NULL, NULL)); fprintf(stdout, "<%s>\n", p); assert_se(streq(p, " Foobar bar waldo ")); free(p); assert_se(p = strdup(ANSI_HIGHLIGHT "Hello" ANSI_NORMAL ANSI_HIGHLIGHT_RED " world!" ANSI_NORMAL)); - assert_se(strip_tab_ansi(&p, NULL)); + assert_se(strip_tab_ansi(&p, NULL, NULL)); fprintf(stdout, "<%s>\n", p); assert_se(streq(p, "Hello world!")); free(p); assert_se(p = strdup("\x1B[\x1B[\t\x1B[" ANSI_HIGHLIGHT "\x1B[" "Hello" ANSI_NORMAL ANSI_HIGHLIGHT_RED " world!" ANSI_NORMAL)); - assert_se(strip_tab_ansi(&p, NULL)); + assert_se(strip_tab_ansi(&p, NULL, NULL)); assert_se(streq(p, "\x1B[\x1B[ \x1B[\x1B[Hello world!")); free(p); assert_se(p = strdup("\x1B[waldo")); - assert_se(strip_tab_ansi(&p, NULL)); + assert_se(strip_tab_ansi(&p, NULL, NULL)); assert_se(streq(p, "\x1B[waldo")); free(p);