Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.AspNetCore.Http.Metadata;
using Microsoft.AspNetCore.Shared;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;

namespace Microsoft.AspNetCore.Hosting;

Expand Down Expand Up @@ -57,10 +58,10 @@ public HostingApplicationDiagnostics(

private static bool GetSuppressActivityOpenTelemetryData()
{
// Default to true if the switch isn't set.
// Default to false if the switch isn't set.
if (!AppContext.TryGetSwitch("Microsoft.AspNetCore.Hosting.SuppressActivityOpenTelemetryData", out var enabled))
{
return true;
return false;
}

return enabled;
Expand Down Expand Up @@ -233,7 +234,7 @@ public void RequestEnd(HttpContext httpContext, Exception? exception, HostingApp
// can capture the activity as a metric exemplar.
if (activity is not null)
{
StopActivity(httpContext, activity, context.HasDiagnosticListener);
StopActivity(httpContext, activity, exception, context.HasDiagnosticListener);
}

if (context.EventLogEnabled)
Expand Down Expand Up @@ -457,6 +458,8 @@ private static TagList CreateInitializeActivityTags(HttpContext httpContext)
// Missing values recommended by the spec are:
// - url.query (need configuration around redaction to do properly)
// - http.request.header.<key>
//
// Note that these tags are added even if Activity.IsAllDataRequested is false, as they may be used in sampling decisions.

var request = httpContext.Request;
var creationTags = new TagList();
Expand All @@ -473,7 +476,7 @@ private static TagList CreateInitializeActivityTags(HttpContext httpContext)

HostingTelemetryHelpers.SetActivityHttpMethodTags(ref creationTags, request.Method);

if (request.Headers.TryGetValue("User-Agent", out var values))
if (request.Headers.TryGetValue(HeaderNames.UserAgent, out var values))
{
var userAgent = values.Count > 0 ? values[0] : null;
if (!string.IsNullOrEmpty(userAgent))
Expand All @@ -491,8 +494,13 @@ private static TagList CreateInitializeActivityTags(HttpContext httpContext)
}

[MethodImpl(MethodImplOptions.NoInlining)]
private void StopActivity(HttpContext httpContext, Activity activity, bool hasDiagnosticListener)
private void StopActivity(HttpContext httpContext, Activity activity, Exception? exception, bool hasDiagnosticListener)
{
if (!SuppressActivityOpenTelemetryData && activity.IsAllDataRequested)
{
SetActivityEndTags(httpContext, activity, exception);
}

if (hasDiagnosticListener)
{
StopActivity(activity, httpContext);
Expand All @@ -503,6 +511,36 @@ private void StopActivity(HttpContext httpContext, Activity activity, bool hasDi
}
}

private static void SetActivityEndTags(HttpContext httpContext, Activity activity, Exception? exception)
{
var response = httpContext.Response;

activity.SetTag(HostingTelemetryHelpers.AttributeHttpResponseStatusCode, HostingTelemetryHelpers.GetBoxedStatusCode(response.StatusCode));

if (HostingTelemetryHelpers.TryGetHttpVersion(httpContext.Request.Protocol, out var httpVersion))
{
activity.SetTag(HostingTelemetryHelpers.AttributeNetworkProtocolVersion, httpVersion);
}

var endpoint = HttpExtensions.GetOriginalEndpoint(httpContext);
var route = endpoint?.Metadata.GetMetadata<IRouteDiagnosticsMetadata>()?.Route;
if (route != null)
{
activity.SetTag(HostingTelemetryHelpers.AttributeHttpRoute, RouteDiagnosticsHelpers.ResolveHttpRoute(route));
}

if (exception != null)
{
activity.SetTag(HostingTelemetryHelpers.AttributeErrorType, exception.GetType().FullName);
activity.SetStatus(ActivityStatusCode.Error, exception.Message);
}
else if (HostingTelemetryHelpers.IsErrorStatusCode(response.StatusCode))
{
activity.SetTag(HostingTelemetryHelpers.AttributeErrorType, response.StatusCode.ToString(CultureInfo.InvariantCulture));
activity.SetStatus(ActivityStatusCode.Error);
}
}

// These are versions of DiagnosticSource.Start/StopActivity that don't allocate strings per call (see https://github.com/dotnet/corefx/issues/37055)
// DynamicDependency matches the properties selected in:
// https://github.com/dotnet/diagnostics/blob/7cc6fbef613cdfe5ff64393120d59d7a15e98bd6/src/Microsoft.Diagnostics.Monitoring.EventPipe/Configuration/HttpRequestSourceConfiguration.cs#L20-L33
Expand Down
18 changes: 12 additions & 6 deletions src/Hosting/Hosting/src/Internal/HostingMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Diagnostics;
using System.Diagnostics.Metrics;
using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Shared;

Expand Down Expand Up @@ -56,18 +57,18 @@ public void RequestEnd(string protocol, string scheme, string method, string? ro
{
if (HostingTelemetryHelpers.TryGetHttpVersion(protocol, out var httpVersion))
{
tags.Add("network.protocol.version", httpVersion);
tags.Add(HostingTelemetryHelpers.AttributeNetworkProtocolVersion, httpVersion);
}
if (unhandledRequest)
{
tags.Add("aspnetcore.request.is_unhandled", true);
}

// Add information gathered during request.
tags.Add("http.response.status_code", HostingTelemetryHelpers.GetBoxedStatusCode(statusCode));
tags.Add(HostingTelemetryHelpers.AttributeHttpResponseStatusCode, HostingTelemetryHelpers.GetBoxedStatusCode(statusCode));
if (route != null)
{
tags.Add("http.route", RouteDiagnosticsHelpers.ResolveHttpRoute(route));
tags.Add(HostingTelemetryHelpers.AttributeHttpRoute, RouteDiagnosticsHelpers.ResolveHttpRoute(route));
}

// Add before some built in tags so custom tags are prioritized when dealing with duplicates.
Expand All @@ -85,7 +86,12 @@ public void RequestEnd(string protocol, string scheme, string method, string? ro
{
// Exception tag could have been added by middleware. If an exception is later thrown in request pipeline
// then we don't want to add a duplicate tag here because that breaks some metrics systems.
tags.TryAddTag("error.type", exception.GetType().FullName);
tags.TryAddTag(HostingTelemetryHelpers.AttributeErrorType, exception.GetType().FullName);
}
else if (HostingTelemetryHelpers.IsErrorStatusCode(statusCode))
{
// Add error.type for 5xx status codes when there's no exception.
tags.TryAddTag(HostingTelemetryHelpers.AttributeErrorType, statusCode.ToString(CultureInfo.InvariantCulture));
}

var duration = Stopwatch.GetElapsedTime(startTimestamp, currentTimestamp);
Expand All @@ -102,7 +108,7 @@ public void Dispose()

private static void InitializeRequestTags(ref TagList tags, string scheme, string method)
{
tags.Add("url.scheme", scheme);
tags.Add("http.request.method", HostingTelemetryHelpers.GetNormalizedHttpMethod(method));
tags.Add(HostingTelemetryHelpers.AttributeUrlScheme, scheme);
tags.Add(HostingTelemetryHelpers.AttributeHttpRequestMethod, HostingTelemetryHelpers.GetNormalizedHttpMethod(method));
}
}
13 changes: 11 additions & 2 deletions src/Hosting/Hosting/src/Internal/HostingTelemetryHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@ namespace Microsoft.AspNetCore.Hosting;
internal static class HostingTelemetryHelpers
{
// Semantic Conventions for HTTP.
// Note: Not all telemetry code is using these const attribute names yet.
public const string AttributeHttpRequestMethod = "http.request.method";
public const string AttributeHttpRequestMethodOriginal = "http.request.method_original";
public const string AttributeHttpResponseStatusCode = "http.response.status_code";
public const string AttributeHttpRoute = "http.route";
public const string AttributeUrlScheme = "url.scheme";
public const string AttributeUrlPath = "url.path";
public const string AttributeServerAddress = "server.address";
public const string AttributeServerPort = "server.port";
public const string AttributeUserAgentOriginal = "user_agent.original";
public const string AttributeNetworkProtocolVersion = "network.protocol.version";
public const string AttributeErrorType = "error.type";

// The value "_OTHER" is used for non-standard HTTP methods.
// https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md#common-attributes
Expand Down Expand Up @@ -69,7 +72,7 @@ public static bool TryGetServerPort(HostString host, string scheme, [NotNullWhen

public static object GetBoxedStatusCode(int statusCode)
{
object[] boxes = BoxedStatusCodes;
var boxes = BoxedStatusCodes;
return (uint)statusCode < (uint)boxes.Length
? boxes[statusCode] ??= statusCode
: statusCode;
Expand Down Expand Up @@ -129,4 +132,10 @@ public static void SetActivityHttpMethodTags(ref TagList tags, string originalHt
tags.Add(AttributeHttpRequestMethodOriginal, originalHttpMethod);
}
}

/// <summary>
/// Determines if the status code indicates a server error (5xx).
/// Client errors (4xx) are not considered server errors.
/// </summary>
public static bool IsErrorStatusCode(int statusCode) => statusCode >= 500;
}
Loading
Loading