77
88using System ;
99using System . Collections . Generic ;
10+ using System . Runtime . CompilerServices ;
1011using System . Text . RegularExpressions ;
1112using Datadog . Trace . Agent . DiscoveryService ;
1213using Datadog . Trace . Tagging ;
@@ -21,16 +22,16 @@ internal sealed class TraceFilter
2122{
2223 private readonly List < string > _filterTagsRequire ;
2324 private readonly List < string > _filterTagsReject ;
24- private readonly List < Regex > _filterTagsRegexRequire ;
25- private readonly List < Regex > _filterTagsRegexReject ;
25+ private readonly List < RegexTagFilter > _filterTagsRegexRequire ;
26+ private readonly List < RegexTagFilter > _filterTagsRegexReject ;
2627 private readonly List < Regex > _ignoreResources ;
2728
2829 public TraceFilter ( AgentTraceFilterConfig config )
2930 {
3031 _filterTagsRequire = config . FilterTagsRequire ?? [ ] ;
3132 _filterTagsReject = config . FilterTagsReject ?? [ ] ;
32- _filterTagsRegexRequire = CompilePatterns ( config . FilterTagsRegexRequire ) ;
33- _filterTagsRegexReject = CompilePatterns ( config . FilterTagsRegexReject ) ;
33+ _filterTagsRegexRequire = CompileTagFilters ( config . FilterTagsRegexRequire ) ;
34+ _filterTagsRegexReject = CompileTagFilters ( config . FilterTagsRegexReject ) ;
3435 _ignoreResources = CompilePatterns ( config . IgnoreResources ) ;
3536 }
3637
@@ -61,9 +62,9 @@ public bool ShouldKeepTrace(Span rootSpan)
6162 }
6263 }
6364
64- foreach ( var pattern in _filterTagsRegexReject )
65+ foreach ( var filter in _filterTagsRegexReject )
6566 {
66- if ( MatchesRegexFilter ( rootSpan , pattern ) )
67+ if ( MatchesRegexTagFilter ( rootSpan , filter ) )
6768 {
6869 return false ;
6970 }
@@ -78,9 +79,9 @@ public bool ShouldKeepTrace(Span rootSpan)
7879 }
7980 }
8081
81- foreach ( var pattern in _filterTagsRegexRequire )
82+ foreach ( var filter in _filterTagsRegexRequire )
8283 {
83- if ( ! MatchesRegexFilter ( rootSpan , pattern ) )
84+ if ( ! MatchesRegexTagFilter ( rootSpan , filter ) )
8485 {
8586 return false ;
8687 }
@@ -108,12 +109,13 @@ private static bool MatchesExactFilter(Span span, string filter)
108109 }
109110
110111 /// <summary>
111- /// Matches a regex filter against span tags.
112- /// The regex is matched against the "key:value" string of each tag.
112+ /// Matches a regex tag filter against span tags.
113+ /// Per the spec, patterns are either "key_pattern" (matches any tag whose key matches)
114+ /// or "key_pattern:value_pattern" (both key and value must match their respective patterns).
113115 /// </summary>
114- private static bool MatchesRegexFilter ( Span span , Regex pattern )
116+ private static bool MatchesRegexTagFilter ( Span span , RegexTagFilter filter )
115117 {
116- var processor = new RegexTagMatchProcessor ( pattern ) ;
118+ var processor = new RegexTagFilterProcessor ( filter ) ;
117119 span . Tags . EnumerateTags ( ref processor ) ;
118120 return processor . Matched ;
119121 }
@@ -137,22 +139,84 @@ private static List<Regex> CompilePatterns(List<string>? patterns)
137139 return compiled ;
138140 }
139141
140- private struct RegexTagMatchProcessor : IItemProcessor < string >
142+ /// <summary>
143+ /// Parses regex filter patterns into structured filters that match key and value separately.
144+ /// Format: "key_pattern" (key-only) or "key_pattern:value_pattern" (key and value).
145+ /// </summary>
146+ private static List < RegexTagFilter > CompileTagFilters ( List < string > ? patterns )
141147 {
142- private readonly Regex _pattern ;
148+ if ( patterns is null or { Count : 0 } )
149+ {
150+ return [ ] ;
151+ }
152+
153+ var filters = new List < RegexTagFilter > ( patterns . Count ) ;
154+ foreach ( var pattern in patterns )
155+ {
156+ if ( string . IsNullOrEmpty ( pattern ) )
157+ {
158+ continue ;
159+ }
160+
161+ var colonIndex = pattern . IndexOf ( ':' ) ;
162+ if ( colonIndex < 0 )
163+ {
164+ // Key-only pattern: matches any tag whose key matches
165+ filters . Add ( new RegexTagFilter (
166+ new Regex ( pattern , RegexOptions . Compiled , matchTimeout : TimeSpan . FromSeconds ( 1 ) ) ,
167+ valuePattern : null ) ) ;
168+ }
169+ else
170+ {
171+ // key_pattern:value_pattern — both must match
172+ var keyPart = pattern . Substring ( 0 , colonIndex ) ;
173+ var valuePart = pattern . Substring ( colonIndex + 1 ) ;
174+ filters . Add ( new RegexTagFilter (
175+ new Regex ( keyPart , RegexOptions . Compiled , matchTimeout : TimeSpan . FromSeconds ( 1 ) ) ,
176+ new Regex ( valuePart , RegexOptions . Compiled , matchTimeout : TimeSpan . FromSeconds ( 1 ) ) ) ) ;
177+ }
178+ }
179+
180+ return filters ;
181+ }
182+
183+ /// <summary>
184+ /// A parsed regex tag filter with separate key and optional value patterns.
185+ /// </summary>
186+ private readonly struct RegexTagFilter
187+ {
188+ public readonly Regex KeyPattern ;
189+ public readonly Regex ? ValuePattern ;
190+
191+ public RegexTagFilter ( Regex keyPattern , Regex ? valuePattern )
192+ {
193+ KeyPattern = keyPattern ;
194+ ValuePattern = valuePattern ;
195+ }
196+ }
197+
198+ private struct RegexTagFilterProcessor : IItemProcessor < string >
199+ {
200+ private readonly RegexTagFilter _filter ;
143201 public bool Matched ;
144202
145- public RegexTagMatchProcessor ( Regex pattern )
203+ public RegexTagFilterProcessor ( RegexTagFilter filter )
146204 {
147- _pattern = pattern ;
205+ _filter = filter ;
148206 Matched = false ;
149207 }
150208
209+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
151210 public void Process ( TagItem < string > item )
152211 {
153212 if ( ! Matched && item . Value is not null )
154213 {
155- Matched = _pattern . IsMatch ( $ "{ item . Key } :{ item . Value } ") ;
214+ if ( _filter . KeyPattern . IsMatch ( item . Key ) )
215+ {
216+ // Key-only filter: any matching key is sufficient
217+ // Key:Value filter: value must also match
218+ Matched = _filter . ValuePattern is null || _filter . ValuePattern . IsMatch ( item . Value ) ;
219+ }
156220 }
157221 }
158222 }
0 commit comments