Skip to content

feat(logging): POC of logger rate limiting - #6813

Draft
dehaansa wants to merge 20 commits into
mainfrom
worktree-logger-rate-limiting-sampling
Draft

feat(logging): POC of logger rate limiting#6813
dehaansa wants to merge 20 commits into
mainfrom
worktree-logger-rate-limiting-sampling

Conversation

@dehaansa

@dehaansa dehaansa commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Brief description of Pull Request

Proof-of-concept adding log rate limiting to Alloy's logger, backed by github.com/samber/slog-sampling (Threshold sampler: admit the first N identical lines per tick, then drop the rest). Logs are evaluated for uniqueness by component, message, and level.

  • New rate_limiting block in the logging config block: enabled, tick, threshold, rate, max_signatures. Defaults: enabled=true, tick="10s", threshold=10, rate=0, max_signatures=1000 (LRU-bounded). Omitting the block keeps limiting enabled with defaults; enabled=false disables it.
  • Keys on component identity + log level + message text. Identical repeated lines from the same component are thinned per tick; distinct components/messages are independent. Empty-message records (e.g. keyless go-kit logs) are not rate limited.
  • Dropped lines are counted by a new alloy_logging_suppressed_lines_total{level, component_id} metric; the first admitted line of each window carries a slog_sampling.dropped_count attribute.
  • Adds dependencies: samber/slog-sampling, samber/slog-multi, samber/slog-common, bluele/gcache, cornelk/hashmap.

Note: this is on by default at the moment.

Pull Request Details

The Alloy team's seen a few different situations where Alloy repeated logging of authentication errors or errors triggered by clock skew cause significant disk usage, as well as reduce the visibility of other errors due to the noise. While log rotation is one part of the solution, causing the logger to deduplicate repeated log messages is also an important aspect. This POC implements rate limiting for deduplication.

PR Checklist

  • Documentation added
  • Tests updated
  • Config converters updated
  • This pull request was substantially generated with AI assistance (see the GenAI policy)

dehaansa and others added 19 commits July 29, 2026 15:01
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…applying Update; document empty-message bypass
Comment-only pass over the v2 logger rate-limiting feature code
(sampling.go and the branch-added comments in logger.go,
deferred_handler.go, options.go, and their tests), rewriting comments
in Simplified Technical English while preserving the essential
rationale (versioned-cache replay, empty-message bypass, the
ctx==context.Background() fast path, Enabled routing to the bare
handler, and Update's lock ordering / rebuild-on-change logic).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…trics, extract metricsutil, dedupe test, docs

Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…old before tick)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…epeats

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 3, 2026 18:27
| Name | Type | Description | Default | Required |
|------|------|-------------|---------|----------|
| `enabled` | `bool` | Enable per-message rate limiting. | `true` | no |
| `max_signatures` | `number` | Distinct signatures tracked; least-recently-used is evicted when full. | `1000` | no |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Grafana.GoogleLyHyphens (error)

'recently-used' doesn't need a hyphen.

For more information, refer to Google developer documentation style guide.


Reported by Vale using Grafana Writers' Toolkit style. If you believe we can improve the rule, report an issue.

|------|------|-------------|---------|----------|
| `enabled` | `bool` | Enable per-message rate limiting. | `true` | no |
| `max_signatures` | `number` | Distinct signatures tracked; least-recently-used is evicted when full. | `1000` | no |
| `rate` | `number` | Fraction (0–1) of the over-threshold tail still admitted; `0` drops all excess. | `0` | no |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Grafana.GoogleEnDash (error)

Use an em dash ('—') instead of '–'.

For more information, refer to Google developer documentation style guide.


Reported by Vale using Grafana Writers' Toolkit style. If you believe we can improve the rule, report an issue.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

💻 Deploy preview available (feat(logging): POC of logger rate limiting):

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds an on-by-default, per-(component, level, message) log rate-limiter to Alloy’s runtime logger using github.com/samber/slog-sampling, with live-reconfigurable options and a Prometheus metric for suppressed lines.

Changes:

  • Introduces logging.rate_limiting config block + defaults/validation, and wires it through logger updates.
  • Adds a sampling injector handler layer to key and apply per-component sampling without a background goroutine.
  • Adds tests/benchmarks plus documentation and a new alloy_logging_suppressed_lines_total{level,component_id} metric.

Reviewed changes

Copilot reviewed 16 out of 18 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
internal/util/metricsutil/metricsutil.go New leaf helper package for Prometheus registration without internal/util import cycles.
internal/util/metrics.go Removes the moved MustRegisterOrReturnExisting helper from internal/util.
internal/runtime/logging/sampling.go Implements signature matching, sampling injector handler, and suppressed-lines metric integration.
internal/runtime/logging/sampling_test.go Unit tests for matcher/component sniffing and injector behavior.
internal/runtime/logging/rl_bench_test.go Benchmarks for rate limiting overhead (disabled/admit/drop/churn/parallel).
internal/runtime/logging/options.go Adds RateLimitingOptions block, defaults, and validation.
internal/runtime/logging/options_test.go Tests for default rate limiting enablement and validation behavior.
internal/runtime/logging/logger.go Wires rate limiting into Logger lifecycle with atomic root handler swapping and metric init.
internal/runtime/logging/logger_rl_test.go End-to-end tests for sampling, budgets across updates, context variants, metrics init, and concurrency.
internal/runtime/logging/logger_event_log_test.go Disables rate limiting in a delivery-count-sensitive destination flip test.
internal/runtime/logging/deferred_handler.go Routes the root of the deferred handler tree through the persistent sampling injector.
internal/runtime/internal/controller/node_config_logging.go Initializes rate-limit metrics registration when logging config nodes are created.
internal/runtime/internal/controller/loader.go Switches to metricsutil.MustRegisterOrReturnExisting for loader metric registration.
go.mod Adds github.com/samber/slog-sampling (and indirect deps) to the root module.
go.sum Records checksums for newly introduced root-module dependencies.
docs/sources/reference/config-blocks/logging.md Documents the new logging.rate_limiting block and its semantics/metric.
collector/go.mod Updates generated collector module dependencies (appears incidental to this feature).
collector/go.sum Updates generated collector module sums (appears incidental to this feature).

Comment thread internal/runtime/logging/sampling.go
Comment on lines +9 to +12
// MustRegisterOrReturnExisting registers c on reg. If c is already
// registered, for example because multiple callers share one registerer, it
// returns the existing collector instead of panicking.
// If registration fails for any other reason, it panics.
Comment thread collector/go.mod
Comment on lines 352 to 356
github.com/beorn7/perks v1.0.1 // indirect
github.com/bitfield/gotestdox v0.2.2 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/bluele/gcache v0.0.2 // indirect
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
Copilot AI review requested due to automatic review settings August 3, 2026 19:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 16 out of 18 changed files in this pull request and generated no new comments.

Suppressed comments (1)

internal/runtime/logging/logger_rl_test.go:241

  • require.NoError(t, err) is called from a goroutine. require uses t.FailNow, which only stops the current goroutine and can lead to silently passing tests and/or unsafe test behavior. Prefer reporting the error with t.Errorf (or collecting errors and asserting in the main goroutine).
				require.NoError(t, err)

@dehaansa
dehaansa requested a review from Copilot August 3, 2026 19:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 16 out of 18 changed files in this pull request and generated no new comments.

Suppressed comments (2)

collector/go.mod:356

  • collector/go.mod is explicitly marked as code-generated by the OpenTelemetry collector builder (see file header). Dependency changes here (like adding github.com/bluele/gcache) should come from updating collector/builder-config.yaml and regenerating, rather than editing this file directly; otherwise the next regeneration will likely overwrite the change.
	github.com/beorn7/perks v1.0.1 // indirect
	github.com/bitfield/gotestdox v0.2.2 // indirect
	github.com/blang/semver/v4 v4.0.0 // indirect
	github.com/bluele/gcache v0.0.2 // indirect
	github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect

internal/runtime/logging/sampling.go:254

  • samplingInjector.WithAttrs records the incoming attrs slice directly in ops for later replay. Since this handler retains attrs beyond the WithAttrs call, the slice should be copied to avoid aliasing/mutation by the caller leading to incorrect output or data races.
func (s *samplingInjector) WithAttrs(attrs []slog.Attr) slog.Handler {
	ns := s.clone()
	ns.comp = sniffComponent(s.comp, attrs)
	ns.bgCtx = withComponent(context.Background(), ns.comp)
	ns.ops = append(ns.ops, replayOp{attrs: attrs})
	return ns

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants