Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Api] support for adding default tags to tracer #6137

Merged
Changes from 8 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
de622c3
Add support for tracer tags in GetTracer method for .NET 9.0 and greater
alimahboubi Jan 28, 2025
aa2b630
Refactor: unify GetTracer methods, leverage default parameters, and e…
alimahboubi Feb 15, 2025
c4508b2
Merge remote-tracking branch 'origin/main' into feature/Support-for-a…
alimahboubi Feb 15, 2025
1ecc3c7
- change default value of "version" in "GetTracer"
alimahboubi Feb 19, 2025
81f5a22
Merge remote-tracking branch 'origin/main' into feature/Support-for-a…
alimahboubi Feb 19, 2025
80c4336
fix: Update TracerProvider.GetTracer API to match spec
alimahboubi Feb 20, 2025
1399dbc
fix unit test
alimahboubi Feb 20, 2025
ca89e24
Fix: Ensure TracerKey equality
alimahboubi Feb 21, 2025
c3be9dc
fix: Improve tag comparison logic in AreTagsEqual method
alimahboubi Feb 21, 2025
6f19fd3
chore: Update CHANGELOG to include new TracerProvider.GetTracer overload
alimahboubi Feb 21, 2025
a879c7c
Merge branch 'main' into feature/Support-for-adding-default-tags-to-t…
alimahboubi Feb 24, 2025
ea89f90
Merge branch 'main' into feature/Support-for-adding-default-tags-to-t…
Kielek Feb 25, 2025
54e8261
Merge branch 'main' into feature/Support-for-adding-default-tags-to-t…
Kielek Feb 26, 2025
acc26b4
Merge branch 'main' into feature/Support-for-adding-default-tags-to-t…
alimahboubi Feb 27, 2025
91bbc77
Merge branch 'main' into feature/Support-for-adding-default-tags-to-t…
alimahboubi Mar 3, 2025
8645d62
Merge remote-tracking branch 'origin/main' into feature/Support-for-a…
alimahboubi Mar 7, 2025
47ef129
Fix merge conflicts
alimahboubi Mar 7, 2025
185aff5
Merge remote-tracking branch 'origin/feature/Support-for-adding-defau…
alimahboubi Mar 7, 2025
3d6856d
Merge remote-tracking branch 'origin/main' into feature/Support-for-a…
alimahboubi Mar 13, 2025
259acdf
Enhance tag sorting logic to handle same key and maybe with different…
alimahboubi Mar 13, 2025
4c13c94
Merge branch 'main' into feature/Support-for-adding-default-tags-to-t…
alimahboubi Mar 13, 2025
5668c8f
Merge branch 'main' into feature/Support-for-adding-default-tags-to-t…
alimahboubi Mar 14, 2025
3c2c9b3
Merge remote-tracking branch 'origin/main' into feature/Support-for-a…
alimahboubi Mar 19, 2025
a126785
Merge branch 'main' into feature/Support-for-adding-default-tags-to-t…
alimahboubi Mar 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
OpenTelemetry.Trace.TracerProvider.GetTracer(string! name, string? version = null, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string!, object?>>? tags = null) -> OpenTelemetry.Trace.Tracer!
OpenTelemetry.Trace.TracerProvider.GetTracer(string! name, string? version) -> OpenTelemetry.Trace.Tracer!
*REMOVED*OpenTelemetry.Trace.TracerProvider.GetTracer(string! name, string? version = null) -> OpenTelemetry.Trace.Tracer!

112 changes: 105 additions & 7 deletions src/OpenTelemetry.Api/Trace/TracerProvider.cs
Original file line number Diff line number Diff line change
@@ -33,12 +33,29 @@ protected TracerProvider()
/// <param name="name">Name identifying the instrumentation library.</param>
/// <param name="version">Version of the instrumentation library.</param>
/// <returns>Tracer instance.</returns>
// 1.11.1 BACKCOMPAT OVERLOAD -- DO NOT TOUCH
public Tracer GetTracer(
#if NET
[AllowNull]
#endif
string name,
string? version = null)
string? version) =>
this.GetTracer(name, version, null);

/// <summary>
/// Gets a tracer with given name, version and tags.
/// </summary>
/// <param name="name">Name identifying the instrumentation library.</param>
/// <param name="version">Version of the instrumentation library.</param>
/// <param name="tags">Tags associated with the tracer.</param>
/// <returns>Tracer instance.</returns>
public Tracer GetTracer(
#if NET
[AllowNull]
#endif
string name,
string? version = null,
IEnumerable<KeyValuePair<string, object?>>? tags = null)
{
var tracers = this.Tracers;
if (tracers == null)
@@ -47,7 +64,7 @@ public Tracer GetTracer(
return new(activitySource: null);
}

var key = new TracerKey(name, version);
var key = new TracerKey(name, version, tags);

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

tracer = new(new(key.Name, key.Version));
#if DEBUG
tracer = new(new(key.Name, key.Version, key.Tags));
bool result = tracers.TryAdd(key, tracer);
#if DEBUG
System.Diagnostics.Debug.Assert(result, "Write into tracers cache failed");
#else
tracers.TryAdd(key, tracer);
#endif
}
}
@@ -103,11 +118,94 @@ internal readonly record struct TracerKey
{
public readonly string Name;
public readonly string? Version;
public readonly KeyValuePair<string, object?>[]? Tags;

public TracerKey(string? name, string? version)
public TracerKey(string? name, string? version, IEnumerable<KeyValuePair<string, object?>>? tags)
{
this.Name = name ?? string.Empty;
this.Version = version;
this.Tags = this.GetOrderedTags(tags);
}

public bool Equals(TracerKey other)
{
if (!string.Equals(this.Name, other.Name, StringComparison.Ordinal))
{
return false;
}

if (!string.Equals(this.Version, other.Version, StringComparison.Ordinal))
{
return false;
}

return AreTagsEqual(this.Tags, other.Tags);
}

public override int GetHashCode()
{
unchecked
{
var hash = 17;
hash = (hash * 31) + (this.Name?.GetHashCode() ?? 0);
hash = (hash * 31) + (this.Version?.GetHashCode() ?? 0);
hash = (hash * 31) + GetTagsHashCode(this.Tags);
return hash;
}
}

private static bool AreTagsEqual(
IEnumerable<KeyValuePair<string, object?>>? tags1,
IEnumerable<KeyValuePair<string, object?>>? tags2)
{
if (tags1 is null && tags2 is null)
{
return true;
}

if (tags1 is null || tags2 is null)
{
return false;
}

return tags1.SequenceEqual(tags2);
}

private static int GetTagsHashCode(
IEnumerable<KeyValuePair<string, object?>>? tags)
{
if (tags is null)
{
return 0;
}

var hash = 0;
unchecked
{
foreach (var kvp in tags)
{
hash = (hash * 31) + kvp.Key.GetHashCode();
if (kvp.Value != null)
{
hash = (hash * 31) + kvp.Value.GetHashCode()!;
}
}
}

return hash;
}

private KeyValuePair<string, object?>[]? GetOrderedTags(
IEnumerable<KeyValuePair<string, object?>>? tags)
{
if (tags is null)
{
return null;
}

var orderedTagList = new List<KeyValuePair<string, object?>>(tags);
orderedTagList.Sort((left, right) => string.Compare(left.Key, right.Key, StringComparison.Ordinal));
return orderedTagList.ToArray();
}
}
}
79 changes: 77 additions & 2 deletions test/OpenTelemetry.Api.Tests/Trace/TracerTest.cs
Original file line number Diff line number Diff line change
@@ -303,8 +303,8 @@ public void TracerBecomesNoopWhenParentProviderIsDisposedTest()
Tracer? tracer1;

using (var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource("mytracer")
.Build())
.AddSource("mytracer")
.Build())
{
provider = tracerProvider;
tracer1 = tracerProvider.GetTracer("mytracer");
@@ -408,6 +408,81 @@ static void InnerTest()
}
}

[Fact]
public void GetTracer_WithTags_ReturnsSameInstanceForSameTags()
{
var tags1 = new List<KeyValuePair<string, object?>> { new("tag1", "value1"), new("tag2", "value2") };
var tags2 = new List<KeyValuePair<string, object?>> { new("tag1", "value1"), new("tag2", "value2") };

using var tracerProvider = new TestTracerProvider();
var tracer1 = tracerProvider.GetTracer("test", "1.0.0", tags1);
var tracer2 = tracerProvider.GetTracer("test", "1.0.0", tags2);

Assert.Same(tracer1, tracer2);
}

[Fact]
public void GetTracer_WithTags_ReturnsDifferentInstancesForDifferentTags()
{
var tags1 = new List<KeyValuePair<string, object?>> { new("tag1", "value1") };
var tags2 = new List<KeyValuePair<string, object?>> { new("tag2", "value2") };

using var tracerProvider = new TestTracerProvider();
var tracer1 = tracerProvider.GetTracer("test", "1.0.0", tags1);
var tracer2 = tracerProvider.GetTracer("test", "1.0.0", tags2);

Assert.NotSame(tracer1, tracer2);
}

[Fact]
public void GetTracer_WithTags_OrdersTagsByKey()
{
var tags1 = new List<KeyValuePair<string, object?>> { new("tag2", "value2"), new("tag1", "value1"), };
var tags2 = new List<KeyValuePair<string, object?>> { new("tag1", "value1"), new("tag2", "value2"), };

using var tracerProvider = new TestTracerProvider();
var tracer1 = tracerProvider.GetTracer("test", "1.0.0", tags1);
var tracer2 = tracerProvider.GetTracer("test", "1.0.0", tags2);

Assert.Same(tracer1, tracer2);
}

[Fact]
public void GetTracer_WithTagsAndWithoutTags_ReturnsDifferentInstances()
{
var tags = new List<KeyValuePair<string, object?>> { new("tag1", "value1") };

using var tracerProvider = new TestTracerProvider();
var tracerWithTags = tracerProvider.GetTracer("test", "1.0.0", tags);
var tracerWithoutTags = tracerProvider.GetTracer("test", "1.0.0");

Assert.NotEqual(tracerWithTags, tracerWithoutTags);
}

[Fact]
public void GetTracer_WithTags_AppliesTagsToActivities()
{
var exportedItems = new List<Activity>();
var tags = new List<KeyValuePair<string, object?>> { new("tracerTag", "tracerValue") };

using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource("test")
.AddInMemoryExporter(exportedItems)
.SetSampler(new AlwaysOnSampler())
.Build();

var tracer = tracerProvider.GetTracer("test", "1.0.0", tags);

using (var span = tracer.StartActiveSpan("TestSpan"))
{
// Activity started by the tracer with tags
}

var activity = Assert.Single(exportedItems);

Assert.Contains(activity.Source.Tags!, kvp => kvp.Key == "tracerTag" && (string)kvp.Value! == "tracerValue");
}

public void Dispose()
{
Activity.Current = null;