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] Add metric enrich functionality #1407

Merged
merged 12 commits into from
Nov 8, 2023
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
OpenTelemetry.Instrumentation.AspNet.AspNetInstrumentationOptions
OpenTelemetry.Instrumentation.AspNet.AspNetInstrumentationOptions
OpenTelemetry.Instrumentation.AspNet.AspNetInstrumentationOptions.AspNetInstrumentationOptions() -> void
OpenTelemetry.Instrumentation.AspNet.AspNetInstrumentationOptions.Enrich.get -> System.Action<System.Diagnostics.Activity!, string!, object!>?
OpenTelemetry.Instrumentation.AspNet.AspNetInstrumentationOptions.Enrich.set -> void
OpenTelemetry.Instrumentation.AspNet.AspNetInstrumentationOptions.Filter.get -> System.Func<System.Web.HttpContext!, bool>?
OpenTelemetry.Instrumentation.AspNet.AspNetInstrumentationOptions.Filter.set -> void
OpenTelemetry.Instrumentation.AspNet.AspNetInstrumentationOptions.RecordException.get -> bool
OpenTelemetry.Instrumentation.AspNet.AspNetInstrumentationOptions.RecordException.set -> void
OpenTelemetry.Instrumentation.AspNet.AspNetMetricsInstrumentationOptions
OpenTelemetry.Instrumentation.AspNet.AspNetMetricsInstrumentationOptions.AspNetMetricsInstrumentationOptions() -> void
OpenTelemetry.Instrumentation.AspNet.AspNetMetricsInstrumentationOptions.Enrich.get -> OpenTelemetry.Instrumentation.AspNet.AspNetMetricsInstrumentationOptions.EnrichFunc?
OpenTelemetry.Instrumentation.AspNet.AspNetMetricsInstrumentationOptions.Enrich.set -> void
OpenTelemetry.Instrumentation.AspNet.AspNetMetricsInstrumentationOptions.EnrichFunc
OpenTelemetry.Metrics.MeterProviderBuilderExtensions
OpenTelemetry.Trace.TracerProviderBuilderExtensions
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddAspNetInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! builder) -> OpenTelemetry.Metrics.MeterProviderBuilder!
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddAspNetInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, System.Action<OpenTelemetry.Instrumentation.AspNet.AspNetMetricsInstrumentationOptions!>? configure) -> OpenTelemetry.Metrics.MeterProviderBuilder!
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddAspNetInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder) -> OpenTelemetry.Trace.TracerProviderBuilder!
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddAspNetInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, System.Action<OpenTelemetry.Instrumentation.AspNet.AspNetInstrumentationOptions!>? configure) -> OpenTelemetry.Trace.TracerProviderBuilder!
5 changes: 3 additions & 2 deletions src/OpenTelemetry.Instrumentation.AspNet/AspNetMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ internal sealed class AspNetMetrics : IDisposable
/// <summary>
/// Initializes a new instance of the <see cref="AspNetMetrics"/> class.
/// </summary>
public AspNetMetrics()
/// <param name="options">The metrics configuration options.</param>
public AspNetMetrics(AspNetMetricsInstrumentationOptions options)
{
this.meter = new Meter(InstrumentationName, InstrumentationVersion);
this.httpInMetricsListener = new HttpInMetricsListener(this.meter);
this.httpInMetricsListener = new HttpInMetricsListener(this.meter, options);
}

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// <copyright file="AspNetMetricsInstrumentationOptions.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System.Diagnostics;
using System.Web;

namespace OpenTelemetry.Instrumentation.AspNet;

/// <summary>
/// Options for metric instrumentation.
/// </summary>
public sealed class AspNetMetricsInstrumentationOptions
{
/// <summary>
/// Delegate for enrichment of recorded metric with additional tags.
/// </summary>
/// <param name="context"><see cref="HttpContext"/>: the HttpContext object. Both Request and Response are available.</param>
/// <param name="tags"><see cref="TagList"/>: List of current tags. You can add additional tags to this list. </param>
public delegate void EnrichFunc(HttpContext context, ref TagList tags);

/// <summary>
/// Gets or sets an function to enrich a recorded metric with additional custom tags.
/// </summary>
public EnrichFunc? Enrich { get; set; }
}
6 changes: 6 additions & 0 deletions src/OpenTelemetry.Instrumentation.AspNet/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## Unreleased

* Added enrich functionality to metric instrumentation
([#1407](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1407)).

* New overload of `AddAspNetInstrumentation` now accepts a configuration delegate.
* The `Enrich` can be used to add additional metric attributes.

## 1.6.0-beta.2

Released 2023-Nov-06
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ namespace OpenTelemetry.Instrumentation.AspNet.Implementation;
internal sealed class HttpInMetricsListener : IDisposable
{
private readonly Histogram<double> httpServerDuration;
private readonly AspNetMetricsInstrumentationOptions options;

public HttpInMetricsListener(Meter meter)
public HttpInMetricsListener(Meter meter, AspNetMetricsInstrumentationOptions options)
{
this.httpServerDuration = meter.CreateHistogram<double>("http.server.duration", "ms", "Measures the duration of inbound HTTP requests.");
TelemetryHttpModule.Options.OnRequestStoppedCallback += this.OnStopActivity;
this.options = options;
}

public void Dispose()
Expand All @@ -48,6 +50,18 @@ private void OnStopActivity(Activity activity, HttpContext context)
{ SemanticConventions.AttributeHttpStatusCode, context.Response.StatusCode },
};

if (this.options.Enrich is not null)
{
try
{
this.options.Enrich(context, ref tags);
}
catch (Exception ex)
{
AspNetInstrumentationEventSource.Log.EnrichmentException(nameof(HttpInMetricsListener), ex);
}
}

this.httpServerDuration.Record(activity.Duration.TotalMilliseconds, tags);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// limitations under the License.
// </copyright>

using System;
using OpenTelemetry.Instrumentation.AspNet;
using OpenTelemetry.Internal;

Expand All @@ -29,12 +30,25 @@ public static class MeterProviderBuilderExtensions
/// </summary>
/// <param name="builder"><see cref="MeterProviderBuilder"/> being configured.</param>
/// <returns>The instance of <see cref="MeterProviderBuilder"/> to chain the calls.</returns>
public static MeterProviderBuilder AddAspNetInstrumentation(this MeterProviderBuilder builder) =>
AddAspNetInstrumentation(builder, configure: null);

/// <summary>
/// Enables the incoming requests automatic data collection for ASP.NET.
/// </summary>
/// <param name="builder"><see cref="MeterProviderBuilder"/> being configured.</param>
/// <param name="configure">Callback action for configuring <see cref="AspNetMetricsInstrumentationOptions"/>.</param>
/// <returns>The instance of <see cref="MeterProviderBuilder"/> to chain the calls.</returns>
public static MeterProviderBuilder AddAspNetInstrumentation(
this MeterProviderBuilder builder)
this MeterProviderBuilder builder,
Action<AspNetMetricsInstrumentationOptions>? configure)
{
Guard.ThrowIfNull(builder);

var options = new AspNetMetricsInstrumentationOptions();
configure?.Invoke(options);

builder.AddMeter(AspNetMetrics.InstrumentationName);
return builder.AddInstrumentation(() => new AspNetMetrics());
return builder.AddInstrumentation(() => new AspNetMetrics(options));
}
}
32 changes: 29 additions & 3 deletions src/OpenTelemetry.Instrumentation.AspNet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,13 @@ Currently, the instrumentation supports the following metric.
|-------|-----------------|------|-------------|
| `http.server.duration` | Histogram | `ms` | Measures the duration of inbound HTTP requests. |

## Advanced configuration
## Advanced trace configuration

This instrumentation can be configured to change the default behavior by using
`AspNetInstrumentationOptions`, which allows configuring `Filter` as explained
below.

### Filter
### Trace Filter

This instrumentation by default collects all the incoming http requests. It
allows filtering of requests by using the `Filter` function in
Expand Down Expand Up @@ -162,7 +162,7 @@ instrumentation. OpenTelemetry has a concept of a
[Sampler](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#sampling),
and the `Filter` option does the filtering *before* the Sampler is invoked.

### Enrich
### Trace Enrich

This option allows one to enrich the activity with additional information from
the raw `HttpRequest`, `HttpResponse` objects. The `Enrich` action is called
Expand Down Expand Up @@ -208,6 +208,32 @@ This instrumentation automatically sets Activity Status to Error if an unhandled
exception is thrown. Additionally, `RecordException` feature may be turned on,
to store the exception to the Activity itself as ActivityEvent.

## Advanced metric configuration

This instrumentation can be configured to change the default behavior by using
`AspNetMetricsInstrumentationOptions` as explained below.

### Metric Enrich

This option allows one to enrich the metric with additional information from
the `HttpContext`. The `Enrich` action is always called unless the metric was
filtered. The callback allows for modifying the tag list. If the callback
throws an exception the metric will still be recorded.

```csharp
this.meterProvider = Sdk.CreateMeterProviderBuilder()
.AddAspNetInstrumentation(options => options.Enrich =
(HttpContext context, ref TagList tags) =>
{
// Add request content type to the metric tags.
if (!string.IsNullOrEmpty(context.Request.ContentType))
{
tags.Add("custom.content.type", context.Request.ContentType);
}
})
.Build();
```

## References

* [ASP.NET](https://dotnet.microsoft.com/apps/aspnet)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,49 +58,7 @@ public void AspNetRequestsAreCollectedSuccessfully(
string? filter = null,
bool recordException = false)
{
RouteData routeData;
switch (routeType)
{
case 0: // WebForm, no route data.
routeData = new RouteData();
break;
case 1: // Traditional MVC.
case 2: // Attribute routing MVC.
case 3: // Traditional WebAPI.
routeData = new RouteData()
{
Route = new Route(routeTemplate, null),
};
break;
case 4: // Attribute routing WebAPI.
routeData = new RouteData();
var value = new[]
{
new
{
Route = new
{
RouteTemplate = routeTemplate,
},
},
};
routeData.Values.Add(
"MS_SubRoutes",
value);
break;
default:
throw new NotSupportedException();
}

HttpContext.Current = new HttpContext(
new HttpRequest(string.Empty, url, string.Empty)
{
RequestContext = new RequestContext()
{
RouteData = routeData,
},
},
new HttpResponse(new StringWriter()));
HttpContext.Current = RouteTestHelper.BuildHttpContext(url, routeType, routeTemplate);

typeof(HttpRequest).GetField("_wr", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(HttpContext.Current.Request, Mock.Of<HttpWorkerRequest>());

Expand Down
Loading
Loading