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

Add support for OTEL_INSTRUMENTATION_HTTP_KNOWN_METHODS #5197

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from 12 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
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@ public static MeterProviderBuilder AddAspNetCoreInstrumentation(
#if NET8_0_OR_GREATER
return builder.ConfigureMeters();
#else
// Note: Warm-up the status code and method mapping.
// Note: Warm-up the status code.
_ = TelemetryHelper.BoxedStatusCodes;
_ = RequestMethodHelper.KnownMethods;

builder.AddMeter(HttpInMetricsListener.InstrumentationName);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,8 @@ public static TracerProviderBuilder AddAspNetCoreInstrumentation(
{
Guard.ThrowIfNull(builder);

// Note: Warm-up the status code and method mapping.
// Note: Warm-up the status code.
_ = TelemetryHelper.BoxedStatusCodes;
_ = RequestMethodHelper.KnownMethods;

name ??= Options.DefaultName;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ internal AspNetCoreTraceInstrumentationOptions(IConfiguration configuration)
{
this.EnableGrpcAspNetCoreSupport = enableGrpcInstrumentation;
}

if (configuration.TryGetStringValue("OTEL_INSTRUMENTATION_HTTP_KNOWN_METHODS", out var knownHttpMethods))
{
this.KnownHttpMethods = knownHttpMethods
.Split(',')
.Select(x => x.Trim())
.Where(x => !string.IsNullOrEmpty(x))
.ToList();
CodeBlanch marked this conversation as resolved.
Show resolved Hide resolved
}
}

/// <summary>
Expand Down Expand Up @@ -91,4 +100,7 @@ internal AspNetCoreTraceInstrumentationOptions(IConfiguration configuration)
/// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-spans.md.
/// </remarks>
internal bool EnableGrpcAspNetCoreSupport { get; set; }

internal List<string> KnownHttpMethods { get; set; } =
[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,15 @@ internal class HttpInListener : ListenerHandler
private readonly PropertyFetcher<string> beforeActionTemplateFetcher = new("Template");
#endif
private readonly AspNetCoreTraceInstrumentationOptions options;
private readonly RequestMethodHelper requestMethodHelper;

public HttpInListener(AspNetCoreTraceInstrumentationOptions options)
: base(DiagnosticSourceName)
{
Guard.ThrowIfNull(options);

this.options = options;
this.requestMethodHelper = new RequestMethodHelper(this.options.KnownHttpMethods);
}

public override void OnEventWritten(string name, object payload)
Expand Down Expand Up @@ -105,8 +107,7 @@ public void OnStartActivity(Activity activity, object payload)
// By this time, samplers have already run and
// activity.IsAllDataRequested populated accordingly.

HttpContext context = payload as HttpContext;
if (context == null)
if (payload is not HttpContext context)
{
AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStartActivity), activity.OperationName);
return;
Expand Down Expand Up @@ -176,7 +177,7 @@ public void OnStartActivity(Activity activity, object payload)
#endif

var path = (request.PathBase.HasValue || request.Path.HasValue) ? (request.PathBase + request.Path).ToString() : "/";
activity.DisplayName = GetDisplayName(request.Method);
activity.DisplayName = this.GetDisplayName(request.Method);

// see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md

Expand All @@ -196,7 +197,7 @@ public void OnStartActivity(Activity activity, object payload)
activity.SetTag(SemanticConventions.AttributeUrlQuery, request.QueryString.Value);
}

RequestMethodHelper.SetHttpMethodTag(activity, request.Method);
this.requestMethodHelper.SetHttpMethodTag(activity, request.Method);

activity.SetTag(SemanticConventions.AttributeUrlScheme, request.Scheme);
activity.SetTag(SemanticConventions.AttributeUrlPath, path);
Expand Down Expand Up @@ -226,8 +227,7 @@ public void OnStopActivity(Activity activity, object payload)
{
if (activity.IsAllDataRequested)
{
HttpContext context = payload as HttpContext;
if (context == null)
if (payload is not HttpContext context)
{
AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStopActivity), activity.OperationName);
return;
Expand All @@ -239,7 +239,7 @@ public void OnStopActivity(Activity activity, object payload)
var routePattern = (context.GetEndpoint() as RouteEndpoint)?.RoutePattern.RawText;
if (!string.IsNullOrEmpty(routePattern))
{
activity.DisplayName = GetDisplayName(context.Request.Method, routePattern);
activity.DisplayName = this.GetDisplayName(context.Request.Method, routePattern);
activity.SetTag(SemanticConventions.AttributeHttpRoute, routePattern);
}
#endif
Expand Down Expand Up @@ -382,9 +382,9 @@ private static void AddGrpcAttributes(Activity activity, string grpcMethod, Http
}
}

private static string GetDisplayName(string httpMethod, string httpRoute = null)
private string GetDisplayName(string httpMethod, string httpRoute = null)
{
var normalizedMethod = RequestMethodHelper.GetNormalizedHttpMethod(httpMethod);
var normalizedMethod = this.requestMethodHelper.GetNormalizedHttpMethod(httpMethod);

return string.IsNullOrEmpty(httpRoute)
? normalizedMethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ public static void OnStopEventWritten(string name, object payload)
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeUrlScheme, context.Request.Scheme));
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(context.Response.StatusCode)));

var httpMethod = RequestMethodHelper.GetNormalizedHttpMethod(context.Request.Method);
var requestMethodHelper = new RequestMethodHelper(string.Empty);
CodeBlanch marked this conversation as resolved.
Show resolved Hide resolved
var httpMethod = requestMethodHelper.GetNormalizedHttpMethod(context.Request.Method);
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpRequestMethod, httpMethod));

#if NET6_0_OR_GREATER
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,8 @@ public static MeterProviderBuilder AddHttpClientInstrumentation(
.AddMeter("System.Net.Http")
.AddMeter("System.Net.NameResolution");
#else
// Note: Warm-up the status code and method mapping.
// Note: Warm-up the status code.
_ = TelemetryHelper.BoxedStatusCodes;
_ = RequestMethodHelper.KnownMethods;

#if NETFRAMEWORK
builder.AddMeter(HttpWebRequestActivitySource.MeterName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,8 @@ public static TracerProviderBuilder AddHttpClientInstrumentation(
{
Guard.ThrowIfNull(builder);

// Note: Warm-up the status code and method mapping.
// Note: Warm-up the status code.
_ = TelemetryHelper.BoxedStatusCodes;
_ = RequestMethodHelper.KnownMethods;

name ??= Options.DefaultName;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ public class HttpClientTraceInstrumentationOptions
/// </remarks>
public bool RecordException { get; set; }

internal List<string> KnownHttpMethods { get; set; } =
Copy link
Member

Choose a reason for hiding this comment

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

Is this being set from OTEL_INSTRUMENTATION_HTTP_KNOWN_METHODS anywhere? I don't see a ctor accepting IConfiguration on this class so it is possible some prep works needs to be done to hook that up. LMK if you would like some help with that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct, that was still a to do.
I now made some changes to read from env variables as well and added a unit test for it.
Can you review?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A question I had:
When OTEL_INSTRUMENTATION_HTTP_KNOWN_METHODS is set to an empty string or something illogical like fooBar, http.request.method is still set to GET for example and not to _OTHER.
Do you think this is correct behaviour?

Copy link
Member

Choose a reason for hiding this comment

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

I pushed some changes to the branch. Sorry about that I needed to see how the code was working and then I just started messing around with it 😓

  • Refactored everything into RequestMethodHelper (instead of options classes). Reason for that is OTEL_INSTRUMENTATION_HTTP_KNOWN_METHODS should also apply to metrics. I didn't want to introduce metrics options classes where we don't have them and also didn't want the same code in 4 options classes 🤣 I think now it has better separation of concerns.

  • Your question above about fooBar. I took a look at the spec and it seems like OTEL_INSTRUMENTATION_HTTP_KNOWN_METHODS should fully override the default list. The way it was coded you could only pair down the list of known things. I switched it up so we respect whatever is specified by OTEL_INSTRUMENTATION_HTTP_KNOWN_METHODS. I would like @vishweshbankwar to sound off on this. He has more expertise in the area. But he is out until 1/15 so we'll have to wait a bit to get his review.

[];

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal bool EventFilterHttpRequestMessage(string activityName, object arg1)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

namespace OpenTelemetry.Instrumentation.Http.Implementation;

internal sealed class HttpHandlerDiagnosticListener : ListenerHandler
internal sealed class HttpHandlerDiagnosticListener(HttpClientTraceInstrumentationOptions options) : ListenerHandler("HttpHandlerDiagnosticListener")
{
internal static readonly AssemblyName AssemblyName = typeof(HttpHandlerDiagnosticListener).Assembly.GetName();
internal static readonly bool IsNet7OrGreater;
Expand All @@ -34,7 +34,8 @@ internal sealed class HttpHandlerDiagnosticListener : ListenerHandler
private static readonly PropertyFetcher<HttpResponseMessage> StopResponseFetcher = new("Response");
private static readonly PropertyFetcher<Exception> StopExceptionFetcher = new("Exception");
private static readonly PropertyFetcher<TaskStatus> StopRequestStatusFetcher = new("RequestTaskStatus");
private readonly HttpClientTraceInstrumentationOptions options;
private readonly HttpClientTraceInstrumentationOptions options = options;
private readonly RequestMethodHelper requestMethodHelper = new(options.KnownHttpMethods);

static HttpHandlerDiagnosticListener()
{
Expand All @@ -48,12 +49,6 @@ static HttpHandlerDiagnosticListener()
}
}

public HttpHandlerDiagnosticListener(HttpClientTraceInstrumentationOptions options)
: base("HttpHandlerDiagnosticListener")
{
this.options = options;
}

public override void OnEventWritten(string name, object payload)
{
switch (name)
Expand Down Expand Up @@ -135,7 +130,7 @@ public void OnStartActivity(Activity activity, object payload)
return;
}

RequestMethodHelper.SetHttpClientActivityDisplayName(activity, request.Method.Method);
this.requestMethodHelper.SetHttpClientActivityDisplayName(activity, request.Method.Method);

if (!IsNet7OrGreater)
{
Expand All @@ -144,7 +139,7 @@ public void OnStartActivity(Activity activity, object payload)
}

// see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md
RequestMethodHelper.SetHttpMethodTag(activity, request.Method.Method);
this.requestMethodHelper.SetHttpMethodTag(activity, request.Method.Method);

activity.SetTag(SemanticConventions.AttributeServerAddress, request.RequestUri.Host);
if (!request.RequestUri.IsDefaultPort)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

namespace OpenTelemetry.Instrumentation.Http.Implementation;

internal sealed class HttpHandlerMetricsDiagnosticListener : ListenerHandler
internal sealed class HttpHandlerMetricsDiagnosticListener(string name) : ListenerHandler(name)
{
internal const string OnStopEvent = "System.Net.Http.HttpRequestOut.Stop";

Expand All @@ -34,10 +34,7 @@ internal sealed class HttpHandlerMetricsDiagnosticListener : ListenerHandler
private static readonly HttpRequestOptionsKey<string> HttpRequestOptionsErrorKey = new(SemanticConventions.AttributeErrorType);
#endif

public HttpHandlerMetricsDiagnosticListener(string name)
: base(name)
{
}
private static readonly RequestMethodHelper RequestMethodHelper = new(string.Empty);

public static void OnStopEventWritten(Activity activity, object payload)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ internal static class HttpWebRequestActivitySource
private static readonly Histogram<double> HttpClientRequestDuration = WebRequestMeter.CreateHistogram<double>("http.client.request.duration", "s", "Measures the duration of outbound HTTP requests.");

private static HttpClientTraceInstrumentationOptions tracingOptions;
private static RequestMethodHelper requestMethodHelper;

// Fields for reflection
private static FieldInfo connectionGroupListField;
Expand Down Expand Up @@ -89,18 +90,19 @@ internal static HttpClientTraceInstrumentationOptions TracingOptions
set
{
tracingOptions = value;
requestMethodHelper = new RequestMethodHelper(tracingOptions.KnownHttpMethods);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void AddRequestTagsAndInstrumentRequest(HttpWebRequest request, Activity activity)
{
RequestMethodHelper.SetHttpClientActivityDisplayName(activity, request.Method);
requestMethodHelper.SetHttpClientActivityDisplayName(activity, request.Method);

if (activity.IsAllDataRequested)
{
// see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md
RequestMethodHelper.SetHttpMethodTag(activity, request.Method);
requestMethodHelper.SetHttpMethodTag(activity, request.Method);

activity.SetTag(SemanticConventions.AttributeServerAddress, request.RequestUri.Host);
if (!request.RequestUri.IsDefaultPort)
Expand Down Expand Up @@ -359,12 +361,11 @@ private static void ProcessResult(IAsyncResult asyncResult, AsyncCallback asyncC
// Disposed HttpWebResponse throws when accessing properties, so let's make a copy of the data to ensure that doesn't happen.

HttpWebResponse responseCopy = httpWebResponseCtor(
new object[]
{
[
uriAccessor(response), verbAccessor(response), coreResponseDataAccessor(response), mediaTypeAccessor(response),
usesProxySemanticsAccessor(response), DecompressionMethods.None,
isWebSocketResponseAccessor(response), connectionGroupNameAccessor(response),
});
]);

if (activity != null)
{
Expand Down Expand Up @@ -425,7 +426,7 @@ private static void ProcessResult(IAsyncResult asyncResult, AsyncCallback asyncC

TagList tags = default;

var httpMethod = RequestMethodHelper.GetNormalizedHttpMethod(request.Method);
var httpMethod = requestMethodHelper.GetNormalizedHttpMethod(request.Method);
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpRequestMethod, httpMethod));

tags.Add(SemanticConventions.AttributeServerAddress, request.RequestUri.Host);
Expand Down
Loading