]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
stats: event export - Add json format support
authorJosef 'Jeff' Sipek <jeff.sipek@open-xchange.com>
Thu, 4 Apr 2019 11:23:41 +0000 (14:23 +0300)
committerAki Tuomi <aki.tuomi@open-xchange.com>
Mon, 20 May 2019 06:46:32 +0000 (06:46 +0000)
This commit adds support for format=json.

Since JSON doesn't have a native timestamp data type, one must specify
exactly one time-* format_args token.

src/stats/Makefile.am
src/stats/event-exporter-fmt-json.c [new file with mode: 0644]
src/stats/event-exporter.h
src/stats/stats-metrics.c
src/stats/stats-settings.c

index 30c91cf79ab1c04eb9670e17cea61fb66bcc6b9c..97200a27d0b4eafb9877b4d6a706c5cb84cf55e5 100644 (file)
@@ -17,6 +17,7 @@ stats_SOURCES = \
        client-reader.c \
        client-writer.c \
        event-exporter-fmt.c \
+       event-exporter-fmt-json.c \
        event-exporter-fmt-none.c \
        event-exporter-transport-drop.c \
        main.c \
diff --git a/src/stats/event-exporter-fmt-json.c b/src/stats/event-exporter-fmt-json.c
new file mode 100644 (file)
index 0000000..3ccf09f
--- /dev/null
@@ -0,0 +1,228 @@
+/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "array.h"
+#include "lib-event-private.h"
+#include "event-exporter.h"
+#include "str.h"
+#include "json-parser.h"
+#include "hostpid.h"
+
+static void append_str(string_t *dest, const char *str)
+{
+       str_append_c(dest, '"');
+       json_append_escaped(dest, str);
+       str_append_c(dest, '"');
+}
+
+static void append_int(string_t *dest, intmax_t val)
+{
+       str_printfa(dest, "%jd", val);
+}
+
+static void append_time(string_t *dest, const struct timeval *time,
+                       enum event_exporter_time_fmt fmt)
+{
+       switch (fmt) {
+       case EVENT_EXPORTER_TIME_FMT_NATIVE:
+               i_panic("JSON does not have a native date/time type");
+       case EVENT_EXPORTER_TIME_FMT_UNIX:
+               event_export_helper_fmt_unix_time(dest, time);
+               break;
+       case EVENT_EXPORTER_TIME_FMT_RFC3339:
+               str_append_c(dest, '"');
+               event_export_helper_fmt_rfc3339_time(dest, time);
+               str_append_c(dest, '"');
+               break;
+       }
+}
+
+static void append_field_value(string_t *dest, const struct event_field *field,
+                              const struct metric_export_info *info)
+{
+       switch (field->value_type) {
+       case EVENT_FIELD_VALUE_TYPE_STR:
+               append_str(dest, field->value.str);
+               break;
+       case EVENT_FIELD_VALUE_TYPE_INTMAX:
+               append_int(dest, field->value.intmax);
+               break;
+       case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
+               append_time(dest, &field->value.timeval,
+                           info->exporter->time_format);
+               break;
+       }
+}
+
+static void json_export_name(string_t *dest, struct event *event,
+                            const struct metric_export_info *info)
+{
+       if ((info->include & EVENT_EXPORTER_INCL_NAME) == 0)
+               return;
+
+       append_str(dest, "event");
+       str_append_c(dest, ':');
+       append_str(dest, event->sending_name);
+       str_append_c(dest, ',');
+}
+
+static void json_export_hostname(string_t *dest,
+                                const struct metric_export_info *info)
+{
+       if ((info->include & EVENT_EXPORTER_INCL_HOSTNAME) == 0)
+               return;
+
+       append_str(dest, "hostname");
+       str_append_c(dest, ':');
+       append_str(dest, my_hostname);
+       str_append_c(dest, ',');
+}
+
+static void json_export_timestamps(string_t *dest, struct event *event,
+                                  const struct metric_export_info *info)
+{
+       if ((info->include & EVENT_EXPORTER_INCL_TIMESTAMPS) == 0)
+               return;
+
+       append_str(dest, "start_time");
+       str_append_c(dest, ':');
+       append_time(dest, &event->tv_created, info->exporter->time_format);
+       str_append_c(dest, ',');
+
+       append_str(dest, "end_time");
+       str_append_c(dest, ':');
+       append_time(dest, &ioloop_timeval, info->exporter->time_format);
+       str_append_c(dest, ',');
+}
+
+static void append_category(string_t *dest, struct event_category *cat)
+{
+       /* append parent's categories */
+       if (cat->parent != NULL)
+               append_category(dest, cat->parent);
+
+       /* append this */
+       append_str(dest, cat->name);
+       str_append_c(dest, ',');
+}
+
+static void json_export_categories(string_t *dest, struct event *event,
+                                  const struct metric_export_info *info)
+{
+       struct event_category *const *cats;
+       unsigned int count;
+       unsigned int i;
+
+       if ((info->include & EVENT_EXPORTER_INCL_CATEGORIES) == 0)
+               return;
+
+       append_str(dest, "categories");
+       str_append(dest, ":[");
+
+       cats = event_get_categories(event, &count);
+       for (i = 0; i < count; i++)
+               append_category(dest, cats[i]);
+
+       /* remove trailing comma */
+       if (count != 0)
+               str_truncate(dest, str_len(dest) - 1);
+
+       str_append(dest, "],");
+}
+
+static void json_export_fields(string_t *dest, struct event *event,
+                              const struct metric_export_info *info,
+                              const unsigned int fields_count,
+                              const struct metric_field *fields)
+{
+       bool appended = FALSE;
+
+       if ((info->include & EVENT_EXPORTER_INCL_FIELDS) == 0)
+               return;
+
+       append_str(dest, "fields");
+       str_append(dest, ":{");
+
+       if (fields_count == 0) {
+               /* include all fields */
+               const struct event_field *fields;
+               unsigned int count;
+
+               fields = event_get_fields(event, &count);
+
+               for (unsigned int i = 0; i < count; i++) {
+                       const struct event_field *field = &fields[i];
+
+                       append_str(dest, field->key);
+                       str_append_c(dest, ':');
+                       append_field_value(dest, field, info);
+                       str_append_c(dest, ',');
+
+                       appended = TRUE;
+               }
+       } else {
+               for (unsigned int i = 0; i < fields_count; i++) {
+                       const char *name = fields[i].field_key;
+                       const struct event_field *field;
+
+                       field = event_find_field(event, name);
+                       if (field == NULL)
+                               continue; /* doesn't exist, skip it */
+
+                       append_str(dest, name);
+                       str_append_c(dest, ':');
+                       append_field_value(dest, field, info);
+                       str_append_c(dest, ',');
+
+                       appended = TRUE;
+               }
+       }
+
+       /* remove trailing comma */
+       if (appended)
+               str_truncate(dest, str_len(dest) - 1);
+
+       str_append(dest, "},");
+}
+
+/*
+ * Serialize the event as:
+ *
+ * {
+ *     "name": <event name>,
+ *     "hostname": <hostname>,
+ *     "start_time": <event creation timestamp>,
+ *     "end_time": <event export timestamp>,
+ *     "categories": [ <categories>, ... ],
+ *     "fields": {
+ *             <name>: <value>,
+ *             ...
+ *     }
+ * }
+ *
+ */
+void event_export_fmt_json(const struct metric *metric,
+                          struct event *event, buffer_t *dest)
+{
+       const struct metric_export_info *info = &metric->export_info;
+
+       if (info->include == EVENT_EXPORTER_INCL_NONE) {
+               str_append(dest, "{}");
+               return;
+       }
+
+       str_append_c(dest, '{');
+
+       json_export_name(dest, event, info);
+       json_export_hostname(dest, info);
+       json_export_timestamps(dest, event, info);
+       json_export_categories(dest, event, info);
+       json_export_fields(dest, event, info, metric->fields_count,
+                          metric->fields);
+
+       /* remove trailing comma */
+       str_truncate(dest, str_len(dest) - 1);
+
+       str_append_c(dest, '}');
+}
index 5d0ba25d833ae3f6b988f917765184c677c8aa5e..9cff61a43d40849ddc12eda3f08aab2ec5f21f97 100644 (file)
@@ -4,6 +4,7 @@
 #include "stats-metrics.h"
 
 /* fmt functions */
+void event_export_fmt_json(const struct metric *metric, struct event *event, buffer_t *dest);
 void event_export_fmt_none(const struct metric *metric, struct event *event, buffer_t *dest);
 
 /* transport functions */
index 5d0a49bc11c3a6938cf9c8428961c1953674b948..bd33e688cbed037f259029ed3b792accccb6d582 100644 (file)
@@ -67,6 +67,9 @@ static void stats_exporters_add_set(struct stats_metrics *metrics,
        if (strcmp(set->format, "none") == 0) {
                exporter->format = event_export_fmt_none;
                exporter->format_mime_type = "application/octet-stream";
+       } else if (strcmp(set->format, "json") == 0) {
+               exporter->format = event_export_fmt_json;
+               exporter->format_mime_type = "application/json";
        } else {
                i_unreached();
        }
index 37c30023e1123c61c025c8f310dda36a5620e278..626bb277709238ff2fa31802c1fff2780d95f739 100644 (file)
@@ -234,6 +234,8 @@ static bool stats_exporter_settings_check(void *_set, pool_t pool ATTR_UNUSED,
                return FALSE;
        } else if (strcmp(set->format, "none") == 0) {
                time_fmt_required = FALSE;
+       } else if (strcmp(set->format, "json") == 0) {
+               time_fmt_required = TRUE;
        } else {
                *error_r = t_strdup_printf("Unknown exporter format '%s'",
                                           set->format);