[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

[libvirt] [PATCH 5/5] Add "syslog/json" log output format.



This is independent from the rest of the series, sending it anyway for
consideration; this makes the metadata available to users on non-systemd
platforms.  I believe the technical objections (primarily
async-signal-safety) have been resolved; the question of whether the
project wants to support this or not is still open, of course.

Add a "syslog/json" output format, which uses a JSON representation
(as specified in Lumberjack project, and planned for the CEE standard)
to format individual components, making the result easy to parse without
writing custom code or using unreliable regexps.

Example message (line-wrapped for readability, the actual output is a
single line):

Oct 17 16:29:56 kulicka libvirt: @cee: {"msg":"Domain not found",
    "LIBVIRT_SOURCE":"error","priority":"err",
    "CODE_FILE":"../../src/test/test_driver.c","CODE_LINE":1405,
    "CODE_FUNC":"testLookupDomainByName",
    "timestamp":"2012-10-17 14:29:56.683+0000","DOMAIN":12,"CODE":42,
    "STR1":"Domain not found","STR2":""}

None of the existing log output formats (e.g. "syslog" or "file") is
affected.

Async-signal-safety is preserved by
a) providing a custom allocation function to yajl, and
b) using a custom print function.
Both use memory allocated within the stack frame.

Signed-off-by: Miloslav Trmač <mitr redhat com>
---
 docs/logging.html.in |   3 +
 src/util/logging.c   | 272 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 src/util/logging.h   |   1 +
 3 files changed, 272 insertions(+), 4 deletions(-)

diff --git a/docs/logging.html.in b/docs/logging.html.in
index a95f7bc..4f8ad87 100644
--- a/docs/logging.html.in
+++ b/docs/logging.html.in
@@ -143,6 +143,9 @@
       <li><code>x:stderr</code> output goes to stderr</li>
       <li><code>x:syslog:name</code> use syslog for the output and use the
       given <code>name</code> as the ident</li>
+      <li><code>x:syslog/json:name</code> use JSON-formatted syslog (also
+      called the CEE format or Lumberjack) for the output and use the given
+      <code>name</code> as the ident</li>
       <li><code>x:file:file_path</code> output to a file, with the given
       filepath</li>
     </ul>
diff --git a/src/util/logging.c b/src/util/logging.c
index 5166def..0dafa59 100644
--- a/src/util/logging.c
+++ b/src/util/logging.c
@@ -21,6 +21,7 @@
 
 #include <config.h>
 
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdarg.h>
 #include <stdlib.h>
@@ -50,6 +51,15 @@
 #include "virtime.h"
 #include "intprops.h"
 
+#if HAVE_YAJL
+# include <yajl/yajl_gen.h>
+# ifdef HAVE_YAJL2
+#  define yajl_size_t size_t
+# else
+#  define yajl_size_t unsigned int
+# endif
+#endif
+
 /* Journald output is only supported on Linux new enough to expose
  * htole64.  */
 #if HAVE_SYSLOG_H && defined(__linux__) && HAVE_DECL_HTOLE64
@@ -160,6 +170,8 @@ virLogOutputString(virLogDestination ldest)
         return "file";
     case VIR_LOG_TO_JOURNALD:
         return "journald";
+    case VIR_LOG_TO_SYSLOG_JSON:
+        return "syslog/json";
     }
     return "unknown";
 }
@@ -645,7 +657,8 @@ virLogDefineOutput(virLogOutputFunc f,
     if (f == NULL)
         return -1;
 
-    if (dest == VIR_LOG_TO_SYSLOG || dest == VIR_LOG_TO_FILE) {
+    if (dest == VIR_LOG_TO_SYSLOG || dest == VIR_LOG_TO_SYSLOG_JSON
+        || dest == VIR_LOG_TO_FILE) {
         if (name == NULL)
             return -1;
         ndup = strdup(name);
@@ -1008,6 +1021,238 @@ virLogOutputToSyslog(virLogSource source ATTRIBUTE_UNUSED,
     syslog(virLogPrioritySyslog(priority), "%s", str);
 }
 
+
+#if HAVE_YAJL
+static const char *
+virLogPriorityJSONString(virLogPriority lvl)
+{
+    /* This follows "prioritynames" from <sys/syslog.h> */
+    switch (virLogPrioritySyslog(lvl)) {
+    case LOG_DEBUG:
+        return "debug";
+    case LOG_INFO:
+        return "info";
+    case LOG_WARNING:
+        return "warn";
+    case LOG_ERR:
+        return "err";
+    }
+    return "unknown";
+}
+
+
+/* A trivial "arena" stack allocator for yajl to avoid malloc(). */
+union stackAllocMaxAlign
+{
+    intmax_t i;
+    long double dbl;
+    void (*p);
+    void (*fp)(void);
+};
+
+#define YAJL_ALLOC_SPACE 1024
+struct stackAlloc
+{
+    union
+    {
+        unsigned char buf[YAJL_ALLOC_SPACE];
+        union stackAllocMaxAlign align; /* To align buf correctly */
+    } v;
+    void *next_free;            /* Within v.buf */
+};
+
+static void *
+stackMalloc(void *ctx, yajl_size_t sz)
+{
+    struct stackAlloc *stack;
+    void *end, *res;
+
+    stack = ctx;
+    sz = ((sz + sizeof(union stackAllocMaxAlign) - 1)
+          / sizeof(union stackAllocMaxAlign)
+          * sizeof(union stackAllocMaxAlign));
+    end = stack->v.buf + ARRAY_CARDINALITY(stack->v.buf);
+    if ((unsigned char *)end - (unsigned char *)stack->next_free < sz)
+        return NULL;
+    res = stack->next_free;
+    stack->next_free = (unsigned char *)stack->next_free + sz;
+    return res;
+}
+
+static void *
+stackRealloc(void *ctx ATTRIBUTE_UNUSED, void *ptr ATTRIBUTE_UNUSED,
+             yajl_size_t sz ATTRIBUTE_UNUSED)
+{
+    return NULL;         /* realloc is not necessary in our case. */
+}
+
+static void
+stackFree(void *ctx ATTRIBUTE_UNUSED, void *ptr ATTRIBUTE_UNUSED)
+{
+}
+
+static void
+stackAllocInit(yajl_alloc_funcs *dest, struct stackAlloc *stack)
+{
+    stack->next_free = stack->v.buf;
+
+    dest->malloc = stackMalloc;
+    dest->realloc = stackRealloc;
+    dest->free = stackFree;
+    dest->ctx = stack;
+}
+
+
+#define MAX_JSON_OUTPUT_SIZE 2048
+struct JSONOutput
+{
+    char buf[MAX_JSON_OUTPUT_SIZE];
+    char *next;
+    bool failed;
+};
+
+static void
+JSONOutputPrint(void *ctx, const char *str, yajl_size_t len)
+{
+    struct JSONOutput *dest;
+
+    dest = ctx;
+    if (dest->failed
+        || (dest->buf + ARRAY_CARDINALITY(dest->buf)) - dest->next < len)
+        dest->failed = true;
+    else {
+        memcpy(dest->next, str, len);
+        dest->next += len;
+    }
+}
+
+static int
+yajlAddString(yajl_gen g, const char *key, const char *value)
+{
+    if (yajl_gen_string(g, (const unsigned char *)key, strlen(key))
+        != yajl_gen_status_ok)
+        return -1;
+    if (yajl_gen_string(g, (const unsigned char *)value, strlen(value))
+        != yajl_gen_status_ok)
+        return -1;
+    return 0;
+}
+
+
+static int
+yajlAddInt(yajl_gen g, const char *key, int value)
+{
+    char buf[INT_BUFSIZE_BOUND(value)], *p;
+
+    if (yajl_gen_string(g, (const unsigned char *)key, strlen(key))
+        != yajl_gen_status_ok)
+        return -1;
+    p = virFormatIntDecimal(buf, sizeof(buf), value);
+    if (yajl_gen_number(g, p, strlen(p)) != yajl_gen_status_ok)
+        return -1;
+    return 0;
+}
+
+
+static void
+virLogOutputToSyslogJSON(virLogSource source,
+                         virLogPriority priority,
+                         const char *filename,
+                         int linenr,
+                         const char *funcname,
+                         const char *timestamp,
+                         virLogMetadataPtr metadata,
+                         unsigned int flags,
+                         const char *rawstr,
+                         const char *str,
+                         void *data ATTRIBUTE_UNUSED)
+{
+    struct stackAlloc stack_alloc;
+    struct JSONOutput output;
+    yajl_alloc_funcs alloc_funcs;
+    yajl_gen g = NULL;
+# ifndef HAVE_YAJL2
+    yajl_gen_config conf = { 0, " "};
+# endif
+
+    virCheckFlags(VIR_LOG_STACK_TRACE,);
+
+    output.next = output.buf;
+    output.failed = false;
+
+# ifdef HAVE_YAJL2
+    stackAllocInit(&alloc_funcs, &stack_alloc);
+    g = yajl_gen_alloc(&alloc_funcs);
+    if (g) {
+        yajl_gen_config(g, yajl_gen_beautify, 0);
+        yajl_gen_config(g, yajl_gen_indent_string, " ");
+        yajl_gen_config(g, yajl_gen_validate_utf8, 1);
+        yajl_gen_config(g, yajl_gen_print_callback, JSONOutputPrint,
+                        (void *)&output);
+    }
+# else
+    g = yajl_gen_alloc2(JSONOutputPrint, &conf, &alloc_funcs, &output);
+# endif
+    if (g == NULL)
+        goto error;
+
+    /* The field names follow https://fedorahosted.org/lumberjack/wiki/FieldList
+       - use the same field names when the purpose and type matches, use a
+       different field name otherwise.
+
+       Consistency with the journal names is also recommended.
+
+       msg goes first, so that the most important information is in a fixed
+       position on the screen (= easy to find); the metadata comes later (or
+       perhaps does not fit into the terminal/log viewer window). */
+
+    if (yajl_gen_map_open(g) != yajl_gen_status_ok)
+        goto error;
+
+#define CHECK(E)                                \
+    do {                                        \
+        if ((E) != 0)                           \
+            goto error;                         \
+    } while (0)
+    CHECK(yajlAddString(g, "msg", rawstr));
+    CHECK(yajlAddString(g, "LIBVIRT_SOURCE", virLogSourceTypeToString(source)));
+    CHECK(yajlAddString(g, "priority", virLogPriorityJSONString(priority)));
+    CHECK(yajlAddString(g, "CODE_FILE", filename));
+    CHECK(yajlAddInt(g, "CODE_LINE", linenr));
+    if (funcname != NULL)
+        CHECK(yajlAddString(g, "CODE_FUNC", funcname));
+    CHECK(yajlAddString(g, "timestamp", timestamp));
+
+    if (metadata != NULL) {
+        while (metadata->key != NULL) {
+            if (metadata->s != NULL)
+                CHECK(yajlAddString(g, metadata->key, metadata->s));
+            else
+                CHECK(yajlAddInt(g, metadata->key, metadata->i));
+            metadata++;
+        }
+    }
+#undef CHECK
+
+    if (yajl_gen_map_close(g) != yajl_gen_status_ok)
+        goto error;
+    JSONOutputPrint(&output, "", 1);
+    if (output.failed)
+        goto error;
+
+    syslog(virLogPrioritySyslog(priority), "@cee: %s", output.buf);
+    yajl_gen_free(g);
+    return;
+
+ error:
+    if (g != NULL)
+        yajl_gen_free(g);
+    virLogOutputToSyslog(source, priority, filename, linenr, funcname,
+                         timestamp, metadata, flags, rawstr, str, NULL);
+}
+#endif
+
+
 static char *current_ident = NULL;
 
 
@@ -1021,8 +1266,18 @@ virLogCloseSyslog(void *data ATTRIBUTE_UNUSED)
 
 static int
 virLogAddOutputToSyslog(virLogPriority priority,
-                        const char *ident)
+                        const char *ident,
+                        bool is_json)
 {
+    virLogOutputFunc f;
+
+#ifdef HAVE_YAJL
+    if (is_json)
+        f = virLogOutputToSyslogJSON;
+    else
+#endif
+        f = virLogOutputToSyslog;
+
     /*
      * ident needs to be kept around on Solaris
      */
@@ -1032,7 +1287,7 @@ virLogAddOutputToSyslog(virLogPriority priority,
         return -1;
 
     openlog(current_ident, 0, 0);
-    if (virLogDefineOutput(virLogOutputToSyslog, virLogCloseSyslog, NULL,
+    if (virLogDefineOutput(f, virLogCloseSyslog, NULL,
                            priority, VIR_LOG_TO_SYSLOG, ident, 0) < 0) {
         closelog();
         VIR_FREE(current_ident);
@@ -1307,7 +1562,16 @@ virLogParseOutputs(const char *outputs)
             if (virLogAddOutputToStderr(prio) == 0)
                 count++;
         } else if (STREQLEN(cur, "syslog", 6)) {
+            bool is_json;
+
             cur += 6;
+            if (*cur == ':')
+                is_json = false;
+            else if (STREQLEN(cur, "/json", 5)) {
+                is_json = true;
+                cur += 5;
+            } else
+                goto cleanup;
             if (*cur != ':')
                 goto cleanup;
             cur++;
@@ -1320,7 +1584,7 @@ virLogParseOutputs(const char *outputs)
             name = strndup(str, cur - str);
             if (name == NULL)
                 goto cleanup;
-            if (virLogAddOutputToSyslog(prio, name) == 0)
+            if (virLogAddOutputToSyslog(prio, name, is_json) == 0)
                 count++;
             VIR_FREE(name);
 #endif /* HAVE_SYSLOG_H */
diff --git a/src/util/logging.h b/src/util/logging.h
index 52feecc..4b589cb 100644
--- a/src/util/logging.h
+++ b/src/util/logging.h
@@ -42,6 +42,7 @@ typedef enum {
     VIR_LOG_TO_SYSLOG,
     VIR_LOG_TO_FILE,
     VIR_LOG_TO_JOURNALD,
+    VIR_LOG_TO_SYSLOG_JSON,
 } virLogDestination;
 
 typedef enum {
-- 
1.7.11.7


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]