Remove Enrich and Filter for ASP.NET Core metrics (#4981)

Co-authored-by: Cijo Thomas <cithomas@microsoft.com>
Co-authored-by: Utkarsh Umesan Pillai <66651184+utpilla@users.noreply.github.com>
Co-authored-by: Mikel Blanchard <mblanchard@macrosssoftware.com>
This commit is contained in:
Vishwesh Bankwar 2023-10-23 15:44:15 -07:00 committed by GitHub
parent d07d03086a
commit faa70be1b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 28 additions and 287 deletions

View File

@ -12,19 +12,9 @@ OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreInstrumentationOptions.Filter
OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreInstrumentationOptions.Filter.set -> void
OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreInstrumentationOptions.RecordException.get -> bool
OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreInstrumentationOptions.RecordException.set -> void
OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreMetricsInstrumentationOptions
OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreMetricsInstrumentationOptions.AspNetCoreMetricEnrichmentFunc
OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreMetricsInstrumentationOptions.AspNetCoreMetricsInstrumentationOptions() -> void
OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreMetricsInstrumentationOptions.Enrich.get -> OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreMetricsInstrumentationOptions.AspNetCoreMetricEnrichmentFunc
OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreMetricsInstrumentationOptions.Enrich.set -> void
OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreMetricsInstrumentationOptions.Filter.get -> System.Func<string, Microsoft.AspNetCore.Http.HttpContext, bool>
OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreMetricsInstrumentationOptions.Filter.set -> void
OpenTelemetry.Metrics.MeterProviderBuilderExtensions
OpenTelemetry.Trace.TracerProviderBuilderExtensions
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddAspNetCoreInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder builder) -> OpenTelemetry.Metrics.MeterProviderBuilder
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddAspNetCoreInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder builder, string name, System.Action<OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreMetricsInstrumentationOptions> configureAspNetCoreInstrumentationOptions) -> OpenTelemetry.Metrics.MeterProviderBuilder
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddAspNetCoreInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action<OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreMetricsInstrumentationOptions> configureAspNetCoreInstrumentationOptions) -> OpenTelemetry.Metrics.MeterProviderBuilder
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddAspNetCoreInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder) -> OpenTelemetry.Trace.TracerProviderBuilder
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddAspNetCoreInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder, string name, System.Action<OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreInstrumentationOptions> configureAspNetCoreInstrumentationOptions) -> OpenTelemetry.Trace.TracerProviderBuilder
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddAspNetCoreInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder, System.Action<OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreInstrumentationOptions> configureAspNetCoreInstrumentationOptions) -> OpenTelemetry.Trace.TracerProviderBuilder
virtual OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreMetricsInstrumentationOptions.AspNetCoreMetricEnrichmentFunc.Invoke(string name, Microsoft.AspNetCore.Http.HttpContext context, ref System.Diagnostics.TagList tags) -> void

View File

@ -15,7 +15,6 @@
// </copyright>
using System.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using static OpenTelemetry.Internal.HttpSemanticConventionHelper;
@ -24,7 +23,7 @@ namespace OpenTelemetry.Instrumentation.AspNetCore;
/// <summary>
/// Options for metrics requests instrumentation.
/// </summary>
public class AspNetCoreMetricsInstrumentationOptions
internal sealed class AspNetCoreMetricsInstrumentationOptions
{
internal readonly HttpSemanticConvention HttpSemanticConvention;
@ -42,38 +41,5 @@ public class AspNetCoreMetricsInstrumentationOptions
this.HttpSemanticConvention = GetSemanticConventionOptIn(configuration);
}
/// <summary>
/// Delegate for enrichment of recorded metric with additional tags.
/// </summary>
/// <param name="name">The name of the metric being enriched.</param>
/// <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 AspNetCoreMetricEnrichmentFunc(string name, HttpContext context, ref TagList tags);
/// <summary>
/// Gets or sets a filter function that determines whether or not to
/// collect telemetry on a per request basis.
/// </summary>
/// <remarks>
/// Notes:
/// <list type="bullet">
/// <item>The first parameter is the name of the metric being
/// filtered.</item>
/// <item>The return value for the filter function is interpreted as:
/// <list type="bullet">
/// <item>If filter returns <see langword="true" />, the request is
/// collected.</item>
/// <item>If filter returns <see langword="false" /> or throws an
/// exception the request is NOT collected.</item>
/// </list></item>
/// </list>
/// </remarks>
public Func<string, HttpContext, bool> Filter { get; set; }
/// <summary>
/// Gets or sets an function to enrich a recorded metric with additional custom tags.
/// </summary>
public AspNetCoreMetricEnrichmentFunc Enrich { get; set; }
}

View File

@ -86,6 +86,29 @@ metric. This change only affects users setting `OTEL_SEMCONV_STABILITY_OPT_IN`
to `http` or `http/dup`.
([#4934](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4934))
* **Breaking**: Removed `Enrich` and `Filter` support for **metrics**
instrumentation. With this change, `AspNetCoreMetricsInstrumentationOptions`
is no longer available.
([#4981](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4981))
* `Enrich` migration:
An enrichment API for the `http.server.request.duration` metric is available
inside AspNetCore for users targeting .NET 8.0 (or newer). For details see:
[Enrich the ASP.NET Core request
metric](https://learn.microsoft.com/aspnet/core/log-mon/metrics/metrics?view=aspnetcore-8.0#enrich-the-aspnet-core-request-metric).
* `Filter` migration:
There is no comparable filter mechanism currently available for any .NET
version. Please [share your
feedback](https://github.com/open-telemetry/opentelemetry-dotnet/issues/4982)
if you are impacted by this feature gap.
> **Note**
> The [View API](https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/docs/metrics/customizing-the-sdk#select-specific-tags)
may be used to drop dimensions.
## 1.5.1-beta.1
Released 2023-Jul-20

View File

@ -92,10 +92,4 @@ internal sealed class AspNetCoreInstrumentationEventSource : EventSource
{
this.WriteEvent(5, handlerName, eventName, ex);
}
[Event(6, Message = "'{0}' is not supported for .NET8.0 and above targets", Level = EventLevel.Warning)]
public void UnsupportedOption(string optionName)
{
this.WriteEvent(6, optionName);
}
}

View File

@ -88,20 +88,6 @@ internal sealed class HttpInMetricsListener : ListenerHandler
return;
}
try
{
if (this.options.Filter?.Invoke(HttpServerDurationMetricName, context) == false)
{
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.
@ -134,17 +120,6 @@ internal sealed class HttpInMetricsListener : ListenerHandler
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpRoute, route));
}
#endif
if (this.options.Enrich != null)
{
try
{
this.options.Enrich(HttpServerDurationMetricName, context, ref tags);
}
catch (Exception ex)
{
AspNetCoreInstrumentationEventSource.Log.EnrichmentException(nameof(HttpInMetricsListener), EventName, HttpServerDurationMetricName, 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
@ -161,20 +136,6 @@ internal sealed class HttpInMetricsListener : ListenerHandler
return;
}
try
{
if (this.options.Filter?.Invoke(HttpServerRequestDurationMetricName, context) == false)
{
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.
@ -199,17 +160,6 @@ internal sealed class HttpInMetricsListener : ListenerHandler
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpRoute, route));
}
#endif
if (this.options.Enrich != null)
{
try
{
this.options.Enrich(HttpServerRequestDurationMetricName, context, ref tags);
}
catch (Exception 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

View File

@ -17,9 +17,9 @@
#if !NET8_0_OR_GREATER
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
#endif
using OpenTelemetry.Instrumentation.AspNetCore;
using OpenTelemetry.Instrumentation.AspNetCore.Implementation;
#endif
using OpenTelemetry.Internal;
namespace OpenTelemetry.Metrics;
@ -42,52 +42,11 @@ public static class MeterProviderBuilderExtensions
#if NET8_0_OR_GREATER
return builder.ConfigureMeters();
#else
return AddAspNetCoreInstrumentation(builder, name: null, configureAspNetCoreInstrumentationOptions: null);
#endif
}
/// <summary>
/// Enables the incoming requests automatic data collection for ASP.NET Core.
/// </summary>
/// <param name="builder"><see cref="MeterProviderBuilder"/> being configured.</param>
/// <param name="configureAspNetCoreInstrumentationOptions">Callback action for configuring <see cref="AspNetCoreMetricsInstrumentationOptions"/>.</param>
/// <returns>The instance of <see cref="MeterProviderBuilder"/> to chain the calls.</returns>
public static MeterProviderBuilder AddAspNetCoreInstrumentation(
this MeterProviderBuilder builder,
Action<AspNetCoreMetricsInstrumentationOptions> configureAspNetCoreInstrumentationOptions)
=> AddAspNetCoreInstrumentation(builder, name: null, configureAspNetCoreInstrumentationOptions);
/// <summary>
/// Enables the incoming requests automatic data collection for ASP.NET Core.
/// </summary>
/// <param name="builder"><see cref="MeterProviderBuilder"/> being configured.</param>
/// <param name="name">Name which is used when retrieving options.</param>
/// <param name="configureAspNetCoreInstrumentationOptions">Callback action for configuring <see cref="AspNetCoreMetricsInstrumentationOptions"/>.</param>
/// <returns>The instance of <see cref="MeterProviderBuilder"/> to chain the calls.</returns>
public static MeterProviderBuilder AddAspNetCoreInstrumentation(
this MeterProviderBuilder builder,
string name,
Action<AspNetCoreMetricsInstrumentationOptions> configureAspNetCoreInstrumentationOptions)
{
Guard.ThrowIfNull(builder);
#if NET8_0_OR_GREATER
AspNetCoreInstrumentationEventSource.Log.UnsupportedOption(nameof(AspNetCoreMetricsInstrumentationOptions));
return builder.ConfigureMeters();
#else
// Note: Warm-up the status code mapping.
_ = TelemetryHelper.BoxedStatusCodes;
name ??= Options.DefaultName;
builder.ConfigureServices(services =>
{
if (configureAspNetCoreInstrumentationOptions != null)
{
services.Configure(name, configureAspNetCoreInstrumentationOptions);
}
services.RegisterOptionsFactory(configuration => new AspNetCoreMetricsInstrumentationOptions(configuration));
});
@ -95,12 +54,11 @@ public static class MeterProviderBuilderExtensions
builder.AddInstrumentation(sp =>
{
var options = sp.GetRequiredService<IOptionsMonitor<AspNetCoreMetricsInstrumentationOptions>>().Get(name);
var options = sp.GetRequiredService<IOptionsMonitor<AspNetCoreMetricsInstrumentationOptions>>().Get(Options.DefaultName);
// TODO: Add additional options to AspNetCoreMetricsInstrumentationOptions ?
// RecordException - probably doesn't make sense for metric instrumentation
// EnableGrpcAspNetCoreSupport - this instrumentation will also need to also handle gRPC requests
return new AspNetCoreMetrics(options);
});

View File

@ -64,37 +64,4 @@ public class DependencyInjectionConfigTests
Assert.True(optionsPickedFromDI);
}
#if !NET8_0_OR_GREATER
[Theory]
[InlineData(null)]
[InlineData("CustomName")]
public void TestMetricsOptionsDIConfig(string name)
{
name ??= Options.DefaultName;
bool optionsPickedFromDI = false;
void ConfigureTestServices(IServiceCollection services)
{
services.AddOpenTelemetry()
.WithMetrics(builder => builder
.AddAspNetCoreInstrumentation(name, configureAspNetCoreInstrumentationOptions: null));
services.Configure<AspNetCoreMetricsInstrumentationOptions>(name, options =>
{
optionsPickedFromDI = true;
});
}
// Arrange
using (var client = this.factory
.WithWebHostBuilder(builder =>
builder.ConfigureTestServices(ConfigureTestServices))
.CreateClient())
{
}
Assert.True(optionsPickedFromDI);
}
#endif
}

View File

@ -14,22 +14,18 @@
// limitations under the License.
// </copyright>
#if !NET8_0_OR_GREATER
using System.Diagnostics;
#endif
#if NET8_0_OR_GREATER
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.Builder;
#endif
using Microsoft.AspNetCore.Hosting;
#if NET8_0_OR_GREATER
using Microsoft.AspNetCore.Http;
#endif
using Microsoft.AspNetCore.Mvc.Testing;
#if NET8_0_OR_GREATER
using Microsoft.AspNetCore.RateLimiting;
#endif
#if !NET8_0_OR_GREATER
using Microsoft.AspNetCore.TestHost;
#endif
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@ -360,109 +356,6 @@ public class MetricTests
expectedRoutes: new List<string> { "api/Values", "api/Values/{id}" },
expectedTagsCount: 6);
}
[Fact]
public async Task MetricNotCollectedWhenFilterIsApplied()
{
var metricItems = new List<Metric>();
void ConfigureTestServices(IServiceCollection services)
{
this.meterProvider = Sdk.CreateMeterProviderBuilder()
.AddAspNetCoreInstrumentation(opt => opt.Filter = (name, ctx) => ctx.Request.Path != "/api/values/2")
.AddInMemoryExporter(metricItems)
.Build();
}
using (var client = this.factory
.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(ConfigureTestServices);
builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
})
.CreateClient())
{
using var response1 = await client.GetAsync("/api/values").ConfigureAwait(false);
using var response2 = await client.GetAsync("/api/values/2").ConfigureAwait(false);
response1.EnsureSuccessStatusCode();
response2.EnsureSuccessStatusCode();
}
// We need to let End callback execute as it is executed AFTER response was returned.
// In unit tests environment there may be a lot of parallel unit tests executed, so
// giving some breezing room for the End callback to complete
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
this.meterProvider.Dispose();
var requestMetrics = metricItems
.Where(item => item.Name == "http.server.duration")
.ToArray();
var metric = Assert.Single(requestMetrics);
// Assert single because we filtered out one route
var metricPoint = Assert.Single(GetMetricPoints(metric));
AssertMetricPoint_Old(metricPoint);
}
[Fact]
public async Task MetricEnrichedWithCustomTags()
{
var tagsToAdd = new KeyValuePair<string, object>[]
{
new("custom_tag_1", 1),
new("custom_tag_2", "one"),
};
var metricItems = new List<Metric>();
void ConfigureTestServices(IServiceCollection services)
{
this.meterProvider = Sdk.CreateMeterProviderBuilder()
.AddAspNetCoreInstrumentation(opt => opt.Enrich = (string _, HttpContext _, ref TagList tags) =>
{
foreach (var keyValuePair in tagsToAdd)
{
tags.Add(keyValuePair);
}
})
.AddInMemoryExporter(metricItems)
.Build();
}
using (var client = this.factory
.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(ConfigureTestServices);
builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
})
.CreateClient())
{
using var response = await client.GetAsync("/api/values").ConfigureAwait(false);
response.EnsureSuccessStatusCode();
}
// We need to let End callback execute as it is executed AFTER response was returned.
// In unit tests environment there may be a lot of parallel unit tests executed, so
// giving some breezing room for the End callback to complete
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
this.meterProvider.Dispose();
var requestMetrics = metricItems
.Where(item => item.Name == "http.server.duration")
.ToArray();
var metric = Assert.Single(requestMetrics);
var metricPoint = Assert.Single(GetMetricPoints(metric));
var tags = AssertMetricPoint_Old(metricPoint, expectedTagsCount: StandardTagsCount + 2);
Assert.Contains(tagsToAdd[0], tags);
Assert.Contains(tagsToAdd[1], tags);
}
#endif
public void Dispose()