@@ -11,19 +11,9 @@ use crate::{
1111 transforms:: { Transform , tag_cardinality_limit:: TagCardinalityLimit } ,
1212} ;
1313
14- /// Configuration of internal metrics for the TagCardinalityLimit transform.
15- #[ configurable_component]
16- #[ derive( Clone , Copy , Debug , Default , Eq , PartialEq ) ]
17- #[ serde( deny_unknown_fields) ]
18- pub struct InternalMetricsConfig {
19- /// Whether to include extended tags (metric_name, tag_key) in the `tag_value_limit_exceeded_total` metric.
20- ///
21- /// This helps identify which metrics and tag keys are hitting cardinality limits, but can significantly
22- /// increase metric cardinality. Defaults to `false` because these tags have potentially unbounded cardinality.
23- #[ serde( default = "default_include_extended_tags" ) ]
24- #[ configurable( metadata( docs:: human_name = "Include Extended Tags" ) ) ]
25- pub include_extended_tags : bool ,
26- }
14+ // =============================================================================
15+ // Top-level configuration
16+ // =============================================================================
2717
2818/// Configuration for the `tag_cardinality_limit` transform.
2919#[ configurable_component( transform(
@@ -79,7 +69,11 @@ pub enum TrackingScope {
7969 PerMetric ,
8070}
8171
82- /// Configuration for the `tag_cardinality_limit` transform for a specific group of metrics.
72+ // =============================================================================
73+ // Global-level configuration block
74+ // =============================================================================
75+
76+ /// Configuration block used at the global level.
8377#[ configurable_component]
8478#[ derive( Clone , Copy , Debug , Eq , PartialEq ) ]
8579pub struct Inner {
@@ -99,7 +93,7 @@ pub struct Inner {
9993 pub internal_metrics : InternalMetricsConfig ,
10094}
10195
102- /// Controls the approach taken for tracking tag cardinality.
96+ /// Controls the approach taken for tracking tag cardinality at the global level .
10397#[ configurable_component]
10498#[ derive( Clone , Copy , Debug , Eq , PartialEq ) ]
10599#[ serde( tag = "mode" , rename_all = "snake_case" , deny_unknown_fields) ]
@@ -122,19 +116,131 @@ pub enum Mode {
122116 Probabilistic ( BloomFilterConfig ) ,
123117}
124118
125- /// Bloom filter configuration in probabilistic mode.
119+ // =============================================================================
120+ // Per-metric configuration block
121+ // =============================================================================
122+
123+ /// Tag cardinality limit configuration per metric name.
124+ #[ configurable_component]
125+ #[ derive( Clone , Debug , Eq , PartialEq ) ]
126+ pub struct PerMetricConfig {
127+ /// Namespace of the metric this configuration refers to.
128+ #[ serde( default ) ]
129+ pub namespace : Option < String > ,
130+
131+ /// Per-tag-key overrides scoped to this metric.
132+ ///
133+ /// Each entry may override `value_limit` for a specific tag key or opt it out of
134+ /// tracking entirely with `excluded: true`. The tracking mode, `cache_size_per_key`,
135+ /// `limit_exceeded_action`, and `internal_metrics` are always inherited from the
136+ /// enclosing per-metric configuration and cannot be set per-tag.
137+ /// Tags not listed here use the per-metric configuration.
138+ #[ configurable(
139+ derived,
140+ metadata( docs:: additional_props_description = "An individual tag configuration." )
141+ ) ]
142+ #[ serde( default ) ]
143+ pub per_tag_limits : HashMap < String , PerTagConfig > ,
144+
145+ #[ serde( flatten) ]
146+ pub config : OverrideInner ,
147+ }
148+
149+ /// Configuration block used at per-metric level. Same shape as the global configuration but
150+ /// with `OverrideMode`, which adds `excluded` for opting that metric out of cardinality
151+ /// control entirely.
126152#[ configurable_component]
127153#[ derive( Clone , Copy , Debug , Eq , PartialEq ) ]
128- pub struct BloomFilterConfig {
129- /// The size of the cache for detecting duplicate tags, in bytes.
154+ pub struct OverrideInner {
155+ /// How many distinct values to accept for any given key. Ignored when `mode: excluded`.
156+ #[ serde( default = "default_value_limit" ) ]
157+ pub value_limit : usize ,
158+
159+ #[ configurable( derived) ]
160+ #[ serde( default = "default_limit_exceeded_action" ) ]
161+ pub limit_exceeded_action : LimitExceededAction ,
162+
163+ #[ serde( flatten) ]
164+ pub mode : OverrideMode ,
165+
166+ #[ configurable( derived) ]
167+ #[ serde( default ) ]
168+ pub internal_metrics : InternalMetricsConfig ,
169+ }
170+
171+ /// Controls the approach taken for tracking tag cardinality at the per-metric level.
172+ /// Adds `excluded` to the global `Mode` variants to allow opting a metric out entirely.
173+ #[ configurable_component]
174+ #[ derive( Clone , Copy , Debug , Eq , PartialEq ) ]
175+ #[ serde( tag = "mode" , rename_all = "snake_case" , deny_unknown_fields) ]
176+ #[ configurable( metadata(
177+ docs:: enum_tag_description = "Controls the approach taken for tracking tag cardinality."
178+ ) ) ]
179+ pub enum OverrideMode {
180+ /// Tracks cardinality exactly. See `Mode::Exact` for details.
181+ Exact ,
182+
183+ /// Tracks cardinality probabilistically. See `Mode::Probabilistic` for details.
184+ Probabilistic ( BloomFilterConfig ) ,
185+
186+ /// Skip cardinality tracking for this metric. All tag values pass through and nothing is
187+ /// recorded. Other tracking fields on the entry (`value_limit`, `limit_exceeded_action`,
188+ /// `internal_metrics`) are ignored when this is selected. Any `per_tag_limits` entries
189+ /// on this metric are also ignored.
130190 ///
131- /// The larger the cache size, the less likely it is to have a false positive, or a case where
132- /// we allow a new value for tag even after we have reached the configured limits.
133- #[ serde( default = "default_cache_size" ) ]
134- #[ configurable( metadata( docs:: human_name = "Cache Size per Key" ) ) ]
135- pub cache_size_per_key : usize ,
191+ /// Only valid in `per_metric_limits` entries; using it as the global `mode` is a
192+ /// configuration error.
193+ Excluded ,
194+ }
195+
196+ impl OverrideMode {
197+ /// Returns the equivalent global `Mode` if this scope is tracked, or `None` if excluded.
198+ pub const fn as_mode ( & self ) -> Option < Mode > {
199+ match self {
200+ OverrideMode :: Exact => Some ( Mode :: Exact ) ,
201+ OverrideMode :: Probabilistic ( b) => Some ( Mode :: Probabilistic ( * b) ) ,
202+ OverrideMode :: Excluded => None ,
203+ }
204+ }
205+ }
206+
207+ // =============================================================================
208+ // Per-tag configuration block
209+ // =============================================================================
210+
211+ /// Tag cardinality limit configuration for a specific tag key, scoped under a per-metric override.
212+ #[ configurable_component]
213+ #[ derive( Clone , Copy , Debug , Eq , PartialEq ) ]
214+ pub struct PerTagConfig {
215+ #[ serde( flatten) ]
216+ pub config : PerTagInner ,
217+ }
218+
219+ /// Configuration block used at the per-tag level.
220+ ///
221+ /// The tracking mode (`exact` or `probabilistic`) and `cache_size_per_key` are always
222+ /// inherited from the enclosing per-metric configuration and cannot be overridden per-tag.
223+ /// A tag can be opted out of cardinality tracking entirely with `excluded: true`.
224+ #[ configurable_component]
225+ #[ derive( Clone , Copy , Debug , Eq , PartialEq ) ]
226+ pub struct PerTagInner {
227+ /// How many distinct values to accept for this tag key. If unset, inherits
228+ /// the `value_limit` from the enclosing per-metric (or global) configuration.
229+ /// Ignored when `excluded: true`.
230+ #[ serde( default ) ]
231+ pub value_limit : Option < usize > ,
232+
233+ /// When `true`, opts this tag out of cardinality tracking entirely. All values
234+ /// for this tag pass through without being recorded or checked against
235+ /// `value_limit`. Defaults to `false`.
236+ #[ serde( default ) ]
237+ pub excluded : bool ,
136238}
137239
240+ // =============================================================================
241+ // Shared building blocks
242+ // =============================================================================
243+
138244/// Possible actions to take when an event arrives that would exceed the cardinality limit for one
139245/// or more of its tags.
140246#[ configurable_component]
@@ -148,26 +254,45 @@ pub enum LimitExceededAction {
148254 DropEvent ,
149255}
150256
151- /// Tag cardinality limit configuration per metric name .
257+ /// Bloom filter configuration in probabilistic mode .
152258#[ configurable_component]
153- #[ derive( Clone , Debug , Eq , PartialEq ) ]
154- pub struct PerMetricConfig {
155- /// Namespace of the metric this configuration refers to.
156- #[ serde( default ) ]
157- pub namespace : Option < String > ,
158-
159- #[ serde( flatten) ]
160- pub config : Inner ,
259+ #[ derive( Clone , Copy , Debug , Eq , PartialEq ) ]
260+ pub struct BloomFilterConfig {
261+ /// The size of the cache for detecting duplicate tags, in bytes.
262+ ///
263+ /// The larger the cache size, the less likely it is to have a false positive, or a case where
264+ /// we allow a new value for tag even after we have reached the configured limits.
265+ #[ serde( default = "default_cache_size" ) ]
266+ #[ configurable( metadata( docs:: human_name = "Cache Size per Key" ) ) ]
267+ pub cache_size_per_key : usize ,
161268}
162269
163- const fn default_limit_exceeded_action ( ) -> LimitExceededAction {
164- LimitExceededAction :: DropTag
270+ /// Configuration of internal metrics for the TagCardinalityLimit transform.
271+ #[ configurable_component]
272+ #[ derive( Clone , Copy , Debug , Default , Eq , PartialEq ) ]
273+ #[ serde( deny_unknown_fields) ]
274+ pub struct InternalMetricsConfig {
275+ /// Whether to include extended tags (metric_name, tag_key) in the `tag_value_limit_exceeded_total` metric.
276+ ///
277+ /// This helps identify which metrics and tag keys are hitting cardinality limits, but can significantly
278+ /// increase metric cardinality. Defaults to `false` because these tags have potentially unbounded cardinality.
279+ #[ serde( default = "default_include_extended_tags" ) ]
280+ #[ configurable( metadata( docs:: human_name = "Include Extended Tags" ) ) ]
281+ pub include_extended_tags : bool ,
165282}
166283
284+ // =============================================================================
285+ // Defaults
286+ // =============================================================================
287+
167288const fn default_value_limit ( ) -> usize {
168289 500
169290}
170291
292+ const fn default_limit_exceeded_action ( ) -> LimitExceededAction {
293+ LimitExceededAction :: DropTag
294+ }
295+
171296const fn default_include_extended_tags ( ) -> bool {
172297 false
173298}
@@ -176,6 +301,10 @@ pub(crate) const fn default_cache_size() -> usize {
176301 5 * 1024 // 5KB
177302}
178303
304+ // =============================================================================
305+ // Transform plumbing
306+ // =============================================================================
307+
179308impl GenerateConfig for Config {
180309 fn generate_config ( ) -> toml:: Value {
181310 toml:: Value :: try_from ( Self {
0 commit comments