Skip to content

Commit 647f1b1

Browse files
alimahboubiKielek
andauthored
[Api] support for adding default tags to tracer (#6137)
Co-authored-by: Piotr Kiełkowicz <[email protected]>
1 parent 402ed87 commit 647f1b1

File tree

4 files changed

+307
-9
lines changed

4 files changed

+307
-9
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
OpenTelemetry.Trace.TracerProvider.GetTracer(string! name, string? version = null, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string!, object?>>? tags = null) -> OpenTelemetry.Trace.Tracer!
2+
OpenTelemetry.Trace.TracerProvider.GetTracer(string! name, string? version) -> OpenTelemetry.Trace.Tracer!
3+
*REMOVED*OpenTelemetry.Trace.TracerProvider.GetTracer(string! name, string? version = null) -> OpenTelemetry.Trace.Tracer!
4+

Diff for: src/OpenTelemetry.Api/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ Notes](../../RELEASENOTES.md).
66

77
## Unreleased
88

9+
* Added a new overload for `TracerProvider.GetTracer` which accepts an optional
10+
`IEnumerable<KeyValuePair<string, object?>>? tags` parameter, allowing
11+
additional attributes to be associated with the `Tracer`.
12+
([#6137](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6137))
13+
914
## 1.11.2
1015

1116
Released 2025-Mar-04

Diff for: src/OpenTelemetry.Api/Trace/TracerProvider.cs

+159-7
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,29 @@ protected TracerProvider()
3333
/// <param name="name">Name identifying the instrumentation library.</param>
3434
/// <param name="version">Version of the instrumentation library.</param>
3535
/// <returns>Tracer instance.</returns>
36+
// 1.11.1 BACKCOMPAT OVERLOAD -- DO NOT TOUCH
3637
public Tracer GetTracer(
3738
#if NET
3839
[AllowNull]
3940
#endif
4041
string name,
41-
string? version = null)
42+
string? version) =>
43+
this.GetTracer(name, version, null);
44+
45+
/// <summary>
46+
/// Gets a tracer with given name, version and tags.
47+
/// </summary>
48+
/// <param name="name">Name identifying the instrumentation library.</param>
49+
/// <param name="version">Version of the instrumentation library.</param>
50+
/// <param name="tags">Tags associated with the tracer.</param>
51+
/// <returns>Tracer instance.</returns>
52+
public Tracer GetTracer(
53+
#if NET
54+
[AllowNull]
55+
#endif
56+
string name,
57+
string? version = null,
58+
IEnumerable<KeyValuePair<string, object?>>? tags = null)
4259
{
4360
var tracers = this.Tracers;
4461
if (tracers == null)
@@ -47,7 +64,7 @@ public Tracer GetTracer(
4764
return new(activitySource: null);
4865
}
4966

50-
var key = new TracerKey(name, version);
67+
var key = new TracerKey(name, version, tags);
5168

5269
if (!tracers.TryGetValue(key, out var tracer))
5370
{
@@ -60,12 +77,10 @@ public Tracer GetTracer(
6077
return new(activitySource: null);
6178
}
6279

63-
tracer = new(new(key.Name, key.Version));
64-
#if DEBUG
80+
tracer = new(new(key.Name, key.Version, key.Tags));
6581
bool result = tracers.TryAdd(key, tracer);
82+
#if DEBUG
6683
System.Diagnostics.Debug.Assert(result, "Write into tracers cache failed");
67-
#else
68-
tracers.TryAdd(key, tracer);
6984
#endif
7085
}
7186
}
@@ -103,11 +118,148 @@ internal readonly record struct TracerKey
103118
{
104119
public readonly string Name;
105120
public readonly string? Version;
121+
public readonly KeyValuePair<string, object?>[]? Tags;
106122

107-
public TracerKey(string? name, string? version)
123+
public TracerKey(string? name, string? version, IEnumerable<KeyValuePair<string, object?>>? tags)
108124
{
109125
this.Name = name ?? string.Empty;
110126
this.Version = version;
127+
this.Tags = this.GetOrderedTags(tags);
128+
}
129+
130+
public bool Equals(TracerKey other)
131+
{
132+
if (!string.Equals(this.Name, other.Name, StringComparison.Ordinal))
133+
{
134+
return false;
135+
}
136+
137+
if (!string.Equals(this.Version, other.Version, StringComparison.Ordinal))
138+
{
139+
return false;
140+
}
141+
142+
return AreTagsEqual(this.Tags, other.Tags);
143+
}
144+
145+
public override int GetHashCode()
146+
{
147+
unchecked
148+
{
149+
var hash = 17;
150+
hash = (hash * 31) + (this.Name?.GetHashCode() ?? 0);
151+
hash = (hash * 31) + (this.Version?.GetHashCode() ?? 0);
152+
hash = (hash * 31) + GetTagsHashCode(this.Tags);
153+
return hash;
154+
}
155+
}
156+
157+
private static bool AreTagsEqual(
158+
KeyValuePair<string, object?>[]? tags1,
159+
KeyValuePair<string, object?>[]? tags2)
160+
{
161+
if (tags1 == null && tags2 == null)
162+
{
163+
return true;
164+
}
165+
166+
if (tags1 == null || tags2 == null || tags1.Length != tags2.Length)
167+
{
168+
return false;
169+
}
170+
171+
for (int i = 0; i < tags1.Length; i++)
172+
{
173+
var kvp1 = tags1[i];
174+
var kvp2 = tags2[i];
175+
176+
if (!string.Equals(kvp1.Key, kvp2.Key, StringComparison.Ordinal))
177+
{
178+
return false;
179+
}
180+
181+
// Compare values
182+
if (kvp1.Value is null)
183+
{
184+
if (kvp2.Value is not null)
185+
{
186+
return false;
187+
}
188+
}
189+
else
190+
{
191+
if (!kvp1.Value.Equals(kvp2.Value))
192+
{
193+
return false;
194+
}
195+
}
196+
}
197+
198+
return true;
199+
}
200+
201+
private static int GetTagsHashCode(
202+
IEnumerable<KeyValuePair<string, object?>>? tags)
203+
{
204+
if (tags is null)
205+
{
206+
return 0;
207+
}
208+
209+
var hash = 0;
210+
unchecked
211+
{
212+
foreach (var kvp in tags)
213+
{
214+
hash = (hash * 31) + kvp.Key.GetHashCode();
215+
if (kvp.Value != null)
216+
{
217+
hash = (hash * 31) + kvp.Value.GetHashCode()!;
218+
}
219+
}
220+
}
221+
222+
return hash;
223+
}
224+
225+
private KeyValuePair<string, object?>[]? GetOrderedTags(
226+
IEnumerable<KeyValuePair<string, object?>>? tags)
227+
{
228+
if (tags is null)
229+
{
230+
return null;
231+
}
232+
233+
var orderedTagList = new List<KeyValuePair<string, object?>>(tags);
234+
orderedTagList.Sort((left, right) =>
235+
{
236+
// First compare by key
237+
int keyComparison = string.Compare(left.Key, right.Key, StringComparison.Ordinal);
238+
if (keyComparison != 0)
239+
{
240+
return keyComparison;
241+
}
242+
243+
// If keys are equal, compare by value
244+
if (left.Value == null && right.Value == null)
245+
{
246+
return 0;
247+
}
248+
249+
if (left.Value == null)
250+
{
251+
return -1;
252+
}
253+
254+
if (right.Value == null)
255+
{
256+
return 1;
257+
}
258+
259+
// Both values are non-null, compare as strings
260+
return string.Compare(left.Value.ToString(), right.Value.ToString(), StringComparison.Ordinal);
261+
});
262+
return orderedTagList.ToArray();
111263
}
112264
}
113265
}

Diff for: test/OpenTelemetry.Api.Tests/Trace/TracerTests.cs

+139-2
Original file line numberDiff line numberDiff line change
@@ -303,8 +303,8 @@ public void TracerBecomesNoopWhenParentProviderIsDisposedTest()
303303
Tracer? tracer1;
304304

305305
using (var tracerProvider = Sdk.CreateTracerProviderBuilder()
306-
.AddSource("mytracer")
307-
.Build())
306+
.AddSource("mytracer")
307+
.Build())
308308
{
309309
provider = tracerProvider;
310310
tracer1 = tracerProvider.GetTracer("mytracer");
@@ -408,6 +408,143 @@ static void InnerTest()
408408
}
409409
}
410410

411+
[Fact]
412+
public void GetTracer_WithSameTags_ReturnsSameInstance()
413+
{
414+
var tags1 = new List<KeyValuePair<string, object?>> { new("tag1", "value1"), new("tag2", "value2") };
415+
var tags2 = new List<KeyValuePair<string, object?>> { new("tag1", "value1"), new("tag2", "value2") };
416+
417+
using var tracerProvider = new TestTracerProvider();
418+
var tracer1 = tracerProvider.GetTracer("test", "1.0.0", tags1);
419+
var tracer2 = tracerProvider.GetTracer("test", "1.0.0", tags2);
420+
421+
Assert.Same(tracer1, tracer2);
422+
}
423+
424+
[Fact]
425+
public void GetTracer_WithoutTags_ReturnsSameInstance()
426+
{
427+
using var tracerProvider = new TestTracerProvider();
428+
var tracer1 = tracerProvider.GetTracer("test", "1.0.0");
429+
var tracer2 = tracerProvider.GetTracer("test", "1.0.0");
430+
431+
Assert.Same(tracer1, tracer2);
432+
}
433+
434+
[Fact]
435+
public void GetTracer_WithDifferentTags_ReturnsDifferentInstances()
436+
{
437+
var tags1 = new List<KeyValuePair<string, object?>> { new("tag1", "value1") };
438+
var tags2 = new List<KeyValuePair<string, object?>> { new("tag2", "value2") };
439+
440+
using var tracerProvider = new TestTracerProvider();
441+
var tracer1 = tracerProvider.GetTracer("test", "1.0.0", tags1);
442+
var tracer2 = tracerProvider.GetTracer("test", "1.0.0", tags2);
443+
444+
Assert.NotSame(tracer1, tracer2);
445+
}
446+
447+
[Fact]
448+
public void GetTracer_WithDifferentOrderTags_ReturnsSameInstance()
449+
{
450+
var tags1 = new List<KeyValuePair<string, object?>> { new("tag2", "value2"), new("tag1", "value1"), };
451+
var tags2 = new List<KeyValuePair<string, object?>> { new("tag1", "value1"), new("tag2", "value2"), };
452+
453+
using var tracerProvider = new TestTracerProvider();
454+
var tracer1 = tracerProvider.GetTracer("test", "1.0.0", tags1);
455+
var tracer2 = tracerProvider.GetTracer("test", "1.0.0", tags2);
456+
457+
Assert.Same(tracer1, tracer2);
458+
}
459+
460+
[Fact]
461+
public void GetTracer_TagsValuesAreIntType_ReturnsSameInstance()
462+
{
463+
var tags1 = new List<KeyValuePair<string, object?>> { new("tag2", 2), new("tag1", 1) };
464+
var tags2 = new List<KeyValuePair<string, object?>> { new("tag1", 1), new("tag2", 2) };
465+
466+
using var tracerProvider = new TestTracerProvider();
467+
var tracer1 = tracerProvider.GetTracer("test", "1.0.0", tags1);
468+
var tracer2 = tracerProvider.GetTracer("test", "1.0.0", tags2);
469+
470+
Assert.Same(tracer1, tracer2);
471+
}
472+
473+
[Fact]
474+
public void GetTracer_TagsValuesAreSameWithDifferentOrder_ReturnsSameInstance()
475+
{
476+
var tags1 = new List<KeyValuePair<string, object?>> { new("tag3", 1), new("tag1", 1), new("tag2", 1), new("tag1", 2), new("tag2", 2) };
477+
var tags2 = new List<KeyValuePair<string, object?>> { new("tag2", 1), new("tag1", 2), new("tag1", 1), new("tag2", 2), new("tag3", 1) };
478+
479+
using var tracerProvider = new TestTracerProvider();
480+
var tracer1 = tracerProvider.GetTracer("test", "1.0.0", tags1);
481+
var tracer2 = tracerProvider.GetTracer("test", "1.0.0", tags2);
482+
483+
Assert.Same(tracer1, tracer2);
484+
}
485+
486+
[Fact]
487+
public void GetTracer_TagsContainNullValues_ReturnsSameInstance()
488+
{
489+
var tags1 = new List<KeyValuePair<string, object?>> { new("tag3", 1), new("tag2", 3), new("tag1", null), new("tag2", null), new("tag1", 2), new("tag2", 2) };
490+
var tags2 = new List<KeyValuePair<string, object?>> { new("tag2", null), new("tag1", 2), new("tag2", 3), new("tag1", null), new("tag2", 2), new("tag3", 1) };
491+
492+
using var tracerProvider = new TestTracerProvider();
493+
var tracer1 = tracerProvider.GetTracer("test", "1.0.0", tags1);
494+
var tracer2 = tracerProvider.GetTracer("test", "1.0.0", tags2);
495+
496+
Assert.Same(tracer1, tracer2);
497+
}
498+
499+
[Fact]
500+
public void GetTracer_WithDifferentTagsSize_ReturnsDifferentInstances()
501+
{
502+
var tags1 = new List<KeyValuePair<string, object?>> { new("tag2", 2), new("tag1", 1) };
503+
var tags2 = new List<KeyValuePair<string, object?>> { new("tag1", 1), new("tag2", 2), new("tag3", 3) };
504+
505+
using var tracerProvider = new TestTracerProvider();
506+
var tracer1 = tracerProvider.GetTracer("test", "1.0.0", tags1);
507+
var tracer2 = tracerProvider.GetTracer("test", "1.0.0", tags2);
508+
509+
Assert.NotSame(tracer1, tracer2);
510+
}
511+
512+
[Fact]
513+
public void GetTracer_WithTagsAndWithoutTags_ReturnsDifferentInstances()
514+
{
515+
var tags = new List<KeyValuePair<string, object?>> { new("tag1", "value1") };
516+
517+
using var tracerProvider = new TestTracerProvider();
518+
var tracerWithTags = tracerProvider.GetTracer("test", "1.0.0", tags);
519+
var tracerWithoutTags = tracerProvider.GetTracer("test", "1.0.0");
520+
521+
Assert.NotEqual(tracerWithTags, tracerWithoutTags);
522+
}
523+
524+
[Fact]
525+
public void GetTracer_WithTags_AppliesTagsToActivities()
526+
{
527+
var exportedItems = new List<Activity>();
528+
var tags = new List<KeyValuePair<string, object?>> { new("tracerTag", "tracerValue") };
529+
530+
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
531+
.AddSource("test")
532+
.AddInMemoryExporter(exportedItems)
533+
.SetSampler(new AlwaysOnSampler())
534+
.Build();
535+
536+
var tracer = tracerProvider.GetTracer("test", "1.0.0", tags);
537+
538+
using (var span = tracer.StartActiveSpan("TestSpan"))
539+
{
540+
// Activity started by the tracer with tags
541+
}
542+
543+
var activity = Assert.Single(exportedItems);
544+
545+
Assert.Contains(activity.Source.Tags!, kvp => kvp.Key == "tracerTag" && (string)kvp.Value! == "tracerValue");
546+
}
547+
411548
public void Dispose()
412549
{
413550
Activity.Current = null;

0 commit comments

Comments
 (0)