@@ -16,14 +16,23 @@ mod tag_value_set;
1616mod tests;
1717
1818pub use config:: {
19- BloomFilterConfig , Config , Inner , LimitExceededAction , Mode , PerMetricConfig , TrackingScope ,
19+ BloomFilterConfig , Config , Inner , LimitExceededAction , Mode , OverrideInner , OverrideMode ,
20+ PerMetricConfig , PerTagConfig , PerTagInner , TrackingScope ,
2021} ;
2122use tag_value_set:: AcceptedTagValueSet ;
2223
2324use crate :: event:: metric:: TagValueSet ;
2425
2526type MetricId = ( Option < String > , String ) ;
2627
28+ /// Tag tracking settings for a single (metric, tag) pair.
29+ enum TagSettings {
30+ /// The tag is excluded from cardinality control; pass values through unchanged.
31+ Excluded ,
32+ /// The tag is tracked using these settings.
33+ Tracked ( Inner ) ,
34+ }
35+
2736#[ derive( Debug ) ]
2837pub struct TagCardinalityLimit {
2938 config : Config ,
@@ -38,19 +47,73 @@ impl TagCardinalityLimit {
3847 }
3948 }
4049
41- fn get_config_for_metric ( & self , metric_key : Option < & MetricId > ) -> & Inner {
42- match metric_key {
43- Some ( id) => self
44- . config
45- . per_metric_limits
46- . iter ( )
47- . find ( |( name, config) | {
48- * * name == id. 1 && ( config. namespace . is_none ( ) || config. namespace == id. 0 )
50+ /// Resolve the configuration that applies to a specific (metric, tag) pair.
51+ ///
52+ /// Lookup chain (per field):
53+ /// - `value_limit`, `mode`, `internal_metrics`: per-tag override → per-metric override → global.
54+ /// - `limit_exceeded_action`: per-metric override → global. Per-tag entries always inherit
55+ /// the action from the enclosing per-metric (or global) config.
56+ ///
57+ /// Per-metric exclusion is blanket: if the matching per-metric entry has `mode: excluded`,
58+ /// every tag on the metric is excluded and `per_tag_limits` is ignored.
59+ fn get_config_for_metric_tag (
60+ & self ,
61+ metric_key : Option < & MetricId > ,
62+ tag_key : & str ,
63+ ) -> TagSettings {
64+ // No matching per-metric override → use the global config as-is.
65+ let Some ( ( metric_namespace, metric_name) ) = metric_key else {
66+ return TagSettings :: Tracked ( self . config . global ) ;
67+ } ;
68+ let Some ( ( _, per_metric) ) = self . config . per_metric_limits . iter ( ) . find ( |( name, cfg) | {
69+ * name == metric_name && ( cfg. namespace . is_none ( ) || cfg. namespace == * metric_namespace)
70+ } ) else {
71+ return TagSettings :: Tracked ( self . config . global ) ;
72+ } ;
73+
74+ // Per-metric exclusion is blanket — per-tag overrides do not apply.
75+ let Some ( metric_mode) = per_metric. config . mode . as_mode ( ) else {
76+ return TagSettings :: Excluded ;
77+ } ;
78+ let limit_exceeded_action = per_metric. config . limit_exceeded_action ;
79+
80+ // Per-tag override may further exclude a specific tag, replace `mode`,
81+ // or replace `value_limit` (unset `value_limit` inherits from the enclosing
82+ // per-metric config).
83+ if let Some ( per_tag) = per_metric. per_tag_limits . get ( tag_key) {
84+ let Some ( mode) = per_tag. config . mode . as_mode ( ) else {
85+ return TagSettings :: Excluded ;
86+ } ;
87+ return TagSettings :: Tracked ( Inner {
88+ value_limit : per_tag
89+ . config
90+ . value_limit
91+ . unwrap_or ( per_metric. config . value_limit ) ,
92+ limit_exceeded_action,
93+ mode,
94+ internal_metrics : per_metric. config . internal_metrics ,
95+ } ) ;
96+ }
97+ TagSettings :: Tracked ( Inner {
98+ value_limit : per_metric. config . value_limit ,
99+ limit_exceeded_action,
100+ mode : metric_mode,
101+ internal_metrics : per_metric. config . internal_metrics ,
102+ } )
103+ }
104+
105+ /// Returns the `limit_exceeded_action` that applies to this metric. Decided once per event:
106+ /// per-metric override if any, else global.
107+ fn metric_action ( & self , metric_key : Option < & MetricId > ) -> LimitExceededAction {
108+ if let Some ( id) = metric_key
109+ && let Some ( ( _, pmc) ) =
110+ self . config . per_metric_limits . iter ( ) . find ( |( name, c) | {
111+ * * name == id. 1 && ( c. namespace . is_none ( ) || c. namespace == id. 0 )
49112 } )
50- . map ( |( _, c) | & c. config )
51- . unwrap_or ( & self . config . global ) ,
52- None => & self . config . global ,
113+ {
114+ return pmc. config . limit_exceeded_action ;
53115 }
116+ self . config . global . limit_exceeded_action
54117 }
55118
56119 /// Takes in key and a value corresponding to a tag on an incoming Metric
@@ -67,7 +130,10 @@ impl TagCardinalityLimit {
67130 key : & str ,
68131 value : & TagValueSet ,
69132 ) -> bool {
70- let config = * self . get_config_for_metric ( metric_key) ;
133+ let config = match self . get_config_for_metric_tag ( metric_key, key) {
134+ TagSettings :: Excluded => return true ,
135+ TagSettings :: Tracked ( inner) => inner,
136+ } ;
71137 let metric_accepted_tags = self . accepted_tags . entry ( metric_key. cloned ( ) ) . or_default ( ) ;
72138 let tag_value_set = metric_accepted_tags
73139 . entry_ref ( key)
@@ -102,20 +168,28 @@ impl TagCardinalityLimit {
102168 key : & str ,
103169 value : & TagValueSet ,
104170 ) -> bool {
171+ let resolved = match self . get_config_for_metric_tag ( metric_key, key) {
172+ TagSettings :: Excluded => return false ,
173+ TagSettings :: Tracked ( inner) => inner,
174+ } ;
105175 self . accepted_tags
106176 . get ( & metric_key. cloned ( ) )
107177 . and_then ( |metric_accepted_tags| {
108178 metric_accepted_tags. get ( key) . map ( |value_set| {
109- !value_set. contains ( value)
110- && value_set. len ( ) >= self . get_config_for_metric ( metric_key) . value_limit
179+ !value_set. contains ( value) && value_set. len ( ) >= resolved. value_limit
111180 } )
112181 } )
113182 . unwrap_or ( false )
114183 }
115184
116- /// Record a key and value corresponding to a tag on an incoming Metric.
185+ /// Record an accepted tag value (mutation-only, no limit check). Used by the `DropEvent`
186+ /// path's record pass after a mutation-free pre-check has confirmed every tag has room.
187+ /// Excluded tags are skipped — no storage allocated.
117188 fn record_tag_value ( & mut self , metric_key : Option < & MetricId > , key : & str , value : & TagValueSet ) {
118- let config = * self . get_config_for_metric ( metric_key) ;
189+ let config = match self . get_config_for_metric_tag ( metric_key, key) {
190+ TagSettings :: Excluded => return ,
191+ TagSettings :: Tracked ( inner) => inner,
192+ } ;
119193 let metric_accepted_tags = self . accepted_tags . entry ( metric_key. cloned ( ) ) . or_default ( ) ;
120194 metric_accepted_tags
121195 . entry_ref ( key)
@@ -143,24 +217,24 @@ impl TagCardinalityLimit {
143217 }
144218 } ;
145219 if let Some ( tags_map) = metric. tags_mut ( ) {
146- match self
147- . get_config_for_metric ( metric_key. as_ref ( ) )
148- . limit_exceeded_action
149- {
220+ match self . metric_action ( metric_key. as_ref ( ) ) {
150221 LimitExceededAction :: DropEvent => {
151- // This needs to check all the tags, to ensure that the ordering of tag names
152- // doesn't change the behavior of the check.
153-
222+ // This needs to check all the tags, to ensure that the ordering of tag
223+ // names doesn't change the behavior of the check.
154224 for ( key, value) in tags_map. iter_sets ( ) {
225+ let TagSettings :: Tracked ( resolved) =
226+ self . get_config_for_metric_tag ( metric_key. as_ref ( ) , key)
227+ else {
228+ continue ; // excluded tags can never trigger DropEvent
229+ } ;
155230 if self . tag_limit_exceeded ( metric_key. as_ref ( ) , key, value) {
156- let config = self . get_config_for_metric ( metric_key. as_ref ( ) ) ;
231+ let include_extended_tags =
232+ resolved. internal_metrics . include_extended_tags ;
157233 emit ! ( TagCardinalityLimitRejectingEvent {
158234 metric_name: & metric_name,
159235 tag_key: key,
160236 tag_value: & value. to_string( ) ,
161- include_extended_tags: config
162- . internal_metrics
163- . include_extended_tags,
237+ include_extended_tags,
164238 } ) ;
165239 return None ;
166240 }
@@ -170,12 +244,17 @@ impl TagCardinalityLimit {
170244 }
171245 }
172246 LimitExceededAction :: DropTag => {
173- let config = self . get_config_for_metric ( metric_key. as_ref ( ) ) ;
174- let include_extended_tags = config. internal_metrics . include_extended_tags ;
175247 tags_map. retain ( |key, value| {
176248 if self . try_accept_tag ( metric_key. as_ref ( ) , key, value) {
177249 true
178250 } else {
251+ let include_extended_tags =
252+ match self . get_config_for_metric_tag ( metric_key. as_ref ( ) , key) {
253+ TagSettings :: Tracked ( inner) => {
254+ inner. internal_metrics . include_extended_tags
255+ }
256+ TagSettings :: Excluded => false , // unreachable: excluded tags accept
257+ } ;
179258 emit ! ( TagCardinalityLimitRejectingTag {
180259 metric_name: & metric_name,
181260 tag_key: key,
0 commit comments