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

HttpSemanticConventions - new metric in AspNetCore Instrumentation. #4802

Merged
merged 51 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
91f08c8
update AspNetCore for new metric 'http.server.request.duration'
TimothyMothra Aug 24, 2023
c111eea
cleanup
TimothyMothra Aug 24, 2023
32d8946
remove attributes
TimothyMothra Aug 24, 2023
3208431
comment
TimothyMothra Aug 24, 2023
d2f7ef4
readme
TimothyMothra Aug 24, 2023
1a49dab
comment
TimothyMothra Aug 24, 2023
7c11551
update readme
TimothyMothra Aug 24, 2023
93fe6ad
cleanup
TimothyMothra Aug 24, 2023
3842aa8
check flag in ctor
TimothyMothra Aug 25, 2023
ec2b825
Merge branch 'main' into 4484_newMetrics
TimothyMothra Aug 25, 2023
5f74488
update tags on metrics
TimothyMothra Aug 29, 2023
9fb75e5
refactor
TimothyMothra Aug 29, 2023
c335f65
Merge branch 'main' into 4484_newMetrics
TimothyMothra Aug 29, 2023
6c86a4a
fix name in log
TimothyMothra Aug 29, 2023
3bcefae
another fix
TimothyMothra Aug 29, 2023
e588453
more fixes
TimothyMothra Aug 29, 2023
88355cd
big refactor, split Old and New into two methods
TimothyMothra Aug 29, 2023
6a3f651
investigating test fix
TimothyMothra Aug 30, 2023
1a72356
final fix for test issue
TimothyMothra Aug 30, 2023
b9d194c
Merge branch 'main' into 4484_newMetrics
TimothyMothra Aug 30, 2023
ac76993
Merge branch 'main' into 4484_newMetrics
TimothyMothra Sep 5, 2023
b57320d
change unit to seconds
TimothyMothra Sep 5, 2023
7fdc29f
update Readme
TimothyMothra Sep 5, 2023
a782b51
cleanup if condition
TimothyMothra Sep 7, 2023
3788d15
fix indentation
TimothyMothra Sep 7, 2023
275de4e
update readme
TimothyMothra Sep 7, 2023
c6d5d76
change test to use IConfiguration
TimothyMothra Sep 7, 2023
3789fdd
markdown lint
TimothyMothra Sep 8, 2023
f110b29
split test methods
TimothyMothra Sep 9, 2023
6a52fcf
Merge branch 'main' into 4484_newMetrics
TimothyMothra Sep 11, 2023
7c11aac
Update src/OpenTelemetry.Instrumentation.AspNetCore/README.md
TimothyMothra Sep 11, 2023
9ab958c
cleanup Readme after suggestion conflict
TimothyMothra Sep 12, 2023
3646823
update changelog
TimothyMothra Sep 12, 2023
314c9d9
update changelog
TimothyMothra Sep 12, 2023
255b2da
refactor tests
TimothyMothra Sep 12, 2023
12d10cf
Update src/OpenTelemetry.Instrumentation.AspNetCore/README.md
TimothyMothra Sep 12, 2023
c38a93e
update changelog
TimothyMothra Sep 12, 2023
26ea138
Merge branch '4484_newMetrics' of https://github.com/TimothyMothra/op…
TimothyMothra Sep 12, 2023
1fd022b
Merge branch 'main' into 4484_newMetrics
TimothyMothra Sep 12, 2023
1f787be
markdownlint
TimothyMothra Sep 12, 2023
0b257af
rewrite changelog
TimothyMothra Sep 13, 2023
4987540
rewrite Readme
TimothyMothra Sep 13, 2023
0cfa424
markdownlint
TimothyMothra Sep 13, 2023
163d4d7
Merge branch 'main' into 4484_newMetrics
TimothyMothra Sep 13, 2023
80df848
rewrite changelog
TimothyMothra Sep 14, 2023
9f83898
Merge branch '4484_newMetrics' of https://github.com/TimothyMothra/op…
TimothyMothra Sep 14, 2023
744a980
edit changelog & readme
TimothyMothra Sep 14, 2023
1d694bc
update
TimothyMothra Sep 14, 2023
390d563
Merge branch 'main' into 4484_newMetrics
TimothyMothra Sep 14, 2023
6d4bb65
Merge branch '4484_newMetrics' of https://github.com/TimothyMothra/op…
TimothyMothra Sep 14, 2023
0015a20
feedback
TimothyMothra Sep 14, 2023
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
29 changes: 29 additions & 0 deletions src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,35 @@

## Unreleased

* Introduced a new metric, `http.server.request.duration` measured in seconds.
The OTel SDK
[applies custom histogram buckets](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4820)
for this metric to comply with the
[Semantic Convention for Http Metrics](https://github.com/open-telemetry/semantic-conventions/blob/2bad9afad58fbd6b33cc683d1ad1f006e35e4a5d/docs/http/http-metrics.md).
This new metric is only available for users who opt-in to the new
semantic convention by configuring the `OTEL_SEMCONV_STABILITY_OPT_IN`
environment variable to either `http` (to emit only the new metric) or
`http/dup` (to emit both the new and old metrics).
([#4802](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4802))
* New metric: `http.server.request.duration`
* Unit: `s` (seconds)
* Histogram Buckets: `0, 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5,
0.75, 1, 2.5, 5, 7.5, 10`
* Old metric: `http.server.duration`
* Unit: `ms` (milliseconds)
* Histogram Buckets: `0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500,
5000, 7500, 10000`

Note: the older `http.server.duration` metric and
`OTEL_SEMCONV_STABILITY_OPT_IN` environment variable will eventually be
removed after the HTTP semantic conventions are marked stable.
At which time this instrumentation can publish a stable release. Refer to
the specification for more information regarding the new HTTP semantic
conventions for both
[spans](https://github.com/open-telemetry/semantic-conventions/blob/2bad9afad58fbd6b33cc683d1ad1f006e35e4a5d/docs/http/http-spans.md)
and
[metrics](https://github.com/open-telemetry/semantic-conventions/blob/2bad9afad58fbd6b33cc683d1ad1f006e35e4a5d/docs/http/http-metrics.md).

## 1.5.1-beta.1

Released 2023-Jul-20
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,16 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation;

internal sealed class HttpInMetricsListener : ListenerHandler
{
private const string HttpServerDurationMetricName = "http.server.duration";
internal const string HttpServerDurationMetricName = "http.server.duration";
internal const string HttpServerRequestDurationMetricName = "http.server.request.duration";

private const string OnStopEvent = "Microsoft.AspNetCore.Hosting.HttpRequestIn.Stop";
private const string EventName = "OnStopActivity";

private readonly Meter meter;
private readonly AspNetCoreMetricsInstrumentationOptions options;
private readonly Histogram<double> httpServerDuration;
private readonly Histogram<double> httpServerRequestDuration;
private readonly bool emitOldAttributes;
private readonly bool emitNewAttributes;

Expand All @@ -42,109 +45,172 @@ internal HttpInMetricsListener(string name, Meter meter, AspNetCoreMetricsInstru
{
this.meter = meter;
this.options = options;
this.httpServerDuration = meter.CreateHistogram<double>(HttpServerDurationMetricName, "ms", "Measures the duration of inbound HTTP requests.");

this.emitOldAttributes = this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.Old);

this.emitNewAttributes = this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.New);

if (this.emitOldAttributes)
{
this.httpServerDuration = meter.CreateHistogram<double>(HttpServerDurationMetricName, "ms", "Measures the duration of inbound HTTP requests.");
}

if (this.emitNewAttributes)
{
this.httpServerRequestDuration = meter.CreateHistogram<double>(HttpServerRequestDurationMetricName, "s", "Measures the duration of inbound HTTP requests.");
}
}

public override void OnEventWritten(string name, object payload)
{
if (name == OnStopEvent)
{
var context = payload as HttpContext;
if (context == null)
if (this.emitOldAttributes)
{
AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInMetricsListener), EventName, HttpServerDurationMetricName);
return;
this.OnEventWritten_Old(name, payload);
}

try
if (this.emitNewAttributes)
{
if (this.options.Filter?.Invoke(HttpServerDurationMetricName, context) == false)
{
AspNetCoreInstrumentationEventSource.Log.RequestIsFilteredOut(nameof(HttpInMetricsListener), EventName, HttpServerDurationMetricName);
return;
}
this.OnEventWritten_New(name, payload);
}
catch (Exception ex)
}
}

public void OnEventWritten_Old(string name, object payload)
{
var context = payload as HttpContext;
if (context == null)
{
AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInMetricsListener), EventName, HttpServerDurationMetricName);
return;
}

try
{
if (this.options.Filter?.Invoke(HttpServerDurationMetricName, context) == false)
{
AspNetCoreInstrumentationEventSource.Log.RequestFilterException(nameof(HttpInMetricsListener), EventName, HttpServerDurationMetricName, ex);
AspNetCoreInstrumentationEventSource.Log.RequestIsFilteredOut(nameof(HttpInMetricsListener), EventName, HttpServerDurationMetricName);
return;
}
}
catch (Exception ex)
{
AspNetCoreInstrumentationEventSource.Log.RequestFilterException(nameof(HttpInMetricsListener), EventName, HttpServerDurationMetricName, ex);
return;
}

// TODO: Prometheus pulls metrics by invoking the /metrics endpoint. Decide if it makes sense to suppress this.
// Below is just a temporary way of achieving this suppression for metrics (we should consider suppressing traces too).
// If we want to suppress activity from Prometheus then we should use SuppressInstrumentationScope.
if (context.Request.Path.HasValue && context.Request.Path.Value.Contains("metrics"))
{
return;
}

TagList tags = default;

// TODO: Prometheus pulls metrics by invoking the /metrics endpoint. Decide if it makes sense to suppress this.
// Below is just a temporary way of achieving this suppression for metrics (we should consider suppressing traces too).
// If we want to suppress activity from Prometheus then we should use SuppressInstrumentationScope.
if (context.Request.Path.HasValue && context.Request.Path.Value.Contains("metrics"))
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocol(context.Request.Protocol)));
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpScheme, context.Request.Scheme));
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpMethod, context.Request.Method));
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpStatusCode, TelemetryHelper.GetBoxedStatusCode(context.Response.StatusCode)));

if (context.Request.Host.HasValue)
{
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeNetHostName, context.Request.Host.Host));

if (context.Request.Host.Port is not null && context.Request.Host.Port != 80 && context.Request.Host.Port != 443)
{
return;
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeNetHostPort, context.Request.Host.Port));
}
}

TagList tags = default;

// see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/trace/semantic_conventions/http.md
if (this.emitOldAttributes)
#if NET6_0_OR_GREATER
var route = (context.GetEndpoint() as RouteEndpoint)?.RoutePattern.RawText;
if (!string.IsNullOrEmpty(route))
{
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpRoute, route));
}
#endif
if (this.options.Enrich != null)
{
try
{
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocol(context.Request.Protocol)));
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpScheme, context.Request.Scheme));
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpMethod, context.Request.Method));
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpStatusCode, TelemetryHelper.GetBoxedStatusCode(context.Response.StatusCode)));

if (context.Request.Host.HasValue)
{
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeNetHostName, context.Request.Host.Host));

if (context.Request.Host.Port is not null && context.Request.Host.Port != 80 && context.Request.Host.Port != 443)
{
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeNetHostPort, context.Request.Host.Port));
}
}
this.options.Enrich(HttpServerDurationMetricName, context, ref tags);
}
catch (Exception ex)
{
AspNetCoreInstrumentationEventSource.Log.EnrichmentException(nameof(HttpInMetricsListener), EventName, HttpServerDurationMetricName, ex);
}
}

// see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.21.0/docs/http/http-spans.md
if (this.emitNewAttributes)
// We are relying here on ASP.NET Core to set duration before writing the stop event.
// https://github.com/dotnet/aspnetcore/blob/d6fa351048617ae1c8b47493ba1abbe94c3a24cf/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs#L449
// TODO: Follow up with .NET team if we can continue to rely on this behavior.
this.httpServerDuration.Record(Activity.Current.Duration.TotalMilliseconds, tags);
}

public void OnEventWritten_New(string name, object payload)
{
var context = payload as HttpContext;
if (context == null)
{
AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInMetricsListener), EventName, HttpServerRequestDurationMetricName);
return;
}

try
{
if (this.options.Filter?.Invoke(HttpServerRequestDurationMetricName, context) == false)
{
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeNetworkProtocolVersion, HttpTagHelper.GetFlavorTagValueFromProtocol(context.Request.Protocol)));
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeUrlScheme, context.Request.Scheme));
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpRequestMethod, context.Request.Method));
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(context.Response.StatusCode)));

if (context.Request.Host.HasValue)
{
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeServerAddress, context.Request.Host.Host));

if (context.Request.Host.Port is not null && context.Request.Host.Port != 80 && context.Request.Host.Port != 443)
{
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeServerPort, context.Request.Host.Port));
}
}
AspNetCoreInstrumentationEventSource.Log.RequestIsFilteredOut(nameof(HttpInMetricsListener), EventName, HttpServerRequestDurationMetricName);
return;
}
}
catch (Exception ex)
{
AspNetCoreInstrumentationEventSource.Log.RequestFilterException(nameof(HttpInMetricsListener), EventName, HttpServerRequestDurationMetricName, ex);
return;
}

// TODO: Prometheus pulls metrics by invoking the /metrics endpoint. Decide if it makes sense to suppress this.
// Below is just a temporary way of achieving this suppression for metrics (we should consider suppressing traces too).
// If we want to suppress activity from Prometheus then we should use SuppressInstrumentationScope.
if (context.Request.Path.HasValue && context.Request.Path.Value.Contains("metrics"))
{
return;
}

TagList tags = default;

// see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.21.0/docs/http/http-spans.md
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeNetworkProtocolVersion, HttpTagHelper.GetFlavorTagValueFromProtocol(context.Request.Protocol)));
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeUrlScheme, context.Request.Scheme));
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpRequestMethod, context.Request.Method));
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(context.Response.StatusCode)));

#if NET6_0_OR_GREATER
var route = (context.GetEndpoint() as RouteEndpoint)?.RoutePattern.RawText;
if (!string.IsNullOrEmpty(route))
var route = (context.GetEndpoint() as RouteEndpoint)?.RoutePattern.RawText;
if (!string.IsNullOrEmpty(route))
{
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpRoute, route));
}
#endif
if (this.options.Enrich != null)
{
try
{
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpRoute, route));
this.options.Enrich(HttpServerRequestDurationMetricName, context, ref tags);
}
#endif
if (this.options.Enrich != null)
catch (Exception ex)
{
try
{
this.options.Enrich(HttpServerDurationMetricName, context, ref tags);
}
catch (Exception ex)
{
AspNetCoreInstrumentationEventSource.Log.EnrichmentException(nameof(HttpInMetricsListener), EventName, HttpServerDurationMetricName, ex);
}
AspNetCoreInstrumentationEventSource.Log.EnrichmentException(nameof(HttpInMetricsListener), EventName, HttpServerRequestDurationMetricName, ex);
}

// We are relying here on ASP.NET Core to set duration before writing the stop event.
// https://github.com/dotnet/aspnetcore/blob/d6fa351048617ae1c8b47493ba1abbe94c3a24cf/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs#L449
// TODO: Follow up with .NET team if we can continue to rely on this behavior.
this.httpServerDuration.Record(Activity.Current.Duration.TotalMilliseconds, tags);
}

// We are relying here on ASP.NET Core to set duration before writing the stop event.
// https://github.com/dotnet/aspnetcore/blob/d6fa351048617ae1c8b47493ba1abbe94c3a24cf/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs#L449
// TODO: Follow up with .NET team if we can continue to rely on this behavior.
this.httpServerRequestDuration.Record(Activity.Current.Duration.TotalSeconds, tags);
}
}
30 changes: 24 additions & 6 deletions src/OpenTelemetry.Instrumentation.AspNetCore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,31 @@ public void ConfigureServices(IServiceCollection services)

#### List of metrics produced

The instrumentation is implemented based on [metrics semantic
conventions](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#metric-httpserverduration).
Currently, the instrumentation supports the following metric.
A different metric is emitted depending on whether a user opts-in to the new
Http Semantic Conventions using `OTEL_SEMCONV_STABILITY_OPT_IN`.

| Name | Instrument Type | Unit | Description |
|-------|-----------------|------|-------------|
| `http.server.duration` | Histogram | `ms` | Measures the duration of inbound HTTP requests. |
* By default, the instrumentation emits the following metric.

| Name | Instrument Type | Unit | Description | Attributes |
|-------|-----------------|------|-------------|------------|
| `http.server.duration` | Histogram | `ms` | Measures the duration of inbound HTTP requests. | http.flavor, http.scheme, http.method, http.status_code, net.host.name, net.host.port, http.route |

* If user sets the environment variable to `http`, the instrumentation emits
the following metric.

| Name | Instrument Type | Unit | Description | Attributes |
|-------|-----------------|------|-------------|------------|
| `http.server.request.duration` | Histogram | `s` | Measures the duration of inbound HTTP requests. | network.protocol.version, url.scheme, http.request.method, http.response.status_code, http.route |

This metric is emitted in `seconds` as per the semantic convention. While
TimothyMothra marked this conversation as resolved.
Show resolved Hide resolved
the convention [recommends using custom histogram buckets](https://github.com/open-telemetry/semantic-conventions/blob/2bad9afad58fbd6b33cc683d1ad1f006e35e4a5d/docs/http/http-metrics.md)
, this feature is not yet available via .NET Metrics API.
A [workaround](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4820)
has been included in OTel SDK starting version `1.6.0` which applies
recommended buckets by default for `http.server.request.duration`.

* If user sets the environment variable to `http/dup`, the instrumentation
emits both `http.server.duration` and `http.server.request.duration`.

## Advanced configuration

Expand Down
Loading