Skip to content

Conversation

@databus23
Copy link

@databus23 databus23 commented Mar 6, 2025

Support incrementing counters by a specified value

If a value_field is specified for a counter metric, the counter is incremented by the given value instead of just incrementing by 1.

This allows counting more things then just the number of records, such as the total number of bytes sent or received from an access log.


Enter [N/A] in the box, if an item is not applicable to your change.

Testing
Before we can approve your change; please submit the following in a comment:

  • Attached Valgrind output that shows no leaks or memory corruption was found

Documentation

  • Documentation required for this feature

Doc PR: fluent/fluent-bit-docs#1584

Backporting

  • Backport to latest stable release.

Fluent Bit is licensed under Apache 2.0, by submitting this pull request I understand that this code will be released under the terms of that license.

Summary by CodeRabbit

  • New Features

    • Counter metrics can now increment by a numeric value extracted from a configured log field (string, float, or int). Default increment-by-1 remains when no field is set.
  • Bug Fixes

    • Improved validation and error handling for the value-field across metric modes; clearer errors when misconfigured and ensured resources are released on failure.
  • Tests

    • Added a runtime test validating counter behavior when using a value field, including numeric conversion and label association.

@databus23 databus23 force-pushed the logs_to_metric_counter_value branch 2 times, most recently from 68cd15e to e725188 Compare March 6, 2025 23:09
@databus23 databus23 changed the title logs_to_metric: Support optional value_field for counters logs_to_metrics: Support optional value_field for counters Mar 6, 2025
@databus23 databus23 force-pushed the logs_to_metric_counter_value branch 2 times, most recently from 2c1822f to db182ed Compare March 9, 2025 00:10
@databus23
Copy link
Author

databus23 commented Mar 10, 2025

Valgrind output for unit tests:

fluent-bit ➜ /workspaces/fluent-bit/build (logs_to_metric_counter_value) $ valgrind bin/flb-rt-filter_log_
to_metrics 
==35868== Memcheck, a memory error detector
==35868== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==35868== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
==35868== Command: bin/flb-rt-filter_log_to_metrics
==35868== 
Test counter_k8s...                             [ OK ]
Test counter...                                 [ OK ]
Test counter_value_field...                     [ OK ]
Test counter_k8s_two_tuples...                  [ OK ]
Test gauge...                                   [ OK ]
Test histogram...                               [ OK ]
Test counter_regex...                           [ OK ]
Test regex_empty_label_keys...                  [ OK ]
Test label...                                   [ OK ]
SUCCESS: All unit tests have passed.
==35868== 
==35868== HEAP SUMMARY:
==35868==     in use at exit: 0 bytes in 0 blocks
==35868==   total heap usage: 22,585 allocs, 22,585 frees, 16,092,238 bytes allocated
==35868== 
==35868== All heap blocks were freed -- no leaks are possible
==35868== 
==35868== For lists of detected and suppressed errors, rerun with: -s
==35868== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

databus23 added a commit to databus23/fluent-bit-docs that referenced this pull request Mar 10, 2025
This documents a feature added with this PR: fluent/fluent-bit#10055

Signed-off-by: Fabian Ruff <[email protected]>
@databus23
Copy link
Author

@edsiper Kindly ping. At least the docs-required label can be removed

@databus23
Copy link
Author

@leonardo-albertovich kindly ping. Any chance to get a response

@databus23
Copy link
Author

@koleini @fujimotos Kindly ping. Any change that is is being looked at? It has tests, docs, all the things. I would love to see some progress on my PR. Thanks

@github-actions
Copy link
Contributor

github-actions bot commented Sep 7, 2025

This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.

@github-actions github-actions bot added the Stale label Sep 7, 2025
@eschabell
Copy link

@patrick-stephens can we get a review here, the docs PR fluent/fluent-bit-docs#1584 is waiting on this code merge?

@patrick-stephens
Copy link
Collaborator

This needs a rebase and @cosmo0920 I think is ok?

@cosmo0920
Copy link
Contributor

This needs a rebase and @cosmo0920 I think is ok?

This needs rebase off master and needs to verify the CI results especially for Linux and Windows unit testing results.

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 <[email protected]>
@databus23 databus23 force-pushed the logs_to_metric_counter_value branch from db182ed to 5c28512 Compare October 22, 2025 10:08
@coderabbitai
Copy link

coderabbitai bot commented Oct 22, 2025

Walkthrough

Initialization now always validates value_field; when set, a record accessor is created and used to extract numeric values (string/float/int → double) for counter increments. Counters fall back to +1 if value_field is absent; accessors are cleaned up and errors/warnings are handled; a runtime test was added.

Changes

Cohort / File(s) Summary
Core Feature Implementation
plugins/filter_log_to_metrics/log_to_metrics.c
value_field validation moved to unconditional init; store/create a record accessor (value_ra) when value_field is configured; counter mode reads value_field when present, converts string/float/int to double and calls cmt_counter_add with that value (falls back to +1 if absent); added error/warning branches and ensured accessor cleanup; updated config doc string.
Test Coverage
tests/runtime/filter_log_to_metrics.c
Added flb_test_log_to_metrics_counter_value_field() and a {"counter_value_field", flb_test_log_to_metrics_counter_value_field} entry in TEST_LIST to verify counters increment using values extracted from a log field and associated labels.

Sequence Diagram(s)

sequenceDiagram
    participant Log as Log Entry
    participant Filter as log_to_metrics filter
    participant RA as Record Accessor
    participant CMT as cmetrics (counter)

    Log->>Filter: deliver record
    alt filter has `value_field`
        Filter->>RA: use `value_ra` to extract field
        RA-->>Filter: value (string/float/int)
        Filter->>Filter: convert → double (or log warning/error)
        Filter->>CMT: cmt_counter_add(metric, double_value)
    else no `value_field`
        Filter->>CMT: cmt_counter_add(metric, 1)
    end
    CMT-->>Filter: metric updated
    Note over Filter,RA: `value_ra` created at init and freed on teardown
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇
I sniff a field, a secret gleam,
I hop and pull a numeric dream,
A gentle convert, a tiny cheer,
Counters climb — one or more this year,
I thump my foot, metrics appear. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly and accurately conveys the primary change, namely adding support for an optional value_field for counter metrics in the log_to_metrics plugin, making it clear to reviewers what core functionality is being modified.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 640d668 and 5b8e260.

📒 Files selected for processing (1)
  • plugins/filter_log_to_metrics/log_to_metrics.c (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • plugins/filter_log_to_metrics/log_to_metrics.c

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@databus23
Copy link
Author

As requested I rebased the branch on master

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
plugins/filter_log_to_metrics/log_to_metrics.c (1)

937-950: Consider extracting type‑conversion logic to reduce duplication.

The same type‑conversion logic for STRING/FLOAT/INT appears in three places: counter (lines 937–950), gauge (lines 970–985), and histogram (lines 1000–1015). Extracting this into a helper function would improve maintainability.

Example helper:

static int extract_numeric_value(struct flb_ra_value *rval, double *out_value, 
                                  struct flb_filter_instance *f_ins)
{
    if (rval->type == FLB_RA_STRING) {
        if (sscanf(rval->val.string, "%lf", out_value) != 1) {
            flb_plg_error(f_ins, "cannot parse value_field as numeric");
            return -1;
        }
    }
    else if (rval->type == FLB_RA_FLOAT) {
        *out_value = rval->val.f64;
    }
    else if (rval->type == FLB_RA_INT) {
        *out_value = (double)rval->val.i64;
    }
    else {
        flb_plg_error(f_ins, "cannot convert given value to metric");
        return -1;
    }
    return 0;
}

Then replace the three duplicated blocks with:

if (extract_numeric_value(rval, &counter_value, f_ins) != 0) {
    flb_ra_key_value_destroy(rval);
    break;
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 65722e8 and 5c28512.

📒 Files selected for processing (2)
  • plugins/filter_log_to_metrics/log_to_metrics.c (4 hunks)
  • tests/runtime/filter_log_to_metrics.c (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
plugins/filter_log_to_metrics/log_to_metrics.c (3)
lib/cmetrics/src/cmt_counter.c (2)
  • cmt_counter_inc (100-117)
  • cmt_counter_add (119-135)
src/flb_record_accessor.c (3)
  • flb_ra_create (271-358)
  • flb_ra_get_value_object (803-814)
  • flb_ra_destroy (232-248)
src/flb_ra_key.c (1)
  • flb_ra_key_value_destroy (842-851)
tests/runtime/filter_log_to_metrics.c (1)
src/flb_lib.c (10)
  • flb_create (138-220)
  • flb_service_set (647-673)
  • flb_input (261-271)
  • flb_input_set (300-330)
  • flb_filter (287-297)
  • flb_filter_set (613-644)
  • flb_output (274-284)
  • flb_output_set (515-546)
  • flb_start (914-925)
  • flb_lib_push (774-801)
🔇 Additional comments (4)
plugins/filter_log_to_metrics/log_to_metrics.c (2)

646-663: LGTM! Validation logic correctly updated.

The refactored validation now permits counters to operate without value_field while still requiring it for gauge and histogram modes. The record accessor is properly initialized and stored in ctx->value_ra for reuse.


1104-1104: LGTM! Documentation correctly updated.

The config map description now accurately reflects that value_field can be used with counter, gauge, or histogram modes.

tests/runtime/filter_log_to_metrics.c (2)

60-60: LGTM! Test properly declared and registered.

The new test function is correctly declared and added to the test list.

Also applies to: 122-122


326-392: LGTM! Comprehensive test for counter with value_field.

The test correctly validates that when value_field is set to "duration", the counter increments by the numeric value from that field (20) rather than by 1. Pushing 5 messages with duration=20 produces an expected counter value of 100.0. The test also validates metadata (namespace, subsystem, name, description) and labels.

Comment on lines +937 to +938
if (rval->type == FLB_RA_STRING) {
sscanf(rval->val.string, "%lf", &counter_value);
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.

Incorporate changes that were introduced with fluent@e6bc684

Signed-off-by: Fabian Ruff <[email protected]>
@databus23 databus23 force-pushed the logs_to_metric_counter_value branch from 640d668 to 5b8e260 Compare October 22, 2025 21:41
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
plugins/filter_log_to_metrics/log_to_metrics.c (1)

918-949: Counter parsing: validate input, enforce monotonicity, and use plugin log macro.

  • Use strtod with endptr and errno; avoid unchecked sscanf.
  • Reject NaN/Inf and negative values (counters must be non-decreasing).
  • Prefer flb_plg_warn(ctx->ins, ...) over flb_warn for consistency.

Apply this diff:

                 case FLB_LOG_TO_METRICS_COUNTER:

                     // 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");
+                        flb_plg_warn(ctx->ins, "value_field empty or not present; skipping counter update");
                         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 {
+                    {
+                        double parsed = 0.0;
+                        int ok = 1;
+
+                        if (rval->type == FLB_RA_STRING) {
+                            errno = 0;
+                            char *endptr = NULL;
+                            parsed = strtod(rval->val.string, &endptr);
+                            if (errno == ERANGE || endptr == rval->val.string || *endptr != '\0') {
+                                ok = 0;
+                            }
+                        }
+                        else if (rval->type == FLB_RA_FLOAT) {
+                            parsed = rval->val.f64;
+                        }
+                        else if (rval->type == FLB_RA_INT) {
+                            parsed = (double) rval->val.i64;
+                        }
+                        else {
+                            ok = 0;
+                        }
+
+                        if (!ok || !isfinite(parsed) || parsed < 0.0) {
+                            flb_plg_error(f_ins, "invalid counter value (non-numeric, NaN/Inf, or negative)");
+                            flb_ra_key_value_destroy(rval);
+                            break;
+                        }
+                        counter_value = parsed;
+                    }
-                    ret = cmt_counter_add(ctx->c, ts, counter_value,
+                    ret = cmt_counter_add(ctx->c, ts, counter_value,
                                         label_count, label_values);
                     flb_ra_key_value_destroy(rval);
                     rval = NULL;
                     break;

Add missing headers:

 #include <stdio.h>
+#include <errno.h>
+#include <math.h>

Optional: declare double counter_value inside this case to reduce scope. See separate note below.

🧹 Nitpick comments (1)
plugins/filter_log_to_metrics/log_to_metrics.c (1)

840-840: Narrow variable scope of counter_value.

Declare double counter_value inside the COUNTER case block where it’s used; reduces lifetime and accidental reuse.

-    double counter_value = 0;

Then inside the FLB_LOG_TO_METRICS_COUNTER case, before parsing:

double counter_value = 0.0;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c28512 and 640d668.

📒 Files selected for processing (1)
  • plugins/filter_log_to_metrics/log_to_metrics.c (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
plugins/filter_log_to_metrics/log_to_metrics.c (3)
lib/cmetrics/src/cmt_counter.c (2)
  • cmt_counter_inc (100-117)
  • cmt_counter_add (119-135)
src/flb_record_accessor.c (1)
  • flb_ra_get_value_object (803-814)
src/flb_ra_key.c (1)
  • flb_ra_key_value_destroy (842-851)
🔇 Additional comments (2)
plugins/filter_log_to_metrics/log_to_metrics.c (2)

1093-1093: Docstring update LGTM.

Config map text now reflects counter support for value_field.


646-653: No change needed for value_field check
The condition ctx->mode > 0 already applies only to GAUGE (1) and HISTOGRAM (2), since COUNTER is defined as 0. Remove the suggested diff for this check.

Likely an incorrect or invalid review comment.

@github-actions github-actions bot removed the Stale label Oct 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants