Skip to content

[aspnet] Add url.path to sampler tags #1871

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

Merged
merged 12 commits into from
Aug 12, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ internal static class ActivityHelper
TelemetryHttpModule.AspNetSourceName,
typeof(ActivityHelper).Assembly.GetPackageVersion());

[ThreadStatic]
private static KeyValuePair<string, object?>[]? cachedTagsStorage;

/// <summary>
/// Try to get the started <see cref="Activity"/> for the running <see
/// cref="HttpContext"/>.
Expand Down Expand Up @@ -60,7 +63,24 @@ public static bool HasStarted(HttpContext context, out Activity? aspNetActivity)
{
PropagationContext propagationContext = textMapPropagator.Extract(default, context.Request, HttpRequestHeaderValuesGetter);

Activity? activity = AspNetSource.StartActivity(TelemetryHttpModule.AspNetActivityName, ActivityKind.Server, propagationContext.ActivityContext);
KeyValuePair<string, object?>[]? tags;
if (context.Request?.Unvalidated?.Path is string path)
{
tags = cachedTagsStorage ??= new KeyValuePair<string, object?>[1];

tags[0] = new KeyValuePair<string, object?>("url.path", path);
}
else
{
tags = null;
}

Activity? activity = AspNetSource.StartActivity(TelemetryHttpModule.AspNetActivityName, ActivityKind.Server, propagationContext.ActivityContext, tags);

if (tags is not null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a need to do this reset?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need? You could argue that it isn't essential to the functionality.
I think it is a good idea to cleanup the value so that we don't have a reference to some random string living after it's been used. The code required to check and clear the field is a handful of assembly instructions.

{
tags[0] = default;
}

if (activity != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## Unreleased

* `TelemetryHttpModule` will now pass the `url.path` tag (set to
[Request.Unvalidated.Path](https://learn.microsoft.com/dotnet/api/system.web.unvalidatedrequestvalues.path))
when starting `Activity` instances for incoming requests so that it is
available to samplers and may be used to influence the sampling decision made
by [custom
implementations](https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/docs/trace/extending-the-sdk#sampler).
([#1871](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1871))

## 1.9.0-beta.1

Released 2024-Jun-18
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ private void OnStartActivity(Activity activity, HttpContext context)
}

var request = context.Request;
var requestValues = request.Unvalidated;

// see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.24.0/docs/http/http-spans.md
var originalHttpMethod = request.HttpMethod;
Expand All @@ -83,8 +82,6 @@ private void OnStartActivity(Activity activity, HttpContext context)
activity.SetTag(SemanticConventions.AttributeNetworkProtocolVersion, protocolVersion);
}

activity.SetTag(SemanticConventions.AttributeUrlPath, requestValues.Path);

// TODO url.query should be sanitized
var query = url.Query;
if (!string.IsNullOrEmpty(query))
Expand Down
17 changes: 12 additions & 5 deletions src/OpenTelemetry.Instrumentation.AspNet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,18 @@ below.

### Trace Filter

> [!NOTE]
> OpenTelemetry has the concept of a
[Sampler](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#sampling).
When using `OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule` the
`url.path` tag is supplied automatically to samplers when telemetry is started
for incoming requests. It is recommended to use a sampler which inspects
`url.path` (as opposed to the `Filter` option described below) in order to
perform filtering as it will prevent child spans from being created and bypass
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
perform filtering as it will prevent child spans from being created and bypass
perform filtering based on "url.path" Tag, as it will prevent child spans from being created and bypass

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
perform filtering as it will prevent child spans from being created and bypass
perform filtering as it can ensure consistent sampling when ParentBased samplers are used, by consistently recording/not-recording this Activity and the children..

Not sure of how best to phrase this. Will come back later. This is reasonably good to merge now.

data collection for anything NOT recorded by the sampler. The sampler approach
will reduce the impact on the process being instrumented for all filtered
requests.

This instrumentation by default collects all the incoming http requests. It
allows filtering of requests by using the `Filter` function in
`AspNetTraceInstrumentationOptions`. This defines the condition for allowable
Expand All @@ -158,11 +170,6 @@ this.tracerProvider = Sdk.CreateTracerProviderBuilder()
.Build();
```

It is important to note that this `Filter` option is specific to this
instrumentation. OpenTelemetry has a concept of a
[Sampler](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#sampling),
and the `Filter` option does the filtering *before* the Sampler is invoked.

### Trace Enrich

This instrumentation library provides `EnrichWithHttpRequest`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ public void AspNetRequestsAreCollectedSuccessfully(
options.RecordException = recordException;
})
.AddInMemoryExporter(exportedItems)
.SetSampler(
new TestSampler(SamplingDecision.RecordAndSample)
{
ExpectedUrlPath = expectedUrlPath,
})
.Build())
{
using var inMemoryEventListener = new InMemoryEventListener(AspNetInstrumentationEventSource.Log);
Expand Down Expand Up @@ -307,8 +312,16 @@ private class TestSampler(SamplingDecision samplingDecision) : Sampler
{
private readonly SamplingDecision samplingDecision = samplingDecision;

public string? ExpectedUrlPath { get; set; }

public override SamplingResult ShouldSample(in SamplingParameters samplingParameters)
{
if (!string.IsNullOrEmpty(this.ExpectedUrlPath))
{
Assert.NotNull(samplingParameters.Tags);
Assert.Contains(samplingParameters.Tags, t => t.Key == "url.path" && (t.Value as string) == this.ExpectedUrlPath);
}

return new SamplingResult(this.samplingDecision);
}
}
Expand Down
Loading