Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 42 additions & 9 deletions plugins/filter_log_to_metrics/log_to_metrics.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -912,8 +914,39 @@ 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
rval = flb_ra_get_value_object(ctx->value_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);
Comment on lines +931 to +932
Copy link

@coderabbitai coderabbitai bot Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Check sscanf return value to avoid undefined behavior.

Line 938 uses sscanf but ignores its return value. If the string is not a valid number, counter_value remains uninitialized or retains a stale value, leading to incorrect metrics.

Apply this diff:

                     if (rval->type == FLB_RA_STRING) {
-                        sscanf(rval->val.string, "%lf", &counter_value);
+                        if (sscanf(rval->val.string, "%lf", &counter_value) != 1) {
+                            flb_plg_error(f_ins, "cannot parse value_field as numeric");
+                            flb_ra_key_value_destroy(rval);
+                            break;
+                        }
                     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (rval->type == FLB_RA_STRING) {
sscanf(rval->val.string, "%lf", &counter_value);
if (rval->type == FLB_RA_STRING) {
if (sscanf(rval->val.string, "%lf", &counter_value) != 1) {
flb_plg_error(f_ins, "cannot parse value_field as numeric");
flb_ra_key_value_destroy(rval);
break;
}
}
🤖 Prompt for AI Agents
In plugins/filter_log_to_metrics/log_to_metrics.c around lines 937-938, the
sscanf call ignores its return value which can leave counter_value uninitialized
if parsing fails; replace or guard the sscanf usage by checking its return
(expecting 1) and only assign counter_value on success, otherwise handle the
error path (e.g., skip processing this metric, set a safe default, and/or log an
error); alternatively use strtod with an endptr to robustly detect invalid input
and validate endptr before using counter_value.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks coderabbitai for the suggestion but if you check the existing uses of sscanf in the file they also don't check the return value as you suggested.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@databus23 Thanks — you’re right that the other sscanf sites follow the same pattern. To keep things consistent and safer across all modes, here’s a minimal patch that guards all three sscanf calls. On parse failure we log and skip the emission, avoiding a silent add of 0.

diff --git a/plugins/filter_log_to_metrics/log_to_metrics.c b/plugins/filter_log_to_metrics/log_to_metrics.c
--- a/plugins/filter_log_to_metrics/log_to_metrics.c
+++ b/plugins/filter_log_to_metrics/log_to_metrics.c
@@
-                    if (rval->type == FLB_RA_STRING) {
-                        sscanf(rval->val.string, "%lf", &counter_value);
-                    }
+                    if (rval->type == FLB_RA_STRING) {
+                        if (sscanf(rval->val.string, "%lf", &counter_value) != 1) {
+                            flb_plg_error(f_ins, "cannot parse value_field as numeric");
+                            flb_ra_key_value_destroy(rval);
+                            rval = NULL;
+                            break;
+                        }
+                    }
@@
-                    if (rval->type == FLB_RA_STRING) {
-                        sscanf(rval->val.string, "%lf", &gauge_value);
-                    }
+                    if (rval->type == FLB_RA_STRING) {
+                        if (sscanf(rval->val.string, "%lf", &gauge_value) != 1) {
+                            flb_plg_error(f_ins, "cannot parse value_field as numeric");
+                            flb_ra_key_value_destroy(rval);
+                            rval = NULL;
+                            break;
+                        }
+                    }
@@
-                    if (rval->type == FLB_RA_STRING) {
-                        sscanf(rval->val.string, "%lf", &histogram_value);
-                    }
+                    if (rval->type == FLB_RA_STRING) {
+                        if (sscanf(rval->val.string, "%lf", &histogram_value) != 1) {
+                            flb_plg_error(f_ins, "cannot parse value_field as numeric");
+                            flb_ra_key_value_destroy(rval);
+                            rval = NULL;
+                            break;
+                        }
+                    }

If you’d prefer to align with how buckets are parsed, we can switch these three sites to a strtod-based helper (or reuse convert_double) instead; happy to provide that variant too.

}
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");
flb_ra_key_value_destroy(rval);
break;
}
ret = cmt_counter_add(ctx->c, ts, counter_value,
label_count, label_values);
flb_ra_key_value_destroy(rval);
rval = NULL;
break;

case FLB_LOG_TO_METRICS_GAUGE:
Expand Down Expand Up @@ -1057,7 +1090,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",
Expand Down
70 changes: 70 additions & 0 deletions tests/runtime/filter_log_to_metrics.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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;
Expand Down
Loading