From 5c28512b862c88c4df47fdf1e4440bfc0f97c344 Mon Sep 17 00:00:00 2001 From: Fabian Ruff Date: Fri, 7 Mar 2025 00:01:36 +0100 Subject: [PATCH 1/2] logs_to_metrics: Support optional value_field for counters Support incrementing counters by a specified value. If a `value_field` is specified for a counter, the counter is incremented by the given value instead of just counting the number of records. This allows tracking different metrics, such as the total number of bytes sent or received in an access log. Signed-off-by: Fabian Ruff --- .../filter_log_to_metrics/log_to_metrics.c | 62 +++++++++++++--- tests/runtime/filter_log_to_metrics.c | 70 +++++++++++++++++++ 2 files changed, 123 insertions(+), 9 deletions(-) diff --git a/plugins/filter_log_to_metrics/log_to_metrics.c b/plugins/filter_log_to_metrics/log_to_metrics.c index 5259129467e..cc121312921 100644 --- a/plugins/filter_log_to_metrics/log_to_metrics.c +++ b/plugins/filter_log_to_metrics/log_to_metrics.c @@ -643,13 +643,14 @@ static int cb_log_to_metrics_init(struct flb_filter_instance *f_ins, snprintf(metric_description, sizeof(metric_description) - 1, "%s", ctx->metric_description); - /* Value field only needed for modes gauge and histogram */ - if (ctx->mode > 0) { - if (ctx->value_field == NULL || strlen(ctx->value_field) == 0) { - flb_plg_error(f_ins, "value_field is not set"); - log_to_metrics_destroy(ctx); - return -1; + if (ctx->value_field == NULL || strlen(ctx->value_field) == 0) { + /* require value field for modes gauge and histogram */ + if (ctx->mode > 0) { + flb_plg_error(f_ins, "value_field is not set"); + log_to_metrics_destroy(ctx); + return -1; } + } else { snprintf(value_field, sizeof(value_field) - 1, "%s", ctx->value_field); @@ -836,6 +837,7 @@ static int cb_log_to_metrics_filter(const void *data, size_t bytes, char **label_values = NULL; int label_count = 0; int i; + double counter_value = 0; double gauge_value = 0; double histogram_value = 0; char kubernetes_label_values @@ -912,8 +914,50 @@ static int cb_log_to_metrics_filter(const void *data, size_t bytes, /* Calculating and setting metric depending on the mode */ switch (ctx->mode) { case FLB_LOG_TO_METRICS_COUNTER: - ret = cmt_counter_inc(ctx->c, ts, label_count, - label_values); + + // If value_field is not set, increment counter by 1 + if (ctx->value_field == NULL || strlen(ctx->value_field) == 0) { + ret = cmt_counter_inc(ctx->c, ts, label_count, + label_values); + break; + } + // If value_field is set, increment counter by value + ra = flb_ra_create(ctx->value_field, FLB_TRUE); + if (!ra) { + flb_plg_error(ctx->ins, "invalid record accessor key, aborting"); + break; + } + + rval = flb_ra_get_value_object(ra, map); + + if (!rval) { + flb_warn("given value field is empty or not existent"); + break; + } + if (rval->type == FLB_RA_STRING) { + sscanf(rval->val.string, "%lf", &counter_value); + } + else if (rval->type == FLB_RA_FLOAT) { + counter_value = rval->val.f64; + } + else if (rval->type == FLB_RA_INT) { + counter_value = (double)rval->val.i64; + } + else { + flb_plg_error(f_ins, + "cannot convert given value to metric"); + break; + } + ret = cmt_counter_add(ctx->c, ts, counter_value, + label_count, label_values); + if (rval) { + flb_ra_key_value_destroy(rval); + rval = NULL; + } + if (ra) { + flb_ra_destroy(ra); + ra = NULL; + } break; case FLB_LOG_TO_METRICS_GAUGE: @@ -1057,7 +1101,7 @@ static struct flb_config_map config_map[] = { { FLB_CONFIG_MAP_STR, "value_field", NULL, 0, FLB_TRUE, offsetof(struct log_to_metrics_ctx, value_field), - "Numeric field to use for gauge or histogram" + "Numeric field to use for gauge, histogram or counter" }, { FLB_CONFIG_MAP_STR, "metric_name", "a", diff --git a/tests/runtime/filter_log_to_metrics.c b/tests/runtime/filter_log_to_metrics.c index a68007b0885..6327605b2ee 100644 --- a/tests/runtime/filter_log_to_metrics.c +++ b/tests/runtime/filter_log_to_metrics.c @@ -57,6 +57,7 @@ /* Test functions */ void flb_test_log_to_metrics_counter_k8s(void); void flb_test_log_to_metrics_counter(void); +void flb_test_log_to_metrics_counter_value_field(void); void flb_test_log_to_metrics_counter_k8s_two_tuples(void); void flb_test_log_to_metrics_gauge(void); void flb_test_log_to_metrics_histogram(void); @@ -118,6 +119,7 @@ void flb_test_log_to_metrics_label(void); TEST_LIST = { {"counter_k8s", flb_test_log_to_metrics_counter_k8s }, {"counter", flb_test_log_to_metrics_counter }, + {"counter_value_field", flb_test_log_to_metrics_counter_value_field }, {"counter_k8s_two_tuples", flb_test_log_to_metrics_counter_k8s_two_tuples }, {"gauge", flb_test_log_to_metrics_gauge }, {"histogram", flb_test_log_to_metrics_histogram }, @@ -321,6 +323,74 @@ void flb_test_log_to_metrics_counter(void) } +void flb_test_log_to_metrics_counter_value_field(void) +{ + int ret; + int i; + flb_ctx_t *ctx; + int in_ffd; + int filter_ffd; + int out_ffd; + char *result = NULL; + struct flb_lib_out_cb cb_data; + char *input = JSON_MSG1; + char finalString[32768] = ""; + const char *expected = "\"value\":100.0,\"labels\":[\"red\",\"right\"]"; + const char *expected2 = "{\"ns\":\"myns\",\"ss\":\"subsystem\"," + "\"name\":\"test\",\"desc\":\"Counts durations\"}"; + + ctx = flb_create(); + flb_service_set(ctx, "Flush", "0.200000000", "Grace", "1", "Log_Level", + "error", NULL); + + cb_data.cb = callback_test; + cb_data.data = NULL; + + in_ffd = flb_input(ctx, (char *) "lib", NULL); + TEST_CHECK(in_ffd >= 0); + flb_input_set(ctx, in_ffd, "tag", "test", NULL); + + filter_ffd = flb_filter(ctx, (char *) "log_to_metrics", NULL); + TEST_CHECK(filter_ffd >= 0); + ret = flb_filter_set(ctx, filter_ffd, + "Match", "*", + "Tag", "test_metric", + "metric_mode", "counter", + "metric_name", "test", + "metric_description", "Counts durations", + "metric_subsystem", "subsystem", + "metric_namespace", "myns", + "kubernetes_mode", "off", + "label_field", "color", + "label_field", "direction", + "value_field", "duration", + NULL); + + out_ffd = flb_output(ctx, (char *) "lib", (void *)&cb_data); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, + "match", "*", + "format", "json", + NULL); + ret = flb_start(ctx); + TEST_CHECK(ret == 0); + + for (i = 0; i < 5; i++){ + flb_lib_push(ctx, in_ffd, input, strlen(input)); + } + wait_with_timeout(2000, finalString); + result = strstr(finalString, expected); + if (!TEST_CHECK(result != NULL)) { + TEST_MSG("expected substring:\n%s\ngot:\n%s\n", expected, finalString); + } + result = strstr(finalString, expected2); + if (!TEST_CHECK(result != NULL)) { + TEST_MSG("expected substring:\n%s\ngot:\n%s\n", expected, finalString); + } + filter_test_destroy(ctx); + +} + void flb_test_log_to_metrics_counter_k8s_two_tuples(void) { int ret; From 5b8e2608e5e8a2c3ca10a9d4b9a7801831923550 Mon Sep 17 00:00:00 2001 From: Fabian Ruff Date: Wed, 22 Oct 2025 23:36:54 +0200 Subject: [PATCH 2/2] Use pre-created ctx->value_ra Incorporate changes that were introduced with https://github.com/fluent/fluent-bit/commit/e6bc6841484b068c517d524481401394f93d7a29 Signed-off-by: Fabian Ruff --- .../filter_log_to_metrics/log_to_metrics.c | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/plugins/filter_log_to_metrics/log_to_metrics.c b/plugins/filter_log_to_metrics/log_to_metrics.c index cc121312921..3fcb9980560 100644 --- a/plugins/filter_log_to_metrics/log_to_metrics.c +++ b/plugins/filter_log_to_metrics/log_to_metrics.c @@ -922,13 +922,7 @@ static int cb_log_to_metrics_filter(const void *data, size_t bytes, break; } // If value_field is set, increment counter by value - ra = flb_ra_create(ctx->value_field, FLB_TRUE); - if (!ra) { - flb_plg_error(ctx->ins, "invalid record accessor key, aborting"); - break; - } - - rval = flb_ra_get_value_object(ra, map); + rval = flb_ra_get_value_object(ctx->value_ra, map); if (!rval) { flb_warn("given value field is empty or not existent"); @@ -946,18 +940,13 @@ static int cb_log_to_metrics_filter(const void *data, size_t bytes, else { flb_plg_error(f_ins, "cannot convert given value to metric"); + flb_ra_key_value_destroy(rval); break; } ret = cmt_counter_add(ctx->c, ts, counter_value, label_count, label_values); - if (rval) { - flb_ra_key_value_destroy(rval); - rval = NULL; - } - if (ra) { - flb_ra_destroy(ra); - ra = NULL; - } + flb_ra_key_value_destroy(rval); + rval = NULL; break; case FLB_LOG_TO_METRICS_GAUGE: