Skip to content

Commit

Permalink
Add support for some AspNetCoreMetricsInstrumentationOptions
Browse files Browse the repository at this point in the history
  • Loading branch information
Temppus committed Nov 26, 2022
1 parent 39283c7 commit 67b7c7a
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ 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.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
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using System.Diagnostics.Metrics;
using System.Reflection;
using OpenTelemetry.Instrumentation.AspNetCore.Implementation;
using OpenTelemetry.Internal;

namespace OpenTelemetry.Instrumentation.AspNetCore
{
Expand All @@ -44,13 +45,12 @@ internal sealed class AspNetCoreMetrics : IDisposable
private readonly DiagnosticSourceSubscriber diagnosticSourceSubscriber;
private readonly Meter meter;

/// <summary>
/// Initializes a new instance of the <see cref="AspNetCoreMetrics"/> class.
/// </summary>
public AspNetCoreMetrics()
internal AspNetCoreMetrics(AspNetCoreMetricsInstrumentationOptions options)
{
Guard.ThrowIfNull(options);
this.meter = new Meter(InstrumentationName, InstrumentationVersion);
this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(new HttpInMetricsListener("Microsoft.AspNetCore", this.meter), this.isEnabled);
var metricsListener = new HttpInMetricsListener("Microsoft.AspNetCore", this.meter, options);
this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(metricsListener, this.isEnabled);
this.diagnosticSourceSubscriber.Subscribe();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// <copyright file="AspNetCoreInstrumentationOptions.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;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.AspNetCore.Http;

namespace OpenTelemetry.Instrumentation.AspNetCore
{
/// <summary>
/// Options for metrics requests instrumentation.
/// </summary>
public class AspNetCoreMetricsInstrumentationOptions
{
/// <summary>
/// Gets or sets a Filter function that determines whether or not to collect telemetry about requests on a per request basis.
/// The Filter gets the HttpContext, and should return a boolean.
/// If Filter returns true, the request is collected.
/// If Filter returns false or throw exception, the request is filtered out.
/// </summary>
public Func<HttpContext, bool> Filter { get; set; }

/// <summary>
/// Gets or sets an function to enrich an recorded metric with additional custom tags.
/// </summary>
/// <remarks>
/// <para><see cref="HttpContext"/>: the HttpContext object.</para>
/// </remarks>
public Func<HttpContext, IEnumerable<KeyValuePair<string, object>>> EnrichWithCustomTags { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@
// limitations under the License.
// </copyright>

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Metrics;
using Microsoft.AspNetCore.Http;
#if NET6_0_OR_GREATER
using Microsoft.AspNetCore.Routing;
using OpenTelemetry.Internal;
#endif
using OpenTelemetry.Trace;

Expand All @@ -30,12 +32,14 @@ internal sealed class HttpInMetricsListener : ListenerHandler
private const string OnStopEvent = "Microsoft.AspNetCore.Hosting.HttpRequestIn.Stop";

private readonly Meter meter;
private readonly AspNetCoreMetricsInstrumentationOptions options;
private readonly Histogram<double> httpServerDuration;

public HttpInMetricsListener(string name, Meter meter)
internal HttpInMetricsListener(string name, Meter meter, AspNetCoreMetricsInstrumentationOptions options)
: base(name)
{
this.meter = meter;
this.options = options;
this.httpServerDuration = meter.CreateHistogram<double>("http.server.duration", "ms", "measures the duration of the inbound HTTP request");
}

Expand All @@ -50,6 +54,12 @@ public override void OnEventWritten(string name, object payload)
return;
}

if (this.options.Filter?.Invoke(context) == false)
{
AspNetCoreInstrumentationEventSource.Log.RequestIsFilteredOut(Activity.Current.OperationName);
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.
Expand Down Expand Up @@ -81,6 +91,15 @@ public override void OnEventWritten(string name, object payload)
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpRoute, route));
}
#endif
if (this.options.EnrichWithCustomTags != null)
{
var enrichedTags = this.options.EnrichWithCustomTags(context);

foreach (var keyValuePair in enrichedTags)
{
tags.Add(keyValuePair);
}
}

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

using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenTelemetry.Instrumentation.AspNetCore;
using OpenTelemetry.Internal;

Expand All @@ -31,20 +34,56 @@ public static class MeterProviderBuilderExtensions
/// <returns>The instance of <see cref="MeterProviderBuilder"/> to chain the calls.</returns>
public static MeterProviderBuilder AddAspNetCoreInstrumentation(
this MeterProviderBuilder builder)
=> AddAspNetCoreInstrumentation(builder, name: null, configureAspNetCoreInstrumentationOptions: null);

/// <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);

// TODO: Implement an IDeferredMeterProviderBuilder
name ??= Options.DefaultName;

if (configureAspNetCoreInstrumentationOptions != null)
{
builder.ConfigureServices(services => services.Configure(name, configureAspNetCoreInstrumentationOptions));
}

builder.ConfigureBuilder((sp, builder) =>
{
var options = sp.GetRequiredService<IOptionsMonitor<AspNetCoreMetricsInstrumentationOptions>>().Get(name);

// TODO: Implement an IDeferredMeterProviderBuilder

// TODO: Add AspNetCoreMetricsInstrumentationOptions ?
// RecordException - probably doesn't make sense for metric instrumentation
// EnableGrpcAspNetCoreSupport - this instrumentation will also need to also handle gRPC requests

// TODO: Handle AspNetCoreInstrumentationOptions
// Filter - makes sense for metric instrumentation
// Enrich - do we want a similar kind of functionality for metrics?
// RecordException - probably doesn't make sense for metric instrumentation
// EnableGrpcAspNetCoreSupport - this instrumentation will also need to also handle gRPC requests
var instrumentation = new AspNetCoreMetrics(options);
builder.AddMeter(AspNetCoreMetrics.InstrumentationName);
builder.AddInstrumentation(() => instrumentation);
});

var instrumentation = new AspNetCoreMetrics();
builder.AddMeter(AspNetCoreMetrics.InstrumentationName);
return builder.AddInstrumentation(() => instrumentation);
return builder;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
using Xunit;

Expand All @@ -36,7 +37,7 @@ public DependencyInjectionConfigTests(WebApplicationFactory<Program> factory)
[Theory]
[InlineData(null)]
[InlineData("CustomName")]
public void TestDIConfig(string name)
public void TestTracingOptionsDIConfig(string name)
{
name ??= Options.DefaultName;

Expand All @@ -62,5 +63,35 @@ void ConfigureTestServices(IServiceCollection services)

Assert.True(optionsPickedFromDI);
}

[Theory]
[InlineData(null)]
[InlineData("CustomName")]
public void TestMetricsOptionsDIConfig(string name)
{
name ??= Options.DefaultName;

bool optionsPickedFromDI = false;
void ConfigureTestServices(IServiceCollection services)
{
services.AddOpenTelemetryMetrics(
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);
}
}
}
Loading

0 comments on commit 67b7c7a

Please sign in to comment.