Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 9 additions & 2 deletions src/NATS.Client.Core/NatsConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -384,9 +384,16 @@ internal string SpanDestinationName(string subject)
if (subject.StartsWith(Opts.InboxPrefix, StringComparison.Ordinal))
return "inbox";

if (NatsInstrumentationOptions.Default.SpanDestinationNameFormatter is { } formatter)
return formatter(subject);

// to avoid long span names and low cardinality, only take the first two tokens
var tokens = subject.Split('.');
return tokens.Length < 2 ? subject : $"{tokens[0]}.{tokens[1]}";
var subjectSpan = subject.AsSpan();
var firstSeparator = subjectSpan.IndexOf('.');
var secondSeparator = firstSeparator < 0 ? -1 : subjectSpan[(firstSeparator + 1)..].IndexOf('.');
return secondSeparator < 0
? subject
: subjectSpan[..(firstSeparator + 1 + secondSeparator)].ToString();
}

internal NatsStats GetStats() => Counter.ToStats();
Expand Down
8 changes: 8 additions & 0 deletions src/NATS.Client.Core/NatsInstrumentationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,12 @@ public sealed class NatsInstrumentationOptions
/// Gets or sets an action to enrich an Activity.
/// </summary>
public Action<Activity, NatsInstrumentationContext>? Enrich { get; set; }

/// <summary>
/// Gets or sets a function that formats the destination name used in span names.
/// </summary>
/// <remarks>
/// The input is the raw NATS subject. This only changes activity names, not telemetry tags.
/// </remarks>
public Func<string, string>? SpanDestinationNameFormatter { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static TracerProviderBuilder AddNatsClientInstrumentation(this TracerProv

/// <summary>
/// Adds the NATS .NET client <see cref="System.Diagnostics.ActivitySource"/> to the tracer provider and
/// configures the shared <see cref="NatsInstrumentationOptions"/> (filter and enrich callbacks).
/// configures the shared <see cref="NatsInstrumentationOptions"/>.
/// </summary>
/// <param name="builder">The <see cref="TracerProviderBuilder"/> to add the source to.</param>
/// <param name="configure">Action that mutates the process-wide <see cref="NatsInstrumentationOptions.Default"/>.</param>
Expand Down
56 changes: 56 additions & 0 deletions tests/NATS.Client.CoreUnit.Tests/NatsConnectionTelemetryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
namespace NATS.Client.CoreUnit.Tests;

public class NatsConnectionTelemetryTests
{
[Theory]
[InlineData("foo", "foo")]
[InlineData("foo.bar", "foo.bar")]
[InlineData("foo.bar.baz", "foo.bar")]
[InlineData("foo.bar.baz.qux", "foo.bar")]
[InlineData("foo.", "foo.")]
[InlineData("foo..bar", "foo.")]
[InlineData(".foo.bar", ".foo")]
[InlineData("..", ".")]
public async Task SpanDestinationName_collapses_to_first_two_subject_tokens(string subject, string expected)
{
await using var nats = new NatsConnection();

nats.SpanDestinationName(subject).Should().Be(expected);
}

[Fact]
public async Task SpanDestinationName_uses_configured_formatter()
{
var previous = NatsInstrumentationOptions.Default.SpanDestinationNameFormatter;
try
{
NatsInstrumentationOptions.Default.SpanDestinationNameFormatter = subject => $"custom:{subject}";

await using var nats = new NatsConnection();

nats.SpanDestinationName("foo.bar.baz").Should().Be("custom:foo.bar.baz");
}
finally
{
NatsInstrumentationOptions.Default.SpanDestinationNameFormatter = previous;
}
}

[Fact]
public async Task SpanDestinationName_collapses_inbox_before_configured_formatter()
{
var previous = NatsInstrumentationOptions.Default.SpanDestinationNameFormatter;
try
{
NatsInstrumentationOptions.Default.SpanDestinationNameFormatter = subject => $"custom:{subject}";

await using var nats = new NatsConnection();

nats.SpanDestinationName("_INBOX.abc.def").Should().Be("inbox");
}
finally
{
NatsInstrumentationOptions.Default.SpanDestinationNameFormatter = previous;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,20 @@ public void AddNatsClientInstrumentation_with_configure_sets_options()
configured = true;
options.Filter = _ => true;
options.Enrich = (_, _) => { };
options.SpanDestinationNameFormatter = subject => subject;
})
.Build();

configured.Should().BeTrue();
NatsInstrumentationOptions.Default.Filter.Should().NotBeNull();
NatsInstrumentationOptions.Default.Enrich.Should().NotBeNull();
NatsInstrumentationOptions.Default.SpanDestinationNameFormatter.Should().NotBeNull();
}
finally
{
NatsInstrumentationOptions.Default.Filter = null;
NatsInstrumentationOptions.Default.Enrich = null;
NatsInstrumentationOptions.Default.SpanDestinationNameFormatter = null;
}
}

Expand Down
Loading