Skip to content

Commit 6ba8683

Browse files
ArunPiduguDDclaude
andcommitted
refactor(tag_cardinality_limit): simplify per-tag config to LimitOverride/Excluded enum
Replace the PerTagInner struct (with ambiguous optional fields) with a PerTagConfig struct wrapping a PerTagMode tagged enum: - mode: limit_override + value_limit: N — track with explicit cap - mode: excluded — opt out of tracking entirely Removes PerTagInner, PerTagModeKind, and the old `excluded: bool` field. The new shape is unambiguous: every per-tag entry must be one or the other. YAML format is consistent with per-metric `mode: excluded`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 8f6ac35 commit 6ba8683

5 files changed

Lines changed: 112 additions & 132 deletions

File tree

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,6 @@
11
The `tag_cardinality_limit` transform gained two new configuration capabilities:
22

3-
- **Per-tag overrides**: each entry in `per_metric_limits` now supports a `per_tag_limits`
4-
map whose entries can override settings for a specific tag key on that metric. The
5-
following per-tag fields are available:
6-
- `value_limit` (optional): caps distinct values for this tag key. Inherits from the
7-
enclosing per-metric `value_limit` when unset.
8-
- `excluded` (default `false`): when `true`, opts this tag out of cardinality tracking
9-
entirely — all values pass through unchecked.
10-
11-
The tracking mode (`exact`/`probabilistic`), `cache_size_per_key`, `limit_exceeded_action`,
12-
and `internal_metrics` are always inherited from the enclosing per-metric configuration
13-
and cannot be overridden per-tag.
14-
15-
- **Metric exclusion**: `mode: excluded` is now available in `per_metric_limits` entries
16-
(not at the global level). When set, every tag on that metric is opted out of cardinality
17-
control — all tag values pass through and nothing is tracked. Any `per_tag_limits`
18-
overrides on an excluded metric are ignored.
3+
- **Per-tag overrides** (`per_tag_limits`): configure cardinality limits per tag key within a metric, or exclude individual tags from tracking.
4+
- **Metric exclusion**: opt entire metrics out of cardinality tracking via `mode: excluded` in `per_metric_limits`.
195

206
authors: ArunPiduguDD

src/transforms/tag_cardinality_limit/config.rs

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,12 @@ pub struct PerMetricConfig {
129129
#[serde(default)]
130130
pub namespace: Option<String>,
131131

132-
/// Per-tag-key overrides scoped to this metric.
132+
/// Per-tag-key overrides scoped to this metric. Each entry sets a `mode`:
133+
/// - `mode: limit_override` + `value_limit: N` — track with a per-tag cap.
134+
/// - `mode: excluded` — opt this tag out of tracking entirely.
133135
///
134-
/// Each entry may override `value_limit` for a specific tag key or opt it out of
135-
/// tracking entirely with `excluded: true`. The tracking mode, `cache_size_per_key`,
136-
/// `limit_exceeded_action`, and `internal_metrics` are always inherited from the
137-
/// enclosing per-metric configuration and cannot be set per-tag.
136+
/// All other settings (tracking algorithm, `limit_exceeded_action`, etc.)
137+
/// are inherited from the enclosing per-metric configuration.
138138
/// Tags not listed here use the per-metric configuration.
139139
#[configurable(
140140
derived,
@@ -185,12 +185,9 @@ pub enum OverrideMode {
185185
Probabilistic(BloomFilterConfig),
186186

187187
/// Skip cardinality tracking for this metric. All tag values pass through and nothing is
188-
/// recorded. Other tracking fields on the entry (`value_limit`, `limit_exceeded_action`,
188+
/// limited. Other tracking fields on the entry (`value_limit`, `limit_exceeded_action`,
189189
/// `internal_metrics`) are ignored when this is selected. Any `per_tag_limits` entries
190190
/// on this metric are also ignored.
191-
///
192-
/// Only valid in `per_metric_limits` entries; using it as the global `mode` is a
193-
/// configuration error.
194191
Excluded,
195192
}
196193

@@ -209,33 +206,46 @@ impl OverrideMode {
209206
// Per-tag configuration block
210207
// =============================================================================
211208

212-
/// Tag cardinality limit configuration for a specific tag key, scoped under a per-metric override.
209+
/// Per-tag cardinality configuration.
210+
///
211+
/// Specify `mode` to control how this tag is handled:
212+
///
213+
/// Example:
214+
/// ```yaml
215+
/// per_tag_limits:
216+
/// environment:
217+
/// mode: limit_override # track with a per-tag cap
218+
/// value_limit: 3
219+
/// trace_id:
220+
/// mode: excluded # opt out of tracking entirely
221+
/// ```
213222
#[configurable_component]
214223
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
215224
pub struct PerTagConfig {
225+
#[configurable(derived)]
216226
#[serde(flatten)]
217-
pub config: PerTagInner,
227+
pub mode: PerTagMode,
218228
}
219229

220-
/// Configuration block used at the per-tag level.
230+
/// Mode applied to a specific tag key within a per-metric override.
221231
///
222-
/// The tracking mode (`exact` or `probabilistic`) and `cache_size_per_key` are always
223-
/// inherited from the enclosing per-metric configuration and cannot be overridden per-tag.
224-
/// A tag can be opted out of cardinality tracking entirely with `excluded: true`.
232+
/// The tracking algorithm (`exact`/`probabilistic`), `cache_size_per_key`,
233+
/// `limit_exceeded_action`, and `internal_metrics` are always inherited from the
234+
/// enclosing per-metric configuration regardless of the per-tag mode.
225235
#[configurable_component]
226236
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
227-
pub struct PerTagInner {
228-
/// How many distinct values to accept for this tag key. If unset, inherits
229-
/// the `value_limit` from the enclosing per-metric (or global) configuration.
230-
/// Ignored when `excluded: true`.
231-
#[serde(default)]
232-
pub value_limit: Option<usize>,
233-
234-
/// When `true`, opts this tag out of cardinality tracking entirely. All values
235-
/// for this tag pass through without being recorded or checked against
236-
/// `value_limit`. Defaults to `false`.
237-
#[serde(default)]
238-
pub excluded: bool,
237+
#[serde(tag = "mode", rename_all = "snake_case", deny_unknown_fields)]
238+
#[configurable(metadata(docs::enum_tag_description = "Controls how this tag key is handled."))]
239+
pub enum PerTagMode {
240+
/// Track this tag with a per-tag value limit. The enclosing per-metric tracking
241+
/// algorithm and all other settings still apply.
242+
LimitOverride {
243+
/// Maximum number of distinct values to accept for this tag key.
244+
value_limit: usize,
245+
},
246+
/// Opt this tag out of cardinality tracking entirely. All values pass through
247+
/// without being recorded or checked against any `value_limit`.
248+
Excluded,
239249
}
240250

241251
// =============================================================================

src/transforms/tag_cardinality_limit/mod.rs

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ mod tests;
1717

1818
pub use config::{
1919
BloomFilterConfig, Config, Inner, LimitExceededAction, Mode, OverrideInner, OverrideMode,
20-
PerMetricConfig, PerTagConfig, PerTagInner, TrackingScope,
20+
PerMetricConfig, PerTagConfig, PerTagMode, TrackingScope,
2121
};
22+
2223
use tag_value_set::AcceptedTagValueSet;
2324

2425
use crate::event::metric::TagValueSet;
@@ -84,15 +85,14 @@ impl TagCardinalityLimit {
8485

8586
/// Resolve the configuration that applies to a specific (metric, tag) pair.
8687
///
87-
/// Lookup chain (per field):
88-
/// - `value_limit`: per-tag override → per-metric → global.
89-
/// - `mode`, `cache_size_per_key`, `limit_exceeded_action`, `internal_metrics`:
90-
/// per-metric → global. These fields cannot be overridden per-tag.
88+
/// Per-tag entries support two modes:
89+
/// - `mode: limit_override` — uses the per-tag `value_limit`; all other settings
90+
/// (`mode`, `cache_size_per_key`, `limit_exceeded_action`, `internal_metrics`)
91+
/// are inherited from the per-metric config.
92+
/// - `mode: excluded` — opts the tag out entirely; all values pass through.
9193
///
92-
/// Per-tag exclusion: if the per-tag entry has `excluded: true`, the tag is opted out
93-
/// regardless of the enclosing per-metric settings.
94-
/// Per-metric exclusion is blanket: if the per-metric entry has `mode: excluded`,
95-
/// every tag on the metric is excluded and `per_tag_limits` is ignored.
94+
/// Per-metric exclusion is blanket: `mode: excluded` on a per-metric entry opts out
95+
/// every tag on that metric and `per_tag_limits` is ignored.
9696
fn get_config_for_metric_tag(
9797
&self,
9898
metric_key: Option<&MetricId>,
@@ -116,20 +116,22 @@ impl TagCardinalityLimit {
116116
let metric_value_limit = per_metric.config.value_limit;
117117
let internal_metrics = per_metric.config.internal_metrics;
118118

119-
// Per-tag entry may exclude the tag or override `value_limit`.
120-
// All other fields (mode, cache_size_per_key, etc.) are always inherited
121-
// from the per-metric configuration.
119+
// Per-tag entry: LimitOverride uses an explicit value_limit; Excluded opts
120+
// the tag out. All other settings are always inherited from per-metric.
122121
if let Some(per_tag) = per_metric.per_tag_limits.get(tag_key) {
123-
if per_tag.config.excluded {
124-
return TagSettings::Excluded;
122+
match per_tag.mode {
123+
PerTagMode::Excluded => return TagSettings::Excluded,
124+
PerTagMode::LimitOverride { value_limit } => {
125+
// Tracking algorithm and all other settings are always inherited
126+
// from the per-metric config.
127+
return TagSettings::Tracked(Inner {
128+
value_limit,
129+
limit_exceeded_action,
130+
mode: metric_mode,
131+
internal_metrics,
132+
});
133+
}
125134
}
126-
// Mode and cache_size_per_key are always inherited from the per-metric config.
127-
return TagSettings::Tracked(Inner {
128-
value_limit: per_tag.config.value_limit.unwrap_or(metric_value_limit),
129-
limit_exceeded_action,
130-
mode: metric_mode,
131-
internal_metrics,
132-
});
133135
}
134136
TagSettings::Tracked(Inner {
135137
value_limit: metric_value_limit,
@@ -157,7 +159,7 @@ impl TagCardinalityLimit {
157159
///
158160
/// Returns:
159161
/// - `Accepted` if the value is already tracked, fits under the configured
160-
/// `value_limit` and is now recorded, or the tag has `excluded: true`
162+
/// `value_limit` and is now recorded, or the per-tag entry is `mode: excluded`
161163
/// (pass-through).
162164
/// - `Rejected` if the value would exceed `value_limit`; the caller should drop
163165
/// the tag.

src/transforms/tag_cardinality_limit/tests.rs

Lines changed: 24 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -765,10 +765,7 @@ fn max_tracked_keys_caps_across_per_metric_buckets() {
765765

766766
fn make_per_tag(value_limit: usize) -> PerTagConfig {
767767
PerTagConfig {
768-
config: PerTagInner {
769-
value_limit: Some(value_limit),
770-
excluded: false,
771-
},
768+
mode: PerTagMode::LimitOverride { value_limit },
772769
}
773770
}
774771

@@ -804,10 +801,7 @@ fn make_per_metric_excluded(per_tag_limits: HashMap<String, PerTagConfig>) -> Pe
804801

805802
fn make_per_tag_excluded() -> PerTagConfig {
806803
PerTagConfig {
807-
config: PerTagInner {
808-
value_limit: None,
809-
excluded: true,
810-
},
804+
mode: PerTagMode::Excluded,
811805
}
812806
}
813807

@@ -1044,80 +1038,67 @@ fn tag_excluded_unbounded_sibling_limited() {
10441038
}
10451039
}
10461040

1047-
/// A per-tag entry with `value_limit` unset must inherit the per-metric `value_limit`
1048-
/// rather than silently falling back to the serde default of 500.
1041+
/// A per-tag `LimitOverride` entry caps that tag at its explicit `value_limit`,
1042+
/// independent of the per-metric limit.
10491043
#[test]
1050-
fn per_tag_value_limit_inherits_from_per_metric() {
1044+
fn per_tag_limit_override_caps_at_explicit_value() {
10511045
let per_tag = PerTagConfig {
1052-
config: PerTagInner {
1053-
value_limit: None, // unset → should inherit from the per-metric (3)
1054-
excluded: false,
1055-
},
1046+
mode: PerTagMode::LimitOverride { value_limit: 2 },
10561047
};
10571048
let config = make_transform_hashset_with_per_metric_limits(
10581049
500,
10591050
LimitExceededAction::DropTag,
10601051
HashMap::from([(
10611052
"metricA".to_string(),
10621053
make_per_metric(
1063-
3,
1054+
10,
10641055
LimitExceededAction::DropTag,
10651056
HashMap::from([("tag1".to_string(), per_tag)]),
10661057
),
10671058
)]),
10681059
);
10691060
let mut transform = TagCardinalityLimit::new(config);
10701061

1071-
// First 3 distinct values for tag1 are accepted (inherits per-metric limit of 3).
1072-
for i in 0..3 {
1073-
let v = format!("v{i}");
1062+
// First 2 values pass (per-tag limit = 2).
1063+
for v in ["v0", "v1"] {
10741064
let e = transform
1075-
.transform_one(make_metric_with_name(
1076-
metric_tags!("tag1" => v.clone()),
1077-
"metricA",
1078-
))
1065+
.transform_one(make_metric_with_name(metric_tags!("tag1" => v), "metricA"))
10791066
.unwrap();
1080-
assert_eq!(
1081-
v.as_str(),
1082-
e.as_metric().tags().unwrap().get("tag1").unwrap()
1083-
);
1067+
assert_eq!(v, e.as_metric().tags().unwrap().get("tag1").unwrap());
10841068
}
10851069

1086-
// 4th value should be rejected — proves the per-tag entry did NOT silently widen
1087-
// the limit to 500.
1088-
let e4 = transform
1070+
// 3rd value dropped — proves the per-tag limit (2) applies, not the per-metric (10).
1071+
let e3 = transform
10891072
.transform_one(make_metric_with_name(
1090-
metric_tags!("tag1" => "v3"),
1073+
metric_tags!("tag1" => "v2"),
10911074
"metricA",
10921075
))
10931076
.unwrap();
1094-
assert!(!e4.as_metric().tags().unwrap().contains_key("tag1"));
1077+
assert!(!e3.as_metric().tags().unwrap().contains_key("tag1"));
10951078
}
10961079

1097-
/// A per-tag entry like `{ value_limit: 10 }` must deserialize successfully.
1080+
/// Per-tag YAML syntax: `mode: limit_override` with `value_limit`, and `mode: excluded`.
10981081
#[test]
1099-
fn per_tag_value_limit_only_deserializes() {
1082+
fn per_tag_modes_deserialize() {
11001083
let yaml = r#"
11011084
value_limit: 5
11021085
mode: exact
11031086
per_metric_limits:
11041087
metric_a:
11051088
mode: exact
11061089
per_tag_limits:
1107-
tag_only_value_limit:
1090+
capped_tag:
1091+
mode: limit_override
11081092
value_limit: 10
11091093
excluded_tag:
1110-
excluded: true
1094+
mode: excluded
11111095
"#;
11121096
let parsed: Config = serde_yaml::from_str(yaml).expect("yaml should deserialize");
11131097
let per_metric = parsed.per_metric_limits.get("metric_a").unwrap();
1114-
let only_limit = per_metric
1115-
.per_tag_limits
1116-
.get("tag_only_value_limit")
1117-
.unwrap();
1118-
assert_eq!(only_limit.config.value_limit, Some(10));
1119-
assert!(!only_limit.config.excluded);
1098+
1099+
let capped = per_metric.per_tag_limits.get("capped_tag").unwrap();
1100+
assert_eq!(capped.mode, PerTagMode::LimitOverride { value_limit: 10 });
11201101

11211102
let excluded = per_metric.per_tag_limits.get("excluded_tag").unwrap();
1122-
assert!(excluded.config.excluded);
1103+
assert_eq!(excluded.mode, PerTagMode::Excluded);
11231104
}

website/cue/reference/components/transforms/generated/tag_cardinality_limit.cue

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,10 @@ generated: components: transforms: tag_cardinality_limit: configuration: {
128128
type: string: enum: {
129129
exact: "Tracks cardinality exactly. See `Mode::Exact` for details."
130130
excluded: """
131-
Skip cardinality tracking for this scope. All tag values pass through and nothing is
132-
recorded. Other tracking fields on the entry (`value_limit`, `limit_exceeded_action`,
133-
`internal_metrics`) are ignored when this is selected.
134-
135-
Only valid in `per_metric_limits` and `per_tag_limits` entries; using it as the global
136-
`mode` is a configuration error.
131+
Skip cardinality tracking for this metric. All tag values pass through and nothing is
132+
limited. Other tracking fields on the entry (`value_limit`, `limit_exceeded_action`,
133+
`internal_metrics`) are ignored when this is selected. Any `per_tag_limits` entries
134+
on this metric are also ignored.
137135
"""
138136
probabilistic: "Tracks cardinality probabilistically. See `Mode::Probabilistic` for details."
139137
}
@@ -145,34 +143,37 @@ generated: components: transforms: tag_cardinality_limit: configuration: {
145143
}
146144
per_tag_limits: {
147145
description: """
148-
Per-tag-key overrides scoped to this metric.
146+
Per-tag-key overrides scoped to this metric. Each entry sets a `mode`:
147+
- `mode: limit_override` + `value_limit: N` — track with a per-tag cap.
148+
- `mode: excluded` — opt this tag out of tracking entirely.
149149
150-
Each entry may override `value_limit` and `mode` for a specific tag key.
151-
`limit_exceeded_action` and `internal_metrics` are always inherited from the enclosing
152-
per-metric (or global) configuration and cannot be set per-tag.
150+
All other settings (tracking algorithm, `limit_exceeded_action`, etc.)
151+
are inherited from the enclosing per-metric configuration.
153152
Tags not listed here use the per-metric configuration.
154153
"""
155154
required: false
156155
type: object: options: "*": {
157156
description: "An individual tag configuration."
158157
required: true
159158
type: object: options: {
160-
excluded: {
161-
description: """
162-
When `true`, opts this tag out of cardinality tracking entirely. All values
163-
for this tag pass through without being recorded or checked against
164-
`value_limit`. Defaults to `false`.
165-
"""
166-
required: false
167-
type: bool: default: false
159+
mode: {
160+
description: "Controls how this tag key is handled."
161+
required: true
162+
type: string: enum: {
163+
excluded: """
164+
Opt this tag out of cardinality tracking entirely. All values pass through
165+
without being recorded or checked against any `value_limit`.
166+
"""
167+
limit_override: """
168+
Track this tag with a per-tag value limit. The enclosing per-metric tracking
169+
algorithm and all other settings still apply.
170+
"""
171+
}
168172
}
169173
value_limit: {
170-
description: """
171-
How many distinct values to accept for this tag key. If unset, inherits
172-
the `value_limit` from the enclosing per-metric (or global) configuration.
173-
Ignored when `excluded: true`.
174-
"""
175-
required: false
174+
description: "Maximum number of distinct values to accept for this tag key."
175+
relevant_when: "mode = \"limit_override\""
176+
required: true
176177
type: uint: {}
177178
}
178179
}

0 commit comments

Comments
 (0)