[systemd-devel] [PATCH] journalctl: allow customizable output formats

Zbigniew Jędrzejewski-Szmek zbyszek at in.waw.pl
Thu Oct 2 17:13:51 PDT 2014


On Mon, Sep 22, 2014 at 04:33:28PM +0100, Daniel P. Berrange wrote:
> The current '--output FORMAT' argument defines a number of
> common output formats, but there are some useful cases it
> does cover. In particular when reading application logs it
> is often desirable to display the code file name, line number
> and function name. Rather than defining yet more fixed output
> formats, this patch introduces user defined output formats.
Hi,

I think this makes sense. But I think that the format strings you
propose are damn ugly :). Using %() for variables seems too heavy.
Also, journal fields are all text, so I don't think that specifying
the type is useful.

Maybe we could adopt the {} format from Java and Python, as
implemented in Python [1]. It has a fairly rich and consistent field
formatting language. We would care only about the part relevant
to strings, at least in the beginning.

[1] https://docs.python.org/3.5/library/string.html#formatspec

Zbyszek

> The format string is an arbitrary string which contains a
> mixture of literal text and variable subsistitions. Each
> variable name corresponds to a journal field name. A variable
> name can be optionally followed by a data type, and in the
> case of string types, a length limit.
> 
> This is best illustrated with an example:
> 
>   $ journalctl -o "format:%(__REALTIME_TIMESTAMP) \
>         [%(CODE_FILE):%(CODE_LINE):%(CODE_FUNC)] \
>         %(MESSAGE:string:80)\n"  _COMM=libvirtd
>   -- Logs begin at Mon 2013-12-23 16:31:41 GMT, end at Mon 2014-09-22 16:13:00 BST. --
>   Dec 23 17:19:25 [util/virlog.c:877:virLogVMessage] libvirt version: 1.1.3.1, package: 2.fc20 (Fedora Project, 2013-11-17-23:28:43, ...
>   Dec 23 17:19:25 [conf/storage_conf.c:854:virStoragePoolDefParseXML] XML error: unknown storage pool type btrfs
>   Dec 23 17:19:30 [conf/domain_conf.c:12671:virDomainObjParseNode] XML error: unexpected root element <domain>, expecting <domstatus>
>   Dec 23 17:24:45 [qemu/qemu_monitor.c:653:qemuMonitorIO] internal error: End of file from monitor
>   Dec 23 20:12:00 [qemu/qemu_monitor.c:653:qemuMonitorIO] internal error: End of file from monitor
>   -- Reboot --
>   Dec 23 21:06:14 [util/virlog.c:877:virLogVMessage] libvirt version: 1.1.3.1, package: 2.fc20 (Fedora Project, 2013-11-17-23:28:43, ...
>   Dec 23 21:06:21 [conf/storage_conf.c:854:virStoragePoolDefParseXML] XML error: unknown storage pool type btrfs
> 
> Signed-off-by: Daniel P. Berrange <berrange at redhat.com>
> ---
>  man/journalctl.xml                    |  76 +++++
>  src/journal-remote/journal-gatewayd.c |  11 +-
>  src/journal/journalctl.c              |  39 ++-
>  src/shared/logs-show.c                | 532 ++++++++++++++++++++++++++++++----
>  src/shared/logs-show.h                |  16 +-
>  src/shared/output-mode.h              |   1 +
>  src/systemctl/systemctl.c             |  20 +-
>  7 files changed, 615 insertions(+), 80 deletions(-)
> 
> diff --git a/man/journalctl.xml b/man/journalctl.xml
> index acd75a6..bd8c2bd 100644
> --- a/man/journalctl.xml
> +++ b/man/journalctl.xml
> @@ -375,6 +375,21 @@
>                                                          </para>
>                                                  </listitem>
>                                          </varlistentry>
> +
> +                                        <varlistentry>
> +                                                <term>
> +                                                        <option>format:FMT</option>
> +                                                </term>
> +                                                <listitem>
> +                                                        <para>generates output
> +                                                        according to the format
> +                                                        specification given in
> +                                                        the FMT string. See the
> +                                                        OUTPUT FORMAT STRINGS
> +                                                        section for details
> +                                                        </para>
> +                                                </listitem>
> +                                        </varlistentry>
>                                  </variablelist>
>                                  </listitem>
>                          </varlistentry>
> @@ -878,6 +893,64 @@
>          </refsect1>
>  
>          <refsect1>
> +                <title>Output Format Strings</title>
> +
> +                <para>An output format string provides precise control how journal
> +                data records are formatted for output. A format string consists of
> +                mixture of literal text and variables to be substituted with journal
> +                data records. A variable takes the general form</para>
> +
> +                <programlisting>$(NAME:TYPE:LEN)</programlisting>
> +
> +                <para>The NAME component corresponds to any journal entry field
> +                (eg MESSAGE, _SYSTEMD_UNIT, CODE_FUNC, etc). The TYPE component
> +                determines the data format to use for printing the value. If
> +                omitted, it defaults to a sensible format for the NAME of the
> +                field. The LEN component places an upper limit on the length of
> +                strings being printed, beyond which they will be ellipsized.
> +                The valid data types for TYPE are:</para>
> +
> +                <variablelist>
> +                        <varlistentry>
> +                                <term>string</term>
> +                                <listitem><para>displayed if a printable string. If the value
> +                                contains non-printable characters, it will be shown as
> +                                '[blob data]'.</para></listitem>
> +                        </varlistentry>
> +
> +                        <varlistentry>
> +                                <term>string-raw</term>
> +                                <listitem><para>displayed as a raw string. All characters,
> +                                including non-printable ones, will be output.</para></listitem>
> +                        </varlistentry>
> +
> +                        <varlistentry>
> +                                <term>date</term>
> +                                <listitem><para>displayed as a date in traditional syslog
> +                                format.</para></listitem>
> +                        </varlistentry>
> +
> +                        <varlistentry>
> +                                <term>date-precise</term>
> +                                <listitem><para>displayed as a date in traditional syslog
> +                                format, but with microsecond precision.</para></listitem>
> +                        </varlistentry>
> +
> +                        <varlistentry>
> +                                <term>date-iso</term>
> +                                <listitem><para>displayed as a date in ISO 8601 format.</para></listitem>
> +                        </varlistentry>
> +
> +                        <varlistentry>
> +                                <term>date-raw</term>
> +                                <listitem><para>displayed as a date with seconds since the
> +                                epoch and microsecond precision.</para></listitem>
> +                        </varlistentry>
> +                </variablelist>
> +
> +        </refsect1>
> +
> +        <refsect1>
>                  <title>Exit status</title>
>  
>                  <para>On success, 0 is returned; otherwise, a non-zero
> @@ -931,6 +1004,9 @@
>  
>                  <programlisting>journalctl -f -u apache</programlisting>
>  
> +                <para>Display source file locations for libvirt daemon log messages:</para>
> +
> +                <programlisting>journalctl -o "format:%(__REALTIME_TIMESTAMP) [%(CODE_FILE):%(CODE_LINE):%(CODE_FUNC)] %(MESSAGE:string:80)\n"  _COMM=libvirtd</programlisting>
>          </refsect1>
>  
>          <refsect1>
> diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c
> index db81fe3..9d2bf50 100644
> --- a/src/journal-remote/journal-gatewayd.c
> +++ b/src/journal-remote/journal-gatewayd.c
> @@ -129,6 +129,7 @@ static ssize_t request_reader_entries(
>          RequestMeta *m = cls;
>          int r;
>          size_t n, k;
> +        _cleanup_output_formatter_ OutputFormatter *output_formatter = NULL;
>  
>          assert(m);
>          assert(buf);
> @@ -137,6 +138,12 @@ static ssize_t request_reader_entries(
>  
>          pos -= m->delta;
>  
> +        if ((r = output_formatter_from_mode(m->mode,
> +                                            &output_formatter)) < 0) {
> +                log_error("Failed to parse mode: %s", strerror(-r));
> +                return MHD_CONTENT_READER_END_WITH_ERROR;
> +        }
> +
>          while (pos >= m->size) {
>                  off_t sz;
>  
> @@ -203,7 +210,9 @@ static ssize_t request_reader_entries(
>                          }
>                  }
>  
> -                r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL);
> +                r = output_journal(m->tmp, m->journal,
> +                                   output_formatter,
> +                                   0, OUTPUT_FULL_WIDTH, NULL);
>                  if (r < 0) {
>                          log_error("Failed to serialize item: %s", strerror(-r));
>                          return MHD_CONTENT_READER_END_WITH_ERROR;
> diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c
> index 47206d3..2566e53 100644
> --- a/src/journal/journalctl.c
> +++ b/src/journal/journalctl.c
> @@ -62,7 +62,8 @@
>  
>  #define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE)
>  
> -static OutputMode arg_output = OUTPUT_SHORT;
> +static const char *arg_output = "short";
> +static OutputFormatter *arg_formatter = NULL;
>  static bool arg_pager_end = false;
>  static bool arg_follow = false;
>  static bool arg_full = true;
> @@ -190,7 +191,8 @@ static void help(void) {
>                 "  -r --reverse             Show the newest entries first\n"
>                 "  -o --output=STRING       Change journal output mode (short, short-iso,\n"
>                 "                                   short-precise, short-monotonic, verbose,\n"
> -               "                                   export, json, json-pretty, json-sse, cat)\n"
> +               "                                   export, json, json-pretty, json-sse, cat,\n"
> +               "                                   format:FORMAT-STRING)\n"
>                 "  -x --catalog             Add message explanations where available\n"
>                 "     --no-full             Ellipsize fields\n"
>                 "  -a --all                 Show all fields, including long and unprintable\n"
> @@ -303,6 +305,8 @@ static int parse_argv(int argc, char *argv[]) {
>          };
>  
>          int c, r;
> +        int err;
> +        OutputMode mode;
>  
>          assert(argc >= 0);
>          assert(argv);
> @@ -337,19 +341,7 @@ static int parse_argv(int argc, char *argv[]) {
>                          break;
>  
>                  case 'o':
> -                        arg_output = output_mode_from_string(optarg);
> -                        if (arg_output < 0) {
> -                                log_error("Unknown output format '%s'.", optarg);
> -                                return -EINVAL;
> -                        }
> -
> -                        if (arg_output == OUTPUT_EXPORT ||
> -                            arg_output == OUTPUT_JSON ||
> -                            arg_output == OUTPUT_JSON_PRETTY ||
> -                            arg_output == OUTPUT_JSON_SSE ||
> -                            arg_output == OUTPUT_CAT)
> -                                arg_quiet = true;
> -
> +                        arg_output = optarg;
>                          break;
>  
>                  case 'l':
> @@ -646,6 +638,21 @@ static int parse_argv(int argc, char *argv[]) {
>                          assert_not_reached("Unhandled option");
>                  }
>  
> +        if ((err = output_formatter_from_string(arg_output,
> +                                                &arg_formatter)) < 0) {
> +                log_error("Unknown/malformed output format '%s'.", arg_output);
> +                return err;
> +        }
> +
> +        mode = output_formatter_get_mode(arg_formatter);
> +        if (mode == OUTPUT_EXPORT ||
> +            mode == OUTPUT_JSON ||
> +            mode == OUTPUT_JSON_PRETTY ||
> +            mode == OUTPUT_JSON_SSE ||
> +            mode == OUTPUT_CAT)
> +                arg_quiet = true;
> +
> +
>          if (arg_follow && !arg_no_tail && arg_lines < -1)
>                  arg_lines = 10;
>  
> @@ -1960,7 +1967,7 @@ int main(int argc, char *argv[]) {
>                                  on_tty() * OUTPUT_COLOR |
>                                  arg_catalog * OUTPUT_CATALOG;
>  
> -                        r = output_journal(stdout, j, arg_output, 0, flags, &ellipsized);
> +                        r = output_journal(stdout, j, arg_formatter, 0, flags, &ellipsized);
>                          need_seek = true;
>                          if (r == -EADDRNOTAVAIL)
>                                  break;
> diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c
> index 5a7bbaf..75f0dce 100644
> --- a/src/shared/logs-show.c
> +++ b/src/shared/logs-show.c
> @@ -42,6 +42,36 @@
>  
>  #define JSON_THRESHOLD 4096
>  
> +typedef int (*OutputFunc)(OutputFormatter *formatter,
> +                          FILE *f,
> +                          sd_journal*j,
> +                          unsigned n_columns,
> +                          OutputFlags flags);
> +
> +typedef enum {
> +        OUTPUT_FIELD_LITERAL,      /* data is a literal string to print */
> +        OUTPUT_FIELD_DATE,         /* data is a variable to show as a simple date */
> +        OUTPUT_FIELD_DATE_PRECISE, /* data is a variable to show as a precise date */
> +        OUTPUT_FIELD_DATE_ISO,     /* data is a variable to show as a ISO date */
> +        OUTPUT_FIELD_DATE_RAW,     /* data is a variable to show as a RAW timestamp */
> +        OUTPUT_FIELD_STRING,       /* data is a variable to show as a printable string */
> +        OUTPUT_FIELD_STRING_RAW,   /* data is a variable to show as a raw string */
> +} OutputFieldType;
> +
> +typedef struct _OutputField {
> +        OutputFieldType type;
> +        size_t vallen; /* Truncate value to this length when printing */
> +        const char *data;
> +        size_t datalen;
> +} OutputField;
> +
> +struct _OutputFormatter {
> +        OutputFunc handler;
> +        OutputMode mode;
> +        size_t nfields;
> +        OutputField *fields;
> +};
> +
>  static int print_catalog(FILE *f, sd_journal *j) {
>          int r;
>          _cleanup_free_ char *t = NULL, *z = NULL;
> @@ -189,10 +219,59 @@ static bool print_multiline(FILE *f, unsigned prefix, unsigned n_columns, Output
>          return ellipsized;
>  }
>  
> +static int print_time(uint64_t ts,
> +                      OutputFieldType type,
> +                      FILE *f) {
> +        int n, r;
> +        char buf[64];
> +        time_t t;
> +        struct tm tm;
> +
> +        assert(f);
> +
> +        t = (time_t)(ts / USEC_PER_SEC);
> +
> +        switch (type) {
> +        case OUTPUT_FIELD_DATE:
> +                r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", localtime_r(&t, &tm));
> +                break;
> +
> +        case OUTPUT_FIELD_DATE_PRECISE:
> +                r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", localtime_r(&t, &tm));
> +                if (r > 0) {
> +                        snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
> +                                 ".%06llu", (unsigned long long) (ts % USEC_PER_SEC));
> +                }
> +                break;
> +
> +        case OUTPUT_FIELD_DATE_ISO:
> +                r = strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", localtime_r(&t, &tm));
> +                break;
> +
> +        case OUTPUT_FIELD_DATE_RAW:
> +                r = snprintf(buf, sizeof(buf), "[%5llu.%06llu]",
> +                             (unsigned long long) (ts / USEC_PER_SEC),
> +                             (unsigned long long) (ts % USEC_PER_SEC));
> +                break;
> +
> +        default:
> +                return -EINVAL;
> +        }
> +
> +        if (r <= 0) {
> +                log_error("Failed to format time.");
> +                return -EINVAL;
> +        }
> +        fputs(buf, f);
> +        n = strlen(buf);
> +
> +        return n;
> +}
> +
>  static int output_short(
> +                OutputFormatter *formatter,
>                  FILE *f,
>                  sd_journal *j,
> -                OutputMode mode,
>                  unsigned n_columns,
>                  OutputFlags flags) {
>  
> @@ -205,6 +284,7 @@ static int output_short(
>          int p = LOG_INFO;
>          bool ellipsized = false;
>  
> +        assert(formatter);
>          assert(f);
>          assert(j);
>  
> @@ -283,7 +363,7 @@ static int output_short(
>          if (priority_len == 1 && *priority >= '0' && *priority <= '7')
>                  p = *priority - '0';
>  
> -        if (mode == OUTPUT_SHORT_MONOTONIC) {
> +        if (formatter->mode == OUTPUT_SHORT_MONOTONIC) {
>                  uint64_t t;
>                  sd_id128_t boot_id;
>  
> @@ -300,17 +380,12 @@ static int output_short(
>                          return r;
>                  }
>  
> -                fprintf(f, "[%5llu.%06llu]",
> -                        (unsigned long long) (t / USEC_PER_SEC),
> -                        (unsigned long long) (t % USEC_PER_SEC));
> -
> -                n += 1 + 5 + 1 + 6 + 1;
> +                if ((r = print_time(t, OUTPUT_FIELD_DATE_RAW, f)) < 0)
> +                        return r;
>  
> +                n += r;
>          } else {
> -                char buf[64];
>                  uint64_t x;
> -                time_t t;
> -                struct tm tm;
>  
>                  r = -ENOENT;
>  
> @@ -325,30 +400,25 @@ static int output_short(
>                          return r;
>                  }
>  
> -                t = (time_t) (x / USEC_PER_SEC);
> -
> -                switch(mode) {
> +                switch (formatter->mode) {
>                  case OUTPUT_SHORT_ISO:
> -                        r = strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", localtime_r(&t, &tm));
> +                        if ((r = print_time(x, OUTPUT_FIELD_DATE_ISO, f)) < 0)
> +                                return r;
> +
>                          break;
>                  case OUTPUT_SHORT_PRECISE:
> -                        r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", localtime_r(&t, &tm));
> -                        if (r > 0) {
> -                                snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
> -                                         ".%06llu", (unsigned long long) (x % USEC_PER_SEC));
> -                        }
> +                        if ((r = print_time(x, OUTPUT_FIELD_DATE_PRECISE, f)) < 0)
> +                                return r;
> +
>                          break;
>                  default:
> -                        r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", localtime_r(&t, &tm));
> -                }
> +                        if ((r = print_time(x, OUTPUT_FIELD_DATE, f)) < 0)
> +                                return r;
>  
> -                if (r <= 0) {
> -                        log_error("Failed to format time.");
> -                        return -EINVAL;
> +                        break;
>                  }
>  
> -                fputs(buf, f);
> -                n += strlen(buf);
> +                n += r;
>          }
>  
>          if (hostname && shall_print(hostname, hostname_len, flags)) {
> @@ -389,9 +459,9 @@ static int output_short(
>  }
>  
>  static int output_verbose(
> +                OutputFormatter *formatter,
>                  FILE *f,
>                  sd_journal *j,
> -                OutputMode mode,
>                  unsigned n_columns,
>                  OutputFlags flags) {
>  
> @@ -402,6 +472,7 @@ static int output_verbose(
>          char ts[FORMAT_TIMESTAMP_MAX + 7];
>          int r;
>  
> +        assert(formatter);
>          assert(f);
>          assert(j);
>  
> @@ -493,9 +564,9 @@ static int output_verbose(
>  }
>  
>  static int output_export(
> +                OutputFormatter *formatter,
>                  FILE *f,
>                  sd_journal *j,
> -                OutputMode mode,
>                  unsigned n_columns,
>                  OutputFlags flags) {
>  
> @@ -507,6 +578,7 @@ static int output_export(
>          const void *data;
>          size_t length;
>  
> +        assert(formatter);
>          assert(j);
>  
>          sd_journal_set_data_threshold(j, 0);
> @@ -631,9 +703,9 @@ void json_escape(
>  }
>  
>  static int output_json(
> +                OutputFormatter *formatter,
>                  FILE *f,
>                  sd_journal *j,
> -                OutputMode mode,
>                  unsigned n_columns,
>                  OutputFlags flags) {
>  
> @@ -647,6 +719,7 @@ static int output_json(
>          Hashmap *h = NULL;
>          bool done, separator;
>  
> +        assert(formatter);
>          assert(j);
>  
>          sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : JSON_THRESHOLD);
> @@ -669,7 +742,7 @@ static int output_json(
>                  return r;
>          }
>  
> -        if (mode == OUTPUT_JSON_PRETTY)
> +        if (formatter->mode == OUTPUT_JSON_PRETTY)
>                  fprintf(f,
>                          "{\n"
>                          "\t\"__CURSOR\" : \"%s\",\n"
> @@ -681,7 +754,7 @@ static int output_json(
>                          monotonic,
>                          sd_id128_to_string(boot_id, sid));
>          else {
> -                if (mode == OUTPUT_JSON_SSE)
> +                if (formatter->mode == OUTPUT_JSON_SSE)
>                          fputs("data: ", f);
>  
>                  fprintf(f,
> @@ -758,7 +831,7 @@ static int output_json(
>                                  continue;
>  
>                          if (separator) {
> -                                if (mode == OUTPUT_JSON_PRETTY)
> +                                if (formatter->mode == OUTPUT_JSON_PRETTY)
>                                          fputs(",\n\t", f);
>                                  else
>                                          fputs(", ", f);
> @@ -833,9 +906,9 @@ static int output_json(
>  
>          } while (!done);
>  
> -        if (mode == OUTPUT_JSON_PRETTY)
> +        if (formatter->mode == OUTPUT_JSON_PRETTY)
>                  fputs("\n}\n", f);
> -        else if (mode == OUTPUT_JSON_SSE)
> +        else if (formatter->mode == OUTPUT_JSON_SSE)
>                  fputs("}\n\n", f);
>          else
>                  fputs(" }\n", f);
> @@ -852,9 +925,9 @@ finish:
>  }
>  
>  static int output_cat(
> +                OutputFormatter *formatter,
>                  FILE *f,
>                  sd_journal *j,
> -                OutputMode mode,
>                  unsigned n_columns,
>                  OutputFlags flags) {
>  
> @@ -862,6 +935,7 @@ static int output_cat(
>          size_t l;
>          int r;
>  
> +        assert(formatter);
>          assert(j);
>          assert(f);
>  
> @@ -885,13 +959,117 @@ static int output_cat(
>          return 0;
>  }
>  
> -static int (*output_funcs[_OUTPUT_MODE_MAX])(
> +static int output_format(
> +                OutputFormatter *formatter,
>                  FILE *f,
> -                sd_journal*j,
> -                OutputMode mode,
> +                sd_journal *j,
>                  unsigned n_columns,
> -                OutputFlags flags) = {
> +                OutputFlags flags) {
> +
> +        size_t i;
> +        const void *data;
> +        size_t l;
> +        int r;
> +        uint64_t t;
> +        bool truncated;
> +
> +        assert(formatter);
> +        assert(j);
> +        assert(f);
> +
> +        sd_journal_set_data_threshold(j, 0);
> +
> +        for (i = 0 ; i < formatter->nfields ; i++) {
> +                switch (formatter->fields[i].type) {
> +                case OUTPUT_FIELD_LITERAL:
> +                        fputs(formatter->fields[i].data, f);
> +                        break;
> +
> +                case OUTPUT_FIELD_DATE:
> +                case OUTPUT_FIELD_DATE_PRECISE:
> +                case OUTPUT_FIELD_DATE_ISO:
> +                case OUTPUT_FIELD_DATE_RAW:
> +                        if (streq(formatter->fields[i].data,
> +                                  "__MONOTONIC_TIMESTAMP")) {
> +                                sd_id128_t boot_id;
> +                                r = sd_journal_get_monotonic_usec(j, &t, &boot_id);
> +                        } else if (streq(formatter->fields[i].data,
> +                                         "__REALTIME_TIMESTAMP")) {
> +                                r = sd_journal_get_realtime_usec(j, &t);
> +                        } else {
> +                                r = sd_journal_get_data(j, formatter->fields[i].data, &data, &l);
> +
> +                                if (r > 0) {
> +                                        assert(l >= formatter->fields[i].datalen);
>  
> +                                        r = safe_atou64((const char *)data + formatter->fields[i].datalen + 1,
> +                                                        &t);
> +                                }
> +                        }
> +
> +                        if (r < 0) {
> +                                if (r == -ENOENT)
> +                                        continue;
> +
> +                                log_error("Failed to get %s: %s",
> +                                          formatter->fields[i].data, strerror(-r));
> +                                return r;
> +                        }
> +
> +                        r = print_time(t, formatter->fields[i].type, f);
> +                        if (r < 0)
> +                                return r;
> +                        break;
> +
> +                case OUTPUT_FIELD_STRING:
> +                case OUTPUT_FIELD_STRING_RAW:
> +                        sd_journal_set_data_threshold(j, 0);
> +
> +                        r = sd_journal_get_data(j, formatter->fields[i].data, &data, &l);
> +                        if (r < 0) {
> +                                if (r == -ENOENT)
> +                                        continue;
> +
> +                                log_error("Failed to get data: %s", strerror(-r));
> +                                return r;
> +                        }
> +
> +                        assert(l >= formatter->fields[i].datalen);
> +
> +                        data = ((const char *)data) + formatter->fields[i].datalen + 1;
> +                        l -= formatter->fields[i].datalen + 1;
> +
> +                        if (formatter->fields[i].vallen != 0 &&
> +                            l > formatter->fields[i].vallen) {
> +                                l = formatter->fields[i].vallen;
> +                                truncated = true;
> +                        } else {
> +                                truncated = false;
> +                        }
> +
> +                        if (formatter->fields[i].type == OUTPUT_FIELD_STRING_RAW) {
> +                                fwrite((const char*) data, 1, l, f);
> +                        } else {
> +                                if (((const char *)data)[l - 1] == '\n')
> +                                        l--;
> +
> +                                if (!utf8_is_printable(data, l)) {
> +                                        char bytes[FORMAT_BYTES_MAX];
> +                                        fprintf(f, "[%s blob data]", format_bytes(bytes, sizeof(bytes), l));
> +                                } else {
> +                                        fwrite((const char*) data, 1, l, f);
> +                                }
> +                                if (truncated)
> +                                        fputs("...", f);
> +                        }
> +                        break;
> +                }
> +        }
> +
> +        return 0;
> +}
> +
> +static OutputFunc output_funcs[_OUTPUT_MODE_MAX] = {
>          [OUTPUT_SHORT] = output_short,
>          [OUTPUT_SHORT_ISO] = output_short,
>          [OUTPUT_SHORT_PRECISE] = output_short,
> @@ -901,25 +1079,25 @@ static int (*output_funcs[_OUTPUT_MODE_MAX])(
>          [OUTPUT_JSON] = output_json,
>          [OUTPUT_JSON_PRETTY] = output_json,
>          [OUTPUT_JSON_SSE] = output_json,
> -        [OUTPUT_CAT] = output_cat
> +        [OUTPUT_CAT] = output_cat,
> +        [OUTPUT_FORMAT] = output_format,
>  };
>  
>  int output_journal(
>                  FILE *f,
>                  sd_journal *j,
> -                OutputMode mode,
> +                OutputFormatter *formatter,
>                  unsigned n_columns,
>                  OutputFlags flags,
>                  bool *ellipsized) {
>  
>          int ret;
> -        assert(mode >= 0);
> -        assert(mode < _OUTPUT_MODE_MAX);
> +        assert(formatter);
>  
>          if (n_columns <= 0)
>                  n_columns = columns();
>  
> -        ret = output_funcs[mode](f, j, mode, n_columns, flags);
> +        ret = formatter->handler(formatter, f, j, n_columns, flags);
>          fflush(stdout);
>  
>          if (ellipsized && ret > 0)
> @@ -945,7 +1123,7 @@ static int maybe_print_begin_newline(FILE *f, OutputFlags *flags) {
>  
>  static int show_journal(FILE *f,
>                          sd_journal *j,
> -                        OutputMode mode,
> +                        OutputFormatter *formatter,
>                          unsigned n_columns,
>                          usec_t not_before,
>                          unsigned how_many,
> @@ -958,8 +1136,7 @@ static int show_journal(FILE *f,
>          int warn_cutoff = flags & OUTPUT_WARN_CUTOFF;
>  
>          assert(j);
> -        assert(mode >= 0);
> -        assert(mode < _OUTPUT_MODE_MAX);
> +        assert(formatter);
>  
>          /* Seek to end */
>          r = sd_journal_seek_tail(j);
> @@ -1002,7 +1179,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, formatter, n_columns, flags, ellipsized);
>                          if (r < 0)
>                                  goto finish;
>                  }
> @@ -1250,7 +1427,7 @@ int add_match_this_boot(sd_journal *j, const char *machine) {
>  int show_journal_by_unit(
>                  FILE *f,
>                  const char *unit,
> -                OutputMode mode,
> +                OutputFormatter *formatter,
>                  unsigned n_columns,
>                  usec_t not_before,
>                  unsigned how_many,
> @@ -1263,8 +1440,7 @@ int show_journal_by_unit(
>          int r;
>          int jflags = SD_JOURNAL_LOCAL_ONLY | system * SD_JOURNAL_SYSTEM;
>  
> -        assert(mode >= 0);
> -        assert(mode < _OUTPUT_MODE_MAX);
> +        assert(formatter);
>          assert(unit);
>  
>          if (how_many <= 0)
> @@ -1292,7 +1468,7 @@ int show_journal_by_unit(
>                  log_debug("Journal filter: %s", filter);
>          }
>  
> -        return show_journal(f, j, mode, n_columns, not_before, how_many, flags, ellipsized);
> +        return show_journal(f, j, formatter, n_columns, not_before, how_many, flags, ellipsized);
>  }
>  
>  static const char *const output_mode_table[_OUTPUT_MODE_MAX] = {
> @@ -1305,7 +1481,257 @@ static const char *const output_mode_table[_OUTPUT_MODE_MAX] = {
>          [OUTPUT_JSON] = "json",
>          [OUTPUT_JSON_PRETTY] = "json-pretty",
>          [OUTPUT_JSON_SSE] = "json-sse",
> -        [OUTPUT_CAT] = "cat"
> +        [OUTPUT_CAT] = "cat",
> +        [OUTPUT_FORMAT] = "format",
>  };
>  
>  DEFINE_STRING_TABLE_LOOKUP(output_mode, OutputMode);
> +
> +
> +void output_formatter_free(OutputFormatter **formatter) {
> +        if (!*formatter)
> +                return;
> +
> +        free(*formatter);
> +        *formatter = NULL;
> +}
> +
> +static int output_formatter_add_field(OutputFormatter *formatter,
> +                                      OutputFieldType type,
> +                                      size_t vallen,
> +                                      char *data,
> +                                      size_t datalen)
> +{
> +        OutputField *fields;
> +
> +        fields = realloc_multiply(formatter->fields,
> +                                  sizeof(OutputField),
> +                                  formatter->nfields + 1);
> +        if (!fields)
> +                return -ENOMEM;
> +
> +        fields[formatter->nfields].type = type;
> +        fields[formatter->nfields].vallen = vallen;
> +        fields[formatter->nfields].data = data;
> +        fields[formatter->nfields].datalen = datalen;
> +        formatter->fields = fields;
> +        formatter->nfields++;
> +
> +        return 0;
> +}
> +
> +
> +static int output_formatter_expand_escapes(char *data)
> +{
> +        size_t i, j;
> +
> +        for (i = 0, j = 0; data[i] != '\0'; i++) {
> +                if (data[i] == '\\') {
> +                        i++;
> +                        switch (data[i]) {
> +                        case '\'':
> +                                data[j++] = '\'';
> +                                break;
> +                        case '"':
> +                                data[j++] = '"';
> +                                break;
> +                        case '?':
> +                                data[j++] = '?';
> +                                break;
> +                        case '\\':
> +                                data[j++] = '\\';
> +                                break;
> +                        case 'a':
> +                                data[j++] = '\a';
> +                                break;
> +                        case 'b':
> +                                data[j++] = '\b';
> +                                break;
> +                        case 'f':
> +                                data[j++] = '\f';
> +                                break;
> +                        case 'n':
> +                                data[j++] = '\n';
> +                                break;
> +                        case 'r':
> +                                data[j++] = '\r';
> +                                break;
> +                        case 't':
> +                                data[j++] = '\t';
> +                                break;
> +                        case 'v':
> +                                data[j++] = '\v';
> +                                break;
> +                        default:
> +                                return -EINVAL;
> +                        }
> +                } else {
> +                        data[j++] = data[i];
> +                }
> +        }
> +        data[j] = '\0';
> +
> +        return 0;
> +}
> +
> +static int output_formatter_parse_format(OutputFormatter *formatter,
> +                                         const char *format) {
> +        const char *prev = format;
> +        int r;
> +
> +        do {
> +                const char *next;
> +                char *data;
> +                size_t datalen;
> +
> +                next = strstr(format, "%(");
> +
> +                if (!next || next != prev) {
> +                        datalen = next ? (size_t)(next - prev) : strlen(prev);
> +                        data = strndup(prev, datalen);
> +
> +                        if ((r = output_formatter_expand_escapes(data)) < 0)
> +                                return r;
> +
> +                        if ((r = output_formatter_add_field(formatter,
> +                                                            OUTPUT_FIELD_LITERAL, 0,
> +                                                            data, datalen)) < 0) {
> +                                free(data);
> +                                return r;
> +                        }
> +                }
> +
> +                if (next) {
> +                        const char *end;
> +                        OutputFieldType type;
> +                        char *typestr;
> +                        char *lenstr;
> +                        uint64_t vallen;
> +
> +                        end = strchr(next, ')');
> +                        if (!end)
> +                                return -EINVAL;
> +
> +                        datalen = (end - (next + 2));
> +                        data = strndup(next + 2, datalen);
> +
> +                        if ((typestr = strchr(data, ':')) != NULL) {
> +                                datalen = typestr - data;
> +                                *typestr = '\0';
> +                                typestr++;
> +                                lenstr = strchr(typestr, ':');
> +                                if (lenstr) {
> +                                        *lenstr = '\0';
> +                                        lenstr++;
> +                                        if ((r = safe_atou64(lenstr, &vallen)) < 0) {
> +                                                free(data);
> +                                                return r;
> +                                        }
> +                                }
> +
> +                                if (streq(typestr, "string"))
> +                                        type = OUTPUT_FIELD_STRING;
> +                                else if (streq(typestr, "date"))
> +                                        type = OUTPUT_FIELD_DATE;
> +                                else if (streq(typestr, "date-precise"))
> +                                        type = OUTPUT_FIELD_DATE_PRECISE;
> +                                else if (streq(typestr, "date-iso"))
> +                                        type = OUTPUT_FIELD_DATE_ISO;
> +                                else if (streq(typestr, "date-raw"))
> +                                        type = OUTPUT_FIELD_DATE_RAW;
> +                                else if (streq(typestr, "string-raw"))
> +                                        type = OUTPUT_FIELD_STRING_RAW;
> +                                else {
> +                                        free(data);
> +                                        return -EINVAL;
> +                                }
> +                        } else {
> +                                if (streq(data, "__MONOTONIC_TIMESTAMP"))
> +                                        type = OUTPUT_FIELD_DATE_RAW;
> +                                else if (streq(data, "__REALTIME_TIMESTAMP"))
> +                                        type = OUTPUT_FIELD_DATE;
> +                                else
> +                                        type = OUTPUT_FIELD_STRING;
> +                        }
> +
> +                        if ((r = output_formatter_add_field(formatter,
> +                                                            type, vallen,
> +                                                            data, datalen)) < 0) {
> +                                free(data);
> +                                return r;
> +                        }
> +                        format = end + 1;
> +                        prev = end + 1;
> +                } else {
> +                        format = NULL;
> +                }
> +        } while (format && *format);
> +
> +        return 0;
> +}
> +
> +#define DEFAULT_FORMAT "%(__REALTIME_TIMESTAMP) %(_HOSTNAME) %(SYSLOG_IDENTIFIER)[%(_PID)]: %(MESSAGE)\\n"
> +
> +int output_formatter_from_mode(OutputMode mode,
> +                               OutputFormatter **formatter)
> +{
> +        int r;
> +        _cleanup_output_formatter_ OutputFormatter *ret = NULL;
> +
> +        assert(mode >= 0);
> +        assert(mode < _OUTPUT_MODE_MAX);
> +        assert(formatter);
> +        assert(!*formatter);
> +
> +        if (!(ret = new(OutputFormatter, 1)))
> +                return -ENOMEM;
> +
> +        ret->mode = mode;
> +        if (ret->mode == OUTPUT_FORMAT &&
> +            ((r = output_formatter_parse_format(ret, DEFAULT_FORMAT)) < 0))
> +                return r;
> +        ret->handler = output_funcs[ret->mode];
> +
> +        *formatter = ret;
> +        ret = NULL;
> +        return 0;
> +}
> +
> +
> +int output_formatter_from_string(const char *arg,
> +                                 OutputFormatter **formatter) {
> +        int r;
> +        _cleanup_output_formatter_ OutputFormatter *ret = NULL;
> +
> +        assert(arg);
> +        assert(formatter);
> +        assert(!*formatter);
> +
> +        if (!(ret = new(OutputFormatter, 1)))
> +                return -ENOMEM;
> +
> +        if (strneq(arg, "format:", 7)) {
> +                ret->mode = OUTPUT_FORMAT;
> +                if ((r = output_formatter_parse_format(ret, arg + 7)) < 0)
> +                        return r;
> +        } else {
> +                if ((ret->mode = output_mode_from_string(arg)) < 0)
> +                        return -EINVAL;
> +
> +                if (ret->mode == OUTPUT_FORMAT &&
> +                    ((r = output_formatter_parse_format(ret, DEFAULT_FORMAT)) < 0))
> +                        return r;
> +        }
> +        ret->handler = output_funcs[ret->mode];
> +
> +        *formatter = ret;
> +        ret = NULL;
> +        return 0;
> +}
> +
> +
> +OutputMode output_formatter_get_mode(OutputFormatter *formatter) {
> +        assert(formatter);
> +
> +        return formatter->mode;
> +}
> diff --git a/src/shared/logs-show.h b/src/shared/logs-show.h
> index 187ee59..84e3f3d 100644
> --- a/src/shared/logs-show.h
> +++ b/src/shared/logs-show.h
> @@ -30,10 +30,12 @@
>  #include "util.h"
>  #include "output-mode.h"
>  
> +typedef struct _OutputFormatter OutputFormatter;
> +
>  int output_journal(
>                  FILE *f,
>                  sd_journal *j,
> -                OutputMode mode,
> +                OutputFormatter *formatter,
>                  unsigned n_columns,
>                  OutputFlags flags,
>                  bool *ellipsized);
> @@ -52,7 +54,7 @@ int add_matches_for_user_unit(
>  int show_journal_by_unit(
>                  FILE *f,
>                  const char *unit,
> -                OutputMode mode,
> +                OutputFormatter *formatter,
>                  unsigned n_columns,
>                  usec_t not_before,
>                  unsigned how_many,
> @@ -69,3 +71,13 @@ void json_escape(
>  
>  const char* output_mode_to_string(OutputMode m) _const_;
>  OutputMode output_mode_from_string(const char *s) _pure_;
> +
> +int output_formatter_from_string(const char *arg,
> +                                 OutputFormatter **formatter);
> +int output_formatter_from_mode(OutputMode mode,
> +                               OutputFormatter **formatter);
> +
> +OutputMode output_formatter_get_mode(OutputFormatter *formatter);
> +
> +void output_formatter_free(OutputFormatter **formatter);
> +#define _cleanup_output_formatter_ _cleanup_(output_formatter_free)
> diff --git a/src/shared/output-mode.h b/src/shared/output-mode.h
> index ac1bb01..0f695ac 100644
> --- a/src/shared/output-mode.h
> +++ b/src/shared/output-mode.h
> @@ -32,6 +32,7 @@ typedef enum OutputMode {
>          OUTPUT_JSON_PRETTY,
>          OUTPUT_JSON_SSE,
>          OUTPUT_CAT,
> +        OUTPUT_FORMAT,
>          _OUTPUT_MODE_MAX,
>          _OUTPUT_MODE_INVALID = -1
>  } OutputMode;
> diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
> index 9012128..5deeb70 100644
> --- a/src/systemctl/systemctl.c
> +++ b/src/systemctl/systemctl.c
> @@ -134,7 +134,8 @@ static enum action {
>  static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
>  static char *arg_host = NULL;
>  static unsigned arg_lines = 10;
> -static OutputMode arg_output = OUTPUT_SHORT;
> +static const char *arg_output = "short";
> +static OutputFormatter *arg_formatter = NULL;
>  static bool arg_plain = false;
>  
>  static const struct {
> @@ -3486,7 +3487,7 @@ static void print_status_info(
>          if (i->id && arg_transport == BUS_TRANSPORT_LOCAL) {
>                  show_journal_by_unit(stdout,
>                                       i->id,
> -                                     arg_output,
> +                                     arg_formatter,
>                                       0,
>                                       i->inactive_exit_timestamp_monotonic,
>                                       arg_lines,
> @@ -5491,7 +5492,8 @@ static void systemctl_help(void) {
>                 "     --root=PATH      Enable unit files in the specified root directory\n"
>                 "  -n --lines=INTEGER  Number of journal entries to show\n"
>                 "  -o --output=STRING  Change journal output mode (short, short-monotonic,\n"
> -               "                      verbose, export, json, json-pretty, json-sse, cat)\n"
> +               "                      verbose, export, json, json-pretty, json-sse, cat,\n"
> +               "                      format:FORMAT-STRING)\n"
>                 "     --plain          Print unit dependencies as a list instead of a tree\n\n"
>                 "Unit Commands:\n"
>                 "  list-units [PATTERN...]         List loaded units\n"
> @@ -5712,6 +5714,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
>          };
>  
>          int c;
> +        int err;
>  
>          assert(argc >= 0);
>          assert(argv);
> @@ -5933,11 +5936,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
>                          break;
>  
>                  case 'o':
> -                        arg_output = output_mode_from_string(optarg);
> -                        if (arg_output < 0) {
> -                                log_error("Unknown output '%s'.", optarg);
> -                                return -EINVAL;
> -                        }
> +                        arg_output = optarg;
>                          break;
>  
>                  case 'i':
> @@ -5996,6 +5995,11 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
>                  return -EINVAL;
>          }
>  
> +        if ((err = output_formatter_from_string(arg_output, &arg_formatter)) < 0) {
> +                log_error("Unknown / malformed output format '%s'.", arg_output);
> +                return err;
> +        }
> +
>          return 1;
>  }
>  
> -- 
> 1.9.3
> 
> _______________________________________________
> systemd-devel mailing list
> systemd-devel at lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/systemd-devel
> 


More information about the systemd-devel mailing list