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 Filter Support For ASP.NET Instrumented Metrics #1426

Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ OpenTelemetry.Instrumentation.AspNet.AspNetMetricsInstrumentationOptions.AspNetM
OpenTelemetry.Instrumentation.AspNet.AspNetMetricsInstrumentationOptions.Enrich.get -> OpenTelemetry.Instrumentation.AspNet.AspNetMetricsInstrumentationOptions.EnrichFunc?
OpenTelemetry.Instrumentation.AspNet.AspNetMetricsInstrumentationOptions.Enrich.set -> void
OpenTelemetry.Instrumentation.AspNet.AspNetMetricsInstrumentationOptions.EnrichFunc
OpenTelemetry.Instrumentation.AspNet.AspNetMetricsInstrumentationOptions.Filter.get -> System.Func<System.Web.HttpContext!, bool>?
OpenTelemetry.Instrumentation.AspNet.AspNetMetricsInstrumentationOptions.Filter.set -> void
OpenTelemetry.Metrics.MeterProviderBuilderExtensions
OpenTelemetry.Trace.TracerProviderBuilderExtensions
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddAspNetInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! builder) -> OpenTelemetry.Metrics.MeterProviderBuilder!
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,54 @@
// <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; }
}
// <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;
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; }

/// <summary>
/// Gets or sets a filter function that determines whether or not to
/// collect telemetry on a per request basis.
/// </summary>
/// <remarks>
/// <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>
/// </remarks>
public Func<HttpContext, bool>? Filter { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ public void Dispose()

private void OnStopActivity(Activity activity, HttpContext context)
{
try
{
if (this.options.Filter?.Invoke(context) == false && Activity.Current != null)
{
AspNetInstrumentationEventSource.Log.RequestIsFilteredOut(Activity.Current.OperationName);
return;
}
}
catch (Exception ex)
{
AspNetInstrumentationEventSource.Log.RequestFilterException(nameof(HttpInMetricsListener), ex);
return;
}

// TODO: This is just a minimal set of attributes. See the spec for additional attributes:
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#http-server
var tags = new TagList
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ public static class MeterProviderBuilderExtensions
/// <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);

AddAspNetInstrumentation(builder, configure: null);
/// <summary>
/// Enables the incoming requests automatic data collection for ASP.NET.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,25 @@ namespace OpenTelemetry.Instrumentation.AspNet.Tests;
public class HttpInMetricsListenerTests
{
[Theory]
[InlineData("http://localhost/", 0, null, null, "http")]
[InlineData("https://localhost/", 0, null, null, "https")]
[InlineData("http://localhost/api/value", 0, null, null, "http")]
[InlineData("http://localhost/api/value", 1, "{controller}/{action}", null, "http")]
[InlineData("http://localhost/api/value", 2, "{controller}/{action}", null, "http")]
[InlineData("http://localhost/api/value", 3, "{controller}/{action}", null, "http")]
[InlineData("http://localhost/api/value", 4, "{controller}/{action}", null, "http")]
[InlineData("http://localhost:8080/api/value", 0, null, null, "http")]
[InlineData("http://localhost:8080/api/value", 1, "{controller}/{action}", null, "http")]
[InlineData("http://localhost:8080/api/value", 3, "{controller}/{action}", "enrich", "http")]
[InlineData("http://localhost:8080/api/value", 3, "{controller}/{action}", "throw", "http")]
[InlineData("http://localhost:8080/api/value", 3, "{controller}/{action}", null, "http")]
[InlineData("http://localhost/", 0, null, null, null, "http")]
[InlineData("https://localhost/", 0, null, null, null, "https")]
[InlineData("http://localhost/api/value", 0, null, null, null, "http")]
[InlineData("http://localhost/api/value", 1, "{controller}/{action}", null, null, "http")]
[InlineData("http://localhost/api/value", 2, "{controller}/{action}", null, null, "http")]
[InlineData("http://localhost/api/value", 3, "{controller}/{action}", null, null, "http")]
[InlineData("http://localhost/api/value", 4, "{controller}/{action}", null, null, "http")]
[InlineData("http://localhost:8080/api/value", 0, null, null, null, "http")]
[InlineData("http://localhost:8080/api/value", 1, "{controller}/{action}", null, null, "http")]
[InlineData("http://localhost:8080/api/value", 3, "{controller}/{action}", "enrich", null, "http")]
[InlineData("http://localhost:8080/api/value", 3, "{controller}/{action}", "throw", null, "http")]
[InlineData("http://localhost:8080/api/value", 3, "{controller}/{action}", null, "on", "http")]
[InlineData("http://localhost:8080/api/value", 3, "{controller}/{action}", null, null, "http")]
public void AspNetMetricTagsAreCollectedSuccessfully(
string url,
int routeType,
string routeTemplate,
string enrichMode,
string filterMode,
string expectedScheme)
{
double duration = 0;
Expand Down Expand Up @@ -81,6 +83,17 @@ public void AspNetMetricTagsAreCollectedSuccessfully(
tags.Add("enriched", "true");
}
};

options.Filter += (HttpContext httpContext) =>
{
// If filterMode is on, filter out the current metric
if (filterMode == "on")
{
return false;
}

return true;
};
})
.AddInMemoryExporter(exportedItems)
.Build();
Expand All @@ -91,6 +104,13 @@ public void AspNetMetricTagsAreCollectedSuccessfully(

meterProvider.ForceFlush();

// If filtering is enabled, we should have collected no data.
if (filterMode == "on")
{
Assert.Empty(exportedItems);
return;
}

Assert.Single(exportedItems);

var metricPoints = new List<MetricPoint>();
Expand Down Expand Up @@ -118,6 +138,12 @@ public void AspNetMetricTagsAreCollectedSuccessfully(
expectedTagCount++;
}

var expectedMetricPoints = 1;
if (filterMode == "on")
{
expectedMetricPoints++;
}

Assert.Equal(expectedTagCount, metricPoints[0].Tags.Count);
Dictionary<string, object?> tags = new(metricPoint.Tags.Count);
foreach (var tag in metricPoint.Tags)
Expand Down
Loading