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);