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): Sep 17 21:47:22 kulicka libvirt: @cee: {"msg":"Domain not found", "category":"../../src/test/test_driver.c","libvirt_priority":"error", "funcname":"testLookupDomainByName","line":1403, "timestamp":"2012-09-17 19:47:22.731+0000"} None of the existing log output formats (e.g. "syslog" or "file") is affected. The JSON objects are allocated on the stack, which is a little less convenient than allocating them from the heap, but we don't have to care about the allocations failing, calling virReportOOMError and deadlocking on virLogMutex. Signed-off-by: Miloslav Trmač <mitr@xxxxxxxxxx> --- docs/logging.html.in | 3 ++ src/util/json.c | 20 +++++++++- src/util/logging.c | 110 +++++++++++++++++++++++++++++++++++++++++++++++++-- src/util/logging.h | 1 + 4 files changed, 128 insertions(+), 6 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/json.c b/src/util/json.c index 03362c4..96786c8 100644 --- a/src/util/json.c +++ b/src/util/json.c @@ -672,6 +672,8 @@ int virJSONValueObjectIsNull(virJSONValuePtr object, const char *key) * The caller is responsible for sizing @pairs_buf appropriately, one entry * per virJSONStaticObjectAppend* call. */ +/* Don't call any error reporting or logging functions from this function, + the error loging mechanism may use this! */ void virJSONStaticObjectInitialize(virJSONObjectPtr object, virJSONObjectPairPtr pairs_buf) { @@ -690,6 +692,8 @@ void virJSONStaticObjectInitialize(virJSONObjectPtr object, * necessary to free anything, but the caller is responsible for keeping * @key, @value_dest and @value valid until it stops using @object. */ +/* Don't call any error reporting or logging functions from this function, + the error loging mechanism may use this! */ void virJSONStaticObjectAppendString(virJSONObjectPtr object, const char *key, virJSONValuePtr value_dest, const char *value) @@ -721,6 +725,8 @@ void virJSONStaticObjectAppendString(virJSONObjectPtr object, const char *key, * * @buf_size should be at least INT_BUFSIZE_BOUND(@value). */ +/* Don't call any error reporting or logging functions from this function, + the error loging mechanism may use this! */ void virJSONStaticObjectAppendNumberInt(virJSONObjectPtr object, const char *key, virJSONValuePtr value_dest, @@ -1073,6 +1079,8 @@ cleanup: } +/* Don't call any error reporting or logging functions from this function, + the error loging mechanism may use this! */ static yajl_gen virYAJLInit(bool pretty) { yajl_gen g; @@ -1102,6 +1110,8 @@ static yajl_gen virYAJLInit(bool pretty) * * Returns the generator on success, NULL on error. */ +/* Don't call any error reporting or logging functions from this function, + the error loging mechanism may use this! */ virJSONStringGeneratorPtr virJSONStringGeneratorInitObject(void) { yajl_gen g; @@ -1126,6 +1136,8 @@ virJSONStringGeneratorPtr virJSONStringGeneratorInitObject(void) * Terminate the object being generated in @gen, and return its string value * (or NULL on error). The value is valid until @gen is freed. */ +/* Don't call any error reporting or logging functions from this function, + the error loging mechanism may use this! */ const char *virJSONStringGeneratorFinishObject(virJSONStringGeneratorPtr gen) { const unsigned char *res; @@ -1148,19 +1160,21 @@ const char *virJSONStringGeneratorFinishObject(virJSONStringGeneratorPtr gen) * Free @gen if it is not NULL. Invalidates the result of * virJSONStringGeneratorFinishObject(). */ +/* Don't call any error reporting or logging functions from this function, + the error loging mechanism may use this! */ void virJSONStringGeneratorFree(virJSONStringGeneratorPtr gen) { if (gen != NULL) yajl_gen_free(gen); } +/* Don't call any error reporting or logging functions from this function, + the error loging mechanism may use this! */ static int virJSONValueToStringOne(virJSONValuePtr object, yajl_gen g) { int i; - VIR_DEBUG("object=%p type=%d gen=%p", object, object->type, g); - switch (object->type) { case VIR_JSON_TYPE_OBJECT: if (yajl_gen_map_open(g) != yajl_gen_status_ok) @@ -1219,6 +1233,8 @@ static int virJSONValueToStringOne(virJSONValuePtr object, * Note that this adds each property into the "current" object, it does not * add a subobject. */ +/* Don't call any error reporting or logging functions from this function, + the error loging mechanism may use this! */ int virJSONStringGeneratorAddProperties(virJSONStringGeneratorPtr gen, virJSONObjectPtr object) { diff --git a/src/util/logging.c b/src/util/logging.c index b71eacc..97431b2 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> @@ -39,12 +40,15 @@ #include "virterror_internal.h" #include "logging.h" #include "memory.h" +#include "json.h" #include "util.h" #include "buf.h" #include "threads.h" #include "virfile.h" #include "virtime.h" +#include "intprops.h" + #define VIR_FROM_THIS VIR_FROM_NONE /* @@ -127,6 +131,8 @@ static const char *virLogOutputString(virLogDestination ldest) { return "syslog"; case VIR_LOG_TO_FILE: return "file"; + case VIR_LOG_TO_SYSLOG_JSON: + return "syslog/json"; } return "unknown"; } @@ -575,7 +581,8 @@ int virLogDefineOutput(virLogOutputFunc f, virLogCloseFunc c, void *data, 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); @@ -897,6 +904,82 @@ static void virLogOutputToSyslog(const char *category 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"; +} + +static void virLogOutputToSyslogJSON(const char *category, int priority, + const char *funcname, long long linenr, + const char *timestamp, + unsigned int flags, + const char *rawstr, const char *str, + void *data ATTRIBUTE_UNUSED) +{ + /* This can't use virJSONValueToString because that function can create log + messages, which would lead to infinite recursion. */ + virJSONStringGeneratorPtr g = NULL; + virJSONObject json; + virJSONObjectPair json_pairs[6]; + virJSONValue json_msg, json_category, json_priority, json_funcname; + virJSONValue json_line, json_timestamp; + char line_buf[INT_BUFSIZE_BOUND(linenr)]; + const char *json_string; + + virCheckFlags(VIR_LOG_STACK_TRACE,); + + /* 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. + + 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). */ + virJSONStaticObjectInitialize(&json, json_pairs); + virJSONStaticObjectAppendString(&json, "msg", &json_msg, rawstr); + virJSONStaticObjectAppendString(&json, "category", &json_category, + category); + virJSONStaticObjectAppendString(&json, "priority", &json_priority, + virLogPriorityJSONString(priority)); + if (funcname != NULL) + virJSONStaticObjectAppendString(&json, "funcname", &json_funcname, + funcname); + virJSONStaticObjectAppendNumberInt(&json, "line", &json_line, + line_buf, sizeof(line_buf), linenr); + virJSONStaticObjectAppendString(&json, "timestamp", &json_timestamp, + timestamp); + + g = virJSONStringGeneratorInitObject(); + if (g == NULL) + goto error; + if (virJSONStringGeneratorAddProperties(g, &json) != 0) + goto error; + json_string = virJSONStringGeneratorFinishObject(g); + if (json_string == NULL) + goto error; + + syslog(virLogPrioritySyslog(priority), "@cee: %s", json_string); + virJSONStringGeneratorFree(g); + return; + + error: + virJSONStringGeneratorFree(g); + virLogOutputToSyslog(category, priority, funcname, linenr, timestamp, flags, + rawstr, str, NULL); +} +#endif + static char *current_ident = NULL; static void virLogCloseSyslog(void *data ATTRIBUTE_UNUSED) { @@ -904,7 +987,17 @@ static void virLogCloseSyslog(void *data ATTRIBUTE_UNUSED) { VIR_FREE(current_ident); } -static int virLogAddOutputToSyslog(int priority, const char *ident) { +static int virLogAddOutputToSyslog(int priority, 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 */ @@ -914,7 +1007,7 @@ static int virLogAddOutputToSyslog(int priority, const char *ident) { 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); @@ -975,7 +1068,16 @@ int 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++; @@ -988,7 +1090,7 @@ int 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 10ff6c9..a66f5dc 100644 --- a/src/util/logging.h +++ b/src/util/logging.h @@ -80,6 +80,7 @@ typedef enum { VIR_LOG_TO_STDERR = 1, VIR_LOG_TO_SYSLOG, VIR_LOG_TO_FILE, + VIR_LOG_TO_SYSLOG_JSON, } virLogDestination; /** -- 1.7.11.4 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list