Skip to content

Commit d549eb7

Browse files
committed
Handle Tag-only filters
1 parent 695435f commit d549eb7

1 file changed

Lines changed: 81 additions & 17 deletions

File tree

tracer/src/Datadog.Trace/Agent/TraceSamplers/TraceFilter.cs

Lines changed: 81 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
using System;
99
using System.Collections.Generic;
10+
using System.Runtime.CompilerServices;
1011
using System.Text.RegularExpressions;
1112
using Datadog.Trace.Agent.DiscoveryService;
1213
using 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

Comments
 (0)