chiark / gitweb /
journal: implement message catalog
[elogind.git] / src / shared / logs-show.c
index 725adb6451542cb00a188be78a58d3d841e84375..cb93761bd19d83f191229932957ee9b00d1e5aa0 100644 (file)
 #include "log.h"
 #include "util.h"
 #include "utf8.h"
+#include "hashmap.h"
 
 #define PRINT_THRESHOLD 128
 #define JSON_THRESHOLD 4096
 
+static int print_catalog(FILE *f, sd_journal *j) {
+        int r;
+        _cleanup_free_ char *t = NULL, *z = NULL;
+
+
+        r = sd_journal_get_catalog(j, &t);
+        if (r < 0)
+                return r;
+
+        z = strreplace(strstrip(t), "\n", "\n-- ");
+        if (!z)
+                return log_oom();
+
+        fputs("-- ", f);
+        fputs(z, f);
+        fputc('\n', f);
+
+        return 0;
+}
+
 static int parse_field(const void *data, size_t length, const char *field, char **target, size_t *target_size) {
         size_t fl, nl;
         void *buf;
@@ -243,7 +264,7 @@ static int output_short(
                 }
         }
 
-        if (flags)
+        if (flags & OUTPUT_SHOW_ALL)
                 fprintf(f, ": %s%.*s%s\n", color_on, (int) message_len, message, color_off);
         else if (!utf8_is_printable_n(message, message_len)) {
                 char bytes[FORMAT_BYTES_MAX];
@@ -264,6 +285,9 @@ static int output_short(
         } else
                 fputs("\n", f);
 
+        if (flags & OUTPUT_CATALOG)
+                print_catalog(f, j);
+
         return 0;
 }
 
@@ -321,6 +345,9 @@ static int output_verbose(
                         fprintf(f, "\t%.*s\n", (int) length, (const char*) data);
         }
 
+        if (flags & OUTPUT_CATALOG)
+                print_catalog(f, j);
+
         return 0;
 }
 
@@ -405,7 +432,7 @@ static int output_export(
         return 0;
 }
 
-static void json_escape(
+void json_escape(
                 FILE *f,
                 const char* p,
                 size_t l,
@@ -464,12 +491,14 @@ static int output_json(
                 OutputFlags flags) {
 
         uint64_t realtime, monotonic;
-        char *cursor;
+        char *cursor, *k;
         const void *data;
         size_t length;
         sd_id128_t boot_id;
         char sid[33];
         int r;
+        Hashmap *h = NULL;
+        bool done, separator;
 
         assert(j);
 
@@ -502,7 +531,10 @@ static int output_json(
                         (unsigned long long) realtime,
                         (unsigned long long) monotonic,
                         sd_id128_to_string(boot_id, sid));
-        else
+        else {
+                if (mode == OUTPUT_JSON_SSE)
+                        fputs("data: ", f);
+
                 fprintf(f,
                         "{ \"__CURSOR\" : \"%s\", "
                         "\"__REALTIME_TIMESTAMP\" : \"%llu\", "
@@ -512,39 +544,160 @@ static int output_json(
                         (unsigned long long) realtime,
                         (unsigned long long) monotonic,
                         sd_id128_to_string(boot_id, sid));
+        }
         free(cursor);
 
+        h = hashmap_new(string_hash_func, string_compare_func);
+        if (!h)
+                return -ENOMEM;
+
+        /* First round, iterate through the entry and count how often each field appears */
         SD_JOURNAL_FOREACH_DATA(j, data, length) {
-                const char *c;
+                const char *eq;
+                char *n;
+                unsigned u;
 
-                /* We already printed the boot id, from the data in
-                 * the header, hence let's suppress it here */
                 if (length >= 9 &&
                     memcmp(data, "_BOOT_ID=", 9) == 0)
                         continue;
 
-                c = memchr(data, '=', length);
-                if (!c) {
-                        log_error("Invalid field.");
-                        return -EINVAL;
-                }
+                eq = memchr(data, '=', length);
+                if (!eq)
+                        continue;
 
-                if (mode == OUTPUT_JSON_PRETTY)
-                        fputs(",\n\t", f);
-                else
-                        fputs(", ", f);
+                n = strndup(data, eq - (const char*) data);
+                if (!n) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
 
-                json_escape(f, data, c - (const char*) data, flags);
-                fputs(" : ", f);
-                json_escape(f, c + 1, length - (c - (const char*) data) - 1, flags);
+                u = PTR_TO_UINT(hashmap_get(h, n));
+                if (u == 0) {
+                        r = hashmap_put(h, n, UINT_TO_PTR(1));
+                        if (r < 0) {
+                                free(n);
+                                goto finish;
+                        }
+                } else {
+                        r = hashmap_update(h, n, UINT_TO_PTR(u + 1));
+                        free(n);
+                        if (r < 0)
+                                goto finish;
+                }
         }
 
+        separator = true;
+        do {
+                done = true;
+
+                SD_JOURNAL_FOREACH_DATA(j, data, length) {
+                        const char *eq;
+                        char *kk, *n;
+                        size_t m;
+                        unsigned u;
+
+                        /* We already printed the boot id, from the data in
+                         * the header, hence let's suppress it here */
+                        if (length >= 9 &&
+                            memcmp(data, "_BOOT_ID=", 9) == 0)
+                                continue;
+
+                        eq = memchr(data, '=', length);
+                        if (!eq)
+                                continue;
+
+                        if (separator) {
+                                if (mode == OUTPUT_JSON_PRETTY)
+                                        fputs(",\n\t", f);
+                                else
+                                        fputs(", ", f);
+                        }
+
+                        m = eq - (const char*) data;
+
+                        n = strndup(data, m);
+                        if (!n) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                        u = PTR_TO_UINT(hashmap_get2(h, n, (void**) &kk));
+                        if (u == 0) {
+                                /* We already printed this, let's jump to the next */
+                                free(n);
+                                separator = false;
+
+                                continue;
+                        } else if (u == 1) {
+                                /* Field only appears once, output it directly */
+
+                                json_escape(f, data, m, flags);
+                                fputs(" : ", f);
+
+                                json_escape(f, eq + 1, length - m - 1, flags);
+
+                                hashmap_remove(h, n);
+                                free(kk);
+                                free(n);
+
+                                separator = true;
+
+                                continue;
+
+                        } else {
+                                /* Field appears multiple times, output it as array */
+                                json_escape(f, data, m, flags);
+                                fputs(" : [ ", f);
+                                json_escape(f, eq + 1, length - m - 1, flags);
+
+                                /* Iterate through the end of the list */
+
+                                while (sd_journal_enumerate_data(j, &data, &length) > 0) {
+                                        if (length < m + 1)
+                                                continue;
+
+                                        if (memcmp(data, n, m) != 0)
+                                                continue;
+
+                                        if (((const char*) data)[m] != '=')
+                                                continue;
+
+                                        fputs(", ", f);
+                                        json_escape(f, (const char*) data + m + 1, length - m - 1, flags);
+                                }
+
+                                fputs(" ]", f);
+
+                                hashmap_remove(h, n);
+                                free(kk);
+                                free(n);
+
+                                /* Iterate data fields form the beginning */
+                                done = false;
+                                separator = true;
+
+                                break;
+                        }
+                }
+
+        } while (!done);
+
         if (mode == OUTPUT_JSON_PRETTY)
                 fputs("\n}\n", f);
+        else if (mode == OUTPUT_JSON_SSE)
+                fputs("}\n\n", f);
         else
                 fputs(" }\n", f);
 
-        return 0;
+        r = 0;
+
+finish:
+        while ((k = hashmap_steal_first_key(h)))
+                free(k);
+
+        hashmap_free(h);
+
+        return r;
 }
 
 static int output_cat(
@@ -592,6 +745,7 @@ static int (*output_funcs[_OUTPUT_MODE_MAX])(
         [OUTPUT_EXPORT] = output_export,
         [OUTPUT_JSON] = output_json,
         [OUTPUT_JSON_PRETTY] = output_json,
+        [OUTPUT_JSON_SSE] = output_json,
         [OUTPUT_CAT] = output_cat
 };
 
@@ -769,6 +923,7 @@ static const char *const output_mode_table[_OUTPUT_MODE_MAX] = {
         [OUTPUT_EXPORT] = "export",
         [OUTPUT_JSON] = "json",
         [OUTPUT_JSON_PRETTY] = "json-pretty",
+        [OUTPUT_JSON_SSE] = "json-sse",
         [OUTPUT_CAT] = "cat"
 };