]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
stats: Add file transport
authorAki Tuomi <aki.tuomi@open-xchange.com>
Tue, 3 Oct 2023 12:01:56 +0000 (15:01 +0300)
committeraki.tuomi <aki.tuomi@open-xchange.com>
Mon, 30 Oct 2023 06:29:29 +0000 (06:29 +0000)
src/stats/Makefile.am
src/stats/event-exporter-transport-file.c [new file with mode: 0644]
src/stats/event-exporter.h
src/stats/stats-metrics.c
src/stats/stats-settings.c

index 0d6f5982864a609650429c050c605ee1efadbf65..f538cb9a6a5d498a6852a6f88796bd185cfad244 100644 (file)
@@ -42,6 +42,7 @@ libstats_local_la_SOURCES = \
        event-exporter-transport-drop.c \
        event-exporter-transport-http-post.c \
        event-exporter-transport-log.c \
+       event-exporter-transport-file.c \
        $(stats_services) \
        stats-service.c \
        stats-event-category.c \
diff --git a/src/stats/event-exporter-transport-file.c b/src/stats/event-exporter-transport-file.c
new file mode 100644 (file)
index 0000000..41cfe2d
--- /dev/null
@@ -0,0 +1,118 @@
+/* Copyright (c) 2023 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "str.h"
+#include "eacces-error.h"
+#include "ostream.h"
+#include "event-exporter.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+struct exporter_file {
+       struct exporter_file *next;
+       char *fname;
+       struct ostream *output;
+       int fd;
+       time_t last_error;
+};
+
+#define EXPORTER_LAST_ERROR_DELAY 60
+
+static struct exporter_file *exporter_file_list_head = NULL;
+
+static void exporter_file_close(struct exporter_file *node)
+{
+       if (o_stream_finish(node->output) < 0) {
+               i_error("write(%s) failed: %s", node->fname,
+                       o_stream_get_error(node->output));
+               node->last_error = ioloop_time;
+       }
+       o_stream_destroy(&node->output);
+       i_close_fd(&node->fd);
+}
+
+static void exporter_file_destroy(struct exporter_file **_node)
+{
+       struct exporter_file *node = *_node;
+       if (node == NULL)
+               return;
+       *_node = NULL;
+
+       exporter_file_close(node);
+       i_free(node->fname);
+       i_free(node);
+}
+
+void event_export_transport_file_deinit(void)
+{
+       struct exporter_file *node, *next = exporter_file_list_head;
+       exporter_file_list_head = NULL;
+       while (next != NULL) {
+               node = next;
+               next = node->next;
+               exporter_file_destroy(&node);
+       }
+}
+
+static struct exporter_file *exporter_file_init(const struct exporter *exporter)
+{
+       struct exporter_file *node;
+       node = i_new(struct exporter_file, 1);
+       node->fname = i_strdup(t_strcut(exporter->transport_args, ' '));
+       node->fd = -1;
+       node->next = exporter_file_list_head;
+       exporter_file_list_head = node;
+       event_export_transport_assign_context(exporter, node);
+       return node;
+}
+
+static void exporter_file_open_error(struct exporter_file *node, const char *func)
+{
+       if (errno != EACCES)
+               i_error("%s(%s) failed: %m", func, node->fname);
+       else
+               i_error("%s", eacces_error_get_creating(func, node->fname));
+       node->last_error = ioloop_time;
+}
+
+static bool exporter_file_open(struct exporter_file *node)
+{
+       if (likely(node->output != NULL && !node->output->closed))
+               return TRUE;
+       o_stream_destroy(&node->output);
+       i_close_fd(&node->fd);
+       node->fd = open(node->fname, O_CREAT|O_APPEND|O_WRONLY, 0600);
+       if (node->fd == -1) {
+               if (ioloop_time - node->last_error > EXPORTER_LAST_ERROR_DELAY)
+                       exporter_file_open_error(node, "open");
+               return FALSE;
+       }
+       node->output = o_stream_create_fd_file(node->fd, UOFF_T_MAX, FALSE);
+       o_stream_set_name(node->output, node->fname);
+       return TRUE;
+}
+
+void event_export_transport_file(const struct exporter *exporter,
+                                const buffer_t *buf)
+{
+       struct exporter_file *node = exporter->transport_context;
+       if (node == NULL)
+               node = exporter_file_init(exporter);
+       if (!exporter_file_open(node))
+               return;
+       const struct const_iovec vec[] = {
+               { .iov_base = buf->data, .iov_len = buf->used },
+               { .iov_base = "\n", .iov_len = 1 }
+       };
+       if (o_stream_sendv(node->output, vec, N_ELEMENTS(vec)) < 0) {
+               if (ioloop_time - node->last_error > EXPORTER_LAST_ERROR_DELAY) {
+                       i_error("write(%s): %s", o_stream_get_name(node->output),
+                               o_stream_get_error(node->output));
+                       node->last_error = ioloop_time;
+               }
+               o_stream_close(node->output);
+       }
+}
index 2d6c94f74665e94beea840d6a3efe2d3bfef4e55..8fdfeb85a5debe86dbcc16b65bb4160be68c4e53 100644 (file)
@@ -13,6 +13,8 @@ void event_export_transport_drop(const struct exporter *exporter, const buffer_t
 void event_export_transport_http_post(const struct exporter *exporter, const buffer_t *buf);
 void event_export_transport_http_post_deinit(void);
 void event_export_transport_log(const struct exporter *exporter, const buffer_t *buf);
+void event_export_transport_file(const struct exporter *exporter, const buffer_t *buf);
+void event_export_transport_file_deinit(void);
 
 /* append a microsecond resolution RFC3339 UTC timestamp */
 void event_export_helper_fmt_rfc3339_time(string_t *dest, const struct timeval *time);
index 4fd2f3a1bbd6628900ae49711a95589f79c4b64f..ff5bce6643638bf99a9b066195f1103b6403b45b 100644 (file)
@@ -72,6 +72,8 @@ static void stats_exporters_add_set(struct stats_metrics *metrics,
                exporter->transport = event_export_transport_log;
                exporter->format_max_field_len =
                        LOG_EXPORTER_LONG_FIELD_TRUNCATE_LEN;
+       } else if (strcmp(set->transport, "file") == 0) {
+               exporter->transport = event_export_transport_file;
        } else {
                i_unreached();
        }
@@ -317,6 +319,7 @@ static void stats_export_deinit(void)
        /* no need for event_export_transport_drop_deinit() - no-op */
        event_export_transport_http_post_deinit();
        /* no need for event_export_transport_log_deinit() - no-op */
+       event_export_transport_file_deinit();
 }
 
 void stats_metrics_deinit(struct stats_metrics **_metrics)
index a05aaa314659c80f26e66830a97c1ef8ec6715cc..276212f84db18ab86363a2610e1c528d40af5fc9 100644 (file)
@@ -288,7 +288,8 @@ static bool stats_exporter_settings_check(void *_set, pool_t pool ATTR_UNUSED,
                return FALSE;
        } else if (strcmp(set->transport, "drop") == 0 ||
                   strcmp(set->transport, "http-post") == 0 ||
-                  strcmp(set->transport, "log") == 0) {
+                  strcmp(set->transport, "log") == 0 ||
+                  strcmp(set->transport, "file") == 0) {
                /* no-op */
        } else {
                *error_r = t_strdup_printf("Unknown transport type '%s'",