Skip to content

Commit 7befd2d

Browse files
authored
[Instrumentation.AspNet] Generate metrics independently from traces (#2970)
1 parent 6ddcd9a commit 7befd2d

19 files changed

+378
-185
lines changed

opentelemetry-dotnet-contrib.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{1FCC8E
243243
src\Shared\GrpcStatusCanonicalCode.cs = src\Shared\GrpcStatusCanonicalCode.cs
244244
src\Shared\GrpcTagHelper.cs = src\Shared\GrpcTagHelper.cs
245245
src\Shared\Guard.cs = src\Shared\Guard.cs
246+
src\Shared\InstrumentationHandleManager.cs = src\Shared\InstrumentationHandleManager.cs
246247
src\Shared\IServerCertificateValidationEventSource.cs = src\Shared\IServerCertificateValidationEventSource.cs
247248
src\Shared\IsExternalInit.cs = src\Shared\IsExternalInit.cs
248249
src\Shared\ListenerHandler.cs = src\Shared\ListenerHandler.cs

src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/.publicApi/PublicAPI.Unshipped.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Dispose() -> void
55
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Init(System.Web.HttpApplication! context) -> void
66
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.TelemetryHttpModule() -> void
77
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModuleOptions
8-
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModuleOptions.OnExceptionCallback.get -> System.Action<System.Diagnostics.Activity!, System.Web.HttpContext!, System.Exception!>?
8+
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModuleOptions.OnExceptionCallback.get -> System.Action<System.Diagnostics.Activity?, System.Web.HttpContext!, System.Exception!>?
99
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModuleOptions.OnExceptionCallback.set -> void
10-
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModuleOptions.OnRequestStartedCallback.get -> System.Action<System.Diagnostics.Activity!, System.Web.HttpContext!>?
10+
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModuleOptions.OnRequestStartedCallback.get -> System.Action<System.Diagnostics.Activity?, System.Web.HttpContext!>?
1111
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModuleOptions.OnRequestStartedCallback.set -> void
12-
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModuleOptions.OnRequestStoppedCallback.get -> System.Action<System.Diagnostics.Activity!, System.Web.HttpContext!>?
12+
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModuleOptions.OnRequestStoppedCallback.get -> System.Action<System.Diagnostics.Activity?, System.Web.HttpContext!>?
1313
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModuleOptions.OnRequestStoppedCallback.set -> void
1414
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModuleOptions.TextMapPropagator.get -> OpenTelemetry.Context.Propagation.TextMapPropagator!
1515
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModuleOptions.TextMapPropagator.set -> void

src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/ActivityHelper.cs

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public static bool HasStarted(HttpContext context, out Activity? aspNetActivity)
6060
/// <param name="context"><see cref="HttpContext"/>.</param>
6161
/// <param name="onRequestStartedCallback">Callback action.</param>
6262
/// <returns>New root activity.</returns>
63-
public static Activity? StartAspNetActivity(TextMapPropagator textMapPropagator, HttpContext context, Action<Activity, HttpContext>? onRequestStartedCallback)
63+
public static Activity? StartAspNetActivity(TextMapPropagator textMapPropagator, HttpContext context, Action<Activity?, HttpContext>? onRequestStartedCallback)
6464
{
6565
var propagationContext = textMapPropagator.Extract(default, context.Request, HttpRequestHeaderValuesGetter);
6666

@@ -109,6 +109,7 @@ public static bool HasStarted(HttpContext context, out Activity? aspNetActivity)
109109
}
110110
else
111111
{
112+
onRequestStartedCallback?.Invoke(activity, context);
112113
context.Items[ContextKey] = StartedButNotSampledObj;
113114
}
114115

@@ -123,14 +124,15 @@ public static bool HasStarted(HttpContext context, out Activity? aspNetActivity)
123124
/// <param name="context"><see cref="HttpContext"/>.</param>
124125
/// <param name="onRequestStoppedCallback">Callback action.</param>
125126
[MethodImpl(MethodImplOptions.AggressiveInlining)]
126-
public static void StopAspNetActivity(TextMapPropagator textMapPropagator, Activity? aspNetActivity, HttpContext context, Action<Activity, HttpContext>? onRequestStoppedCallback)
127+
public static void StopAspNetActivity(TextMapPropagator textMapPropagator, Activity? aspNetActivity, HttpContext context, Action<Activity?, HttpContext>? onRequestStoppedCallback)
127128
{
128129
if (aspNetActivity == null)
129130
{
130131
Debug.Assert(context.Items[ContextKey] == StartedButNotSampledObj, "Context item is not StartedButNotSampledObj.");
131132

132133
// This is the case where a start was called but no activity was
133134
// created due to a sampling decision.
135+
onRequestStoppedCallback?.Invoke(aspNetActivity, context);
134136
context.Items[ContextKey] = null;
135137
return;
136138
}
@@ -178,21 +180,18 @@ public static void StopAspNetActivity(TextMapPropagator textMapPropagator, Activ
178180
/// <param name="exception"><see cref="Exception"/>.</param>
179181
/// <param name="onExceptionCallback">Callback action.</param>
180182
[MethodImpl(MethodImplOptions.AggressiveInlining)]
181-
public static void WriteActivityException(Activity? aspNetActivity, HttpContext context, Exception exception, Action<Activity, HttpContext, Exception>? onExceptionCallback)
183+
public static void WriteActivityException(Activity? aspNetActivity, HttpContext context, Exception exception, Action<Activity?, HttpContext, Exception>? onExceptionCallback)
182184
{
183-
if (aspNetActivity != null)
185+
try
184186
{
185-
try
186-
{
187-
onExceptionCallback?.Invoke(aspNetActivity, context, exception);
188-
}
189-
catch (Exception callbackEx)
190-
{
191-
AspNetTelemetryEventSource.Log.CallbackException(aspNetActivity, "OnException", callbackEx);
192-
}
193-
194-
AspNetTelemetryEventSource.Log.ActivityException(aspNetActivity, exception);
187+
onExceptionCallback?.Invoke(aspNetActivity, context, exception);
188+
}
189+
catch (Exception callbackEx)
190+
{
191+
AspNetTelemetryEventSource.Log.CallbackException(aspNetActivity, "OnException", callbackEx);
195192
}
193+
194+
AspNetTelemetryEventSource.Log.ActivityException(aspNetActivity, exception);
196195
}
197196

198197
/// <summary>

src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/AspNetTelemetryEventSource.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,20 @@ public void ActivityRestored(Activity activity)
4646
}
4747

4848
[NonEvent]
49-
public void ActivityException(Activity activity, Exception ex)
49+
public void ActivityException(Activity? activity, Exception ex)
5050
{
5151
if (this.IsEnabled(EventLevel.Error, EventKeywords.All))
5252
{
53-
this.ActivityException(activity.Id, ex.ToInvariantString());
53+
this.ActivityException(activity?.Id, ex.ToInvariantString());
5454
}
5555
}
5656

5757
[NonEvent]
58-
public void CallbackException(Activity activity, string eventName, Exception ex)
58+
public void CallbackException(Activity? activity, string eventName, Exception ex)
5959
{
6060
if (this.IsEnabled(EventLevel.Error, EventKeywords.All))
6161
{
62-
this.CallbackException(activity.Id, eventName, ex.ToInvariantString());
62+
this.CallbackException(activity?.Id, eventName, ex.ToInvariantString());
6363
}
6464
}
6565

src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## Unreleased
44

5+
* **Breaking Change**: Modified request lifecycle callbacks to always fire.
6+
This is required as part of making ASP.NET metrics generation
7+
independent from traces.
8+
([#2970](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/2970))
9+
510
## 1.12.0-beta.1
611

712
Released 2025-May-05

src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/TelemetryHttpModuleOptions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,16 @@ public TextMapPropagator TextMapPropagator
3737
/// <summary>
3838
/// Gets or sets a callback action to be fired when a request is started.
3939
/// </summary>
40-
public Action<Activity, HttpContext>? OnRequestStartedCallback { get; set; }
40+
public Action<Activity?, HttpContext>? OnRequestStartedCallback { get; set; }
4141

4242
/// <summary>
4343
/// Gets or sets a callback action to be fired when a request is stopped.
4444
/// </summary>
45-
public Action<Activity, HttpContext>? OnRequestStoppedCallback { get; set; }
45+
public Action<Activity?, HttpContext>? OnRequestStoppedCallback { get; set; }
4646

4747
/// <summary>
4848
/// Gets or sets a callback action to be fired when an unhandled
4949
/// exception is thrown processing a request.
5050
/// </summary>
51-
public Action<Activity, HttpContext, Exception>? OnExceptionCallback { get; set; }
51+
public Action<Activity?, HttpContext, Exception>? OnExceptionCallback { get; set; }
5252
}

src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentation.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
// Copyright The OpenTelemetry Authors
22
// SPDX-License-Identifier: Apache-2.0
33

4+
using System.Diagnostics.Metrics;
5+
using System.Reflection;
46
using OpenTelemetry.Instrumentation.AspNet.Implementation;
7+
using OpenTelemetry.Internal;
58

69
namespace OpenTelemetry.Instrumentation.AspNet;
710

@@ -10,17 +13,33 @@ namespace OpenTelemetry.Instrumentation.AspNet;
1013
/// </summary>
1114
internal sealed class AspNetInstrumentation : IDisposable
1215
{
16+
public static readonly AspNetInstrumentation Instance = new();
17+
18+
public static readonly Assembly Assembly = typeof(HttpInListener).Assembly;
19+
public static readonly AssemblyName AssemblyName = Assembly.GetName();
20+
public static readonly string MeterName = AssemblyName.Name!;
21+
public static readonly Meter Meter = new(MeterName, Assembly.GetPackageVersion());
22+
public static readonly Histogram<double> HttpServerDuration = Meter.CreateHistogram(
23+
"http.server.request.duration",
24+
unit: "s",
25+
description: "Duration of HTTP server requests.",
26+
advice: new InstrumentAdvice<double> { HistogramBucketBoundaries = [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] });
27+
28+
public readonly InstrumentationHandleManager HandleManager = new();
1329
private readonly HttpInListener httpInListener;
1430

1531
/// <summary>
1632
/// Initializes a new instance of the <see cref="AspNetInstrumentation"/> class.
1733
/// </summary>
18-
/// <param name="options">Configuration options for ASP.NET instrumentation.</param>
19-
public AspNetInstrumentation(AspNetTraceInstrumentationOptions options)
34+
private AspNetInstrumentation()
2035
{
21-
this.httpInListener = new HttpInListener(options);
36+
this.httpInListener = new();
2237
}
2338

39+
public AspNetTraceInstrumentationOptions TraceOptions { get; set; } = new();
40+
41+
public AspNetMetricsInstrumentationOptions MetricOptions { get; set; } = new();
42+
2443
/// <inheritdoc/>
2544
public void Dispose()
2645
{

src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentationMeterProviderBuilderExtensions.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,13 @@ public static MeterProviderBuilder AddAspNetInstrumentation(
4343
services.ConfigureOpenTelemetryMeterProvider((sp, meterProviderBuilder) =>
4444
{
4545
var options = sp.GetRequiredService<IOptionsMonitor<AspNetMetricsInstrumentationOptions>>().Get(name: null);
46+
AspNetInstrumentation.Instance.MetricOptions = options;
4647

47-
meterProviderBuilder.AddInstrumentation(() => new AspNetMetrics(options));
48-
meterProviderBuilder.AddMeter(AspNetMetrics.InstrumentationName);
48+
meterProviderBuilder.AddInstrumentation(() =>
49+
{
50+
return AspNetInstrumentation.Instance.HandleManager.AddMetricHandle();
51+
});
52+
meterProviderBuilder.AddMeter(AspNetInstrumentation.MeterName);
4953
});
5054
});
5155
}

src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentationTracerProviderBuilderExtensions.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,12 @@ public static TracerProviderBuilder AddAspNetInstrumentation(
4646
services.ConfigureOpenTelemetryTracerProvider((sp, tracerProviderBuilder) =>
4747
{
4848
var options = sp.GetRequiredService<IOptionsMonitor<AspNetTraceInstrumentationOptions>>().Get(name: null);
49+
AspNetInstrumentation.Instance.TraceOptions = options;
4950

50-
tracerProviderBuilder.AddInstrumentation(() => new AspNetInstrumentation(options));
51+
tracerProviderBuilder.AddInstrumentation(() =>
52+
{
53+
return AspNetInstrumentation.Instance.HandleManager.AddTracingHandle();
54+
});
5155
tracerProviderBuilder.AddSource(TelemetryHttpModule.AspNetSourceName);
5256
});
5357
});

src/OpenTelemetry.Instrumentation.AspNet/AspNetMetrics.cs

Lines changed: 0 additions & 41 deletions
This file was deleted.

0 commit comments

Comments
 (0)