From: Lennart Poettering Date: Wed, 24 Oct 2012 23:19:24 +0000 (+0200) Subject: journal: properly serialize fields with multiple values into JSON X-Git-Tag: v196~244 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=commitdiff_plain;h=d99ae53a7327e1520ea4b9a3408c2d7f938c4b37 journal: properly serialize fields with multiple values into JSON This now matches the JSON serialization spec from: http://www.freedesktop.org/wiki/Software/systemd/json --- diff --git a/man/journalctl.xml b/man/journalctl.xml index 9bc0153a9..026bb2294 100644 --- a/man/journalctl.xml +++ b/man/journalctl.xml @@ -212,7 +212,10 @@ information). json formats entries as JSON data structures, one per - line. json-pretty + line (see Journal + JSON Format for more + information). json-pretty also formats entries as JSON data structures, but formats them in multiple lines in order to make them diff --git a/src/journal/test-journal-send.c b/src/journal/test-journal-send.c index 168c84365..9bf42f9b8 100644 --- a/src/journal/test-journal-send.c +++ b/src/journal/test-journal-send.c @@ -47,5 +47,15 @@ int main(int argc, char *argv[]) { huge, NULL); + sd_journal_send("MESSAGE=uiui", + "VALUE=A", + "VALUE=B", + "VALUE=C", + "SINGLETON=1", + "OTHERVALUE=X", + "OTHERVALUE=Y", + "WITH_BINARY=this is a binary value \a", + NULL); + return 0; } diff --git a/src/shared/hashmap.c b/src/shared/hashmap.c index 26a4eff07..ef78070f4 100644 --- a/src/shared/hashmap.c +++ b/src/shared/hashmap.c @@ -328,7 +328,8 @@ int hashmap_put(Hashmap *h, const void *key, void *value) { hash = h->hash_func(key) % NBUCKETS; - if ((e = hash_scan(h, hash, key))) { + e = hash_scan(h, hash, key); + if (e) { if (e->value == value) return 0; @@ -359,8 +360,8 @@ int hashmap_replace(Hashmap *h, const void *key, void *value) { assert(h); hash = h->hash_func(key) % NBUCKETS; - - if ((e = hash_scan(h, hash, key))) { + e = hash_scan(h, hash, key); + if (e) { e->key = key; e->value = value; return 0; @@ -369,6 +370,21 @@ int hashmap_replace(Hashmap *h, const void *key, void *value) { return hashmap_put(h, key, value); } +int hashmap_update(Hashmap *h, const void *key, void *value) { + struct hashmap_entry *e; + unsigned hash; + + assert(h); + + hash = h->hash_func(key) % NBUCKETS; + e = hash_scan(h, hash, key); + if (!e) + return -ENOENT; + + e->value = value; + return 0; +} + void* hashmap_get(Hashmap *h, const void *key) { unsigned hash; struct hashmap_entry *e; @@ -384,6 +400,24 @@ void* hashmap_get(Hashmap *h, const void *key) { return e->value; } +void* hashmap_get2(Hashmap *h, const void *key, void **key2) { + unsigned hash; + struct hashmap_entry *e; + + if (!h) + return NULL; + + hash = h->hash_func(key) % NBUCKETS; + e = hash_scan(h, hash, key); + if (!e) + return NULL; + + if (key2) + *key2 = (void*) e->key; + + return e->value; +} + bool hashmap_contains(Hashmap *h, const void *key) { unsigned hash; diff --git a/src/shared/hashmap.h b/src/shared/hashmap.h index ed41817dd..55dea0a69 100644 --- a/src/shared/hashmap.h +++ b/src/shared/hashmap.h @@ -51,8 +51,10 @@ Hashmap *hashmap_copy(Hashmap *h); int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t compare_func); int hashmap_put(Hashmap *h, const void *key, void *value); +int hashmap_update(Hashmap *h, const void *key, void *value); int hashmap_replace(Hashmap *h, const void *key, void *value); void* hashmap_get(Hashmap *h, const void *key); +void* hashmap_get2(Hashmap *h, const void *key, void **rkey); bool hashmap_contains(Hashmap *h, const void *key); void* hashmap_remove(Hashmap *h, const void *key); void* hashmap_remove_value(Hashmap *h, const void *key, void *value); diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index 63a48e455..36cce7355 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -29,6 +29,7 @@ #include "log.h" #include "util.h" #include "utf8.h" +#include "hashmap.h" #define PRINT_THRESHOLD 128 #define JSON_THRESHOLD 4096 @@ -464,12 +465,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); @@ -518,31 +521,141 @@ static int output_json( } 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) @@ -550,7 +663,15 @@ static int output_json( 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(