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

[Instrumentation.AspNet] pass http.request.method to sampler #2023

Open
wants to merge 40 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
961e5be
add http.method to create activity
Aug 15, 2024
993d7bc
test
roycald245 Aug 15, 2024
29fbce7
remove comment
roycald245 Aug 26, 2024
9078fe8
Merge branch 'main' into feat/add-http-method
roycald245 Aug 26, 2024
353d9d6
edit changelog
roycald245 Aug 26, 2024
d6d0942
edit readme
roycald245 Aug 26, 2024
dce0aac
update tests
roycald245 Aug 26, 2024
d2e511e
lint
roycald245 Aug 26, 2024
8855fd1
lint markdown
roycald245 Aug 26, 2024
f62e71c
fix passing url.path to http.request.method
roycald245 Aug 26, 2024
4c3c59a
Edit changelog
roycald245 Aug 26, 2024
114cabb
rephrase changelog
roycald245 Aug 26, 2024
8494e65
Update CHANGELOG.md
roycald245 Aug 27, 2024
36496b0
lint changelog
roycald245 Aug 28, 2024
606b118
Merge branch 'main' into feat/add-http-method
Kielek Aug 29, 2024
34e7fa3
Add http method normalization
roycald245 Sep 1, 2024
128ac4a
Merge branch 'main' into feat/add-http-method
roycald245 Sep 1, 2024
ae37251
Merge branch 'main' into feat/add-http-method
roycald245 Sep 3, 2024
4ea758b
Merge branch 'main' into feat/add-http-method
cijothomas Sep 6, 2024
b56b68f
allow RequestDataHelper configuration by environment variable
roycald245 Sep 8, 2024
b1cdc34
Revert allow environment configuration of RequestDataHelper
roycald245 Sep 9, 2024
06d8c26
Merge branch 'main' into feat/add-http-method
roycald245 Sep 9, 2024
7498fde
Revert revert of requestDataHelper parameter
roycald245 Sep 9, 2024
1c59dd0
Merge branch 'main' into feat/add-http-method
Kielek Sep 9, 2024
1241783
Merge branch 'main' into feat/add-http-method
roycald245 Sep 12, 2024
4c0f465
Merge branch 'main' into feat/add-http-method
cijothomas Sep 19, 2024
7cafdcf
Merge branch 'main' into feat/add-http-method
Kielek Sep 24, 2024
0010779
Merge branch 'main' into feat/add-http-method
Kielek Oct 3, 2024
0e07997
Direct feedback
Kielek Oct 3, 2024
81830ed
typo fix
Kielek Oct 3, 2024
7b7c123
Merge branch 'main' into feat/add-http-method
Kielek Oct 3, 2024
511702f
Merge branch 'main' into feat/add-http-method
roycald245 Oct 3, 2024
76fb410
Merge branch 'main' into feat/add-http-method
Kielek Oct 4, 2024
2529b1b
Merge branch 'main' into feat/add-http-method
roycald245 Oct 5, 2024
946aca3
Merge branch 'main' into feat/add-http-method
roycald245 Oct 11, 2024
49b45d8
Merge branch 'main' into feat/add-http-method
roycald245 Oct 13, 2024
5c1b72e
Merge branch 'main' into feat/add-http-method
roycald245 Oct 14, 2024
a3f94ea
Merge branch 'main' into feat/add-http-method
roycald245 Oct 17, 2024
ccb159a
Merge branch 'main' into feat/add-http-method
Kielek Oct 21, 2024
b694696
Merge branch 'main' into feat/add-http-method
roycald245 Oct 27, 2024
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
Expand Up @@ -7,6 +7,7 @@
using OpenTelemetry.Context;
using OpenTelemetry.Context.Propagation;
using OpenTelemetry.Internal;
using OpenTelemetry.Trace;

namespace OpenTelemetry.Instrumentation.AspNet;

Expand All @@ -20,6 +21,7 @@ internal static class ActivityHelper
/// </summary>
internal const string ContextKey = "__AspnetInstrumentationContext__";
internal static readonly object StartedButNotSampledObj = new();
internal static readonly RequestDataHelper RequestDataHelper = new(configureByHttpKnownMethodsEnvironmentalVariable: true);

private const string BaggageSlotName = "otel.baggage";
private static readonly Func<HttpRequest, string, IEnumerable<string>> HttpRequestHeaderValuesGetter = (request, name) => request.Headers.GetValues(name);
Expand All @@ -28,7 +30,7 @@ internal static class ActivityHelper
typeof(ActivityHelper).Assembly.GetPackageVersion());

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

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

KeyValuePair<string, object?>[]? tags;
if (context.Request?.Unvalidated?.Path is string path)
{
tags = cachedTagsStorage ??= new KeyValuePair<string, object?>[1];
string? path = context.Request?.Unvalidated?.Path;
string? originalHttpMethod = context.Request?.HttpMethod;
Comment on lines +68 to +69
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible for context to be null here?

Suggested change
string? path = context.Request?.Unvalidated?.Path;
string? originalHttpMethod = context.Request?.HttpMethod;
string? path = context.Request.Unvalidated?.Path;
string originalHttpMethod = context.Request.HttpMethod;


tags[0] = new KeyValuePair<string, object?>("url.path", path);
}
else
string normalizedHttpMethod = RequestDataHelper.GetNormalizedHttpMethod(originalHttpMethod ?? string.Empty);

List<KeyValuePair<string, object?>> tags = cachedTagsStorage ??= new List<KeyValuePair<string, object?>>(3); // use max capacity

if (path is not null)
{
tags = null;
tags.Add(new KeyValuePair<string, object?>(SemanticConventions.AttributeUrlPath, path));
}

Activity? activity = AspNetSource.StartActivity(TelemetryHttpModule.AspNetActivityName, ActivityKind.Server, propagationContext.ActivityContext, tags);
tags.Add(new KeyValuePair<string, object?>(SemanticConventions.AttributeHttpRequestMethod, normalizedHttpMethod));
Copy link
Member

Choose a reason for hiding this comment

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

RequestDataHelper.SetHttpMethodTag(tags, originalHttpMethod: context.Request.HttpMethod);

class RequestDataHelper
{
    public void SetHttpMethodTag(List<KeyValuePair<string, object?>> tags, string originalHttpMethod)
    {
        var normalizedHttpMethod = this.GetNormalizedHttpMethod(originalHttpMethod);
        tags.Add(new(SemanticConventions.AttributeHttpRequestMethod, normalizedHttpMethod));

        if (originalHttpMethod != normalizedHttpMethod)
        {
            tags.Add(new(SemanticConventions.AttributeHttpRequestMethodOriginal, originalHttpMethod));
        }
    }
}

Reason being it seems like all of this logic is already encapsulated inside RequestDataHelper.


if (tags is not null)
if (originalHttpMethod != null && originalHttpMethod != normalizedHttpMethod)
{
tags[0] = default;
tags.Add(new KeyValuePair<string, object?>(SemanticConventions.AttributeHttpRequestMethodOriginal, originalHttpMethod));
}

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

tags.Clear();

if (activity != null)
{
if (textMapPropagator is not TraceContextPropagator)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +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))
* `TelemetryHttpModule` will now pass the `http.request.method`
and the `url.path` tags (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))
([#1871](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1871),
[#2023](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/2023))

## 1.9.0-beta.1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
<Compile Include="$(RepoRoot)\src\Shared\AssemblyVersionExtensions.cs" Link="Includes\AssemblyVersionExtensions.cs" />
<Compile Include="$(RepoRoot)\src\Shared\ExceptionExtensions.cs" Link="Includes\ExceptionExtensions.cs" />
<Compile Include="$(RepoRoot)\src\Shared\Guard.cs" Link="Includes\Guard.cs" />
<Compile Include="$(RepoRoot)\src\Shared\RequestDataHelper.cs" Link="Includes\RequestDataHelper.cs" />
<Compile Include="$(RepoRoot)\src\Shared\SemanticConventions.cs" Link="Includes\SemanticConventions.cs" />
</ItemGroup>

<ItemGroup>
Expand Down
5 changes: 3 additions & 2 deletions src/OpenTelemetry.Instrumentation.AspNet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,9 @@ below.
> 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` in addition to `http.request.method` tag are 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
data collection for anything NOT recorded by the sampler. The sampler approach
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ public void AspNetRequestsAreCollectedSuccessfully(
new TestSampler(SamplingDecision.RecordAndSample)
{
ExpectedUrlPath = expectedUrlPath,
ExpectedHttpRequestMethod = expectedRequestMethod,
ExpectedOriginalRequestMethod = expectedOriginalRequestMethod,
})
.Build())
{
Expand Down Expand Up @@ -256,7 +258,7 @@ public void ExtractContextIrrespectiveOfSamplingDecision(SamplingDecision sampli
var activityProcessor = new TestActivityProcessor();
Sdk.SetDefaultTextMapPropagator(propagator);
using (var tracerProvider = Sdk.CreateTracerProviderBuilder()
.SetSampler(new TestSampler(samplingDecision))
.SetSampler(new TestSampler(samplingDecision) { ExpectedHttpRequestMethod = "GET", ExpectedUrlPath = "/" })
.AddAspNetInstrumentation()
.AddProcessor(activityProcessor).Build())
{
Expand Down Expand Up @@ -314,13 +316,45 @@ private class TestSampler(SamplingDecision samplingDecision) : Sampler

public string? ExpectedUrlPath { get; set; }

public string? ExpectedHttpRequestMethod { get; set; }

public string? ExpectedOriginalRequestMethod { get; set; }

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

if (!string.IsNullOrEmpty(this.ExpectedHttpRequestMethod))
{
Assert.Contains(samplingParameters.Tags, t => t.Key == "http.request.method" && (t.Value as string) == this.ExpectedHttpRequestMethod);
expectedTagsCount++;
}
else
{
Assert.DoesNotContain(samplingParameters.Tags, t => t.Key == "http.request.method");
}

if (!string.IsNullOrEmpty(this.ExpectedOriginalRequestMethod))
{
Assert.Contains(samplingParameters.Tags, t => t.Key == "http.request.method_original" && (t.Value as string) == this.ExpectedOriginalRequestMethod);
expectedTagsCount++;
}
else
{
Assert.DoesNotContain(samplingParameters.Tags, t => t.Key == "http.request.method_original");
}

Assert.Equal(expectedTagsCount, samplingParameters.Tags.Count());

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