Skip to content

Commit 2ba0409

Browse files
committed
Add disable HTTP metrics endpoint metadata
1 parent 83573c7 commit 2ba0409

File tree

7 files changed

+164
-5
lines changed

7 files changed

+164
-5
lines changed

src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ public void RequestEnd(HttpContext httpContext, Exception? exception, HostingApp
155155
if (context.MetricsEnabled)
156156
{
157157
var endpoint = HttpExtensions.GetOriginalEndpoint(httpContext);
158+
var disableHttpRequestDurationMetric = endpoint?.Metadata.GetMetadata<IDisableHttpMetricsMetadata>() != null;
158159
var route = endpoint?.Metadata.GetMetadata<IRouteDiagnosticsMetadata>()?.Route;
159160

160161
Debug.Assert(context.MetricsTagsFeature != null, "MetricsTagsFeature should be set if MetricsEnabled is true.");
@@ -169,7 +170,8 @@ public void RequestEnd(HttpContext httpContext, Exception? exception, HostingApp
169170
exception,
170171
context.MetricsTagsFeature.TagsList,
171172
startTimestamp,
172-
currentTimestamp);
173+
currentTimestamp,
174+
disableHttpRequestDurationMetric);
173175
}
174176

175177
if (reachedPipelineEnd)

src/Hosting/Hosting/src/Internal/HostingMetrics.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public void RequestStart(string scheme, string method)
4141
_activeRequestsCounter.Add(1, tags);
4242
}
4343

44-
public void RequestEnd(string protocol, string scheme, string method, string? route, int statusCode, bool unhandledRequest, Exception? exception, List<KeyValuePair<string, object?>>? customTags, long startTimestamp, long currentTimestamp)
44+
public void RequestEnd(string protocol, string scheme, string method, string? route, int statusCode, bool unhandledRequest, Exception? exception, List<KeyValuePair<string, object?>>? customTags, long startTimestamp, long currentTimestamp, bool disableHttpRequestDurationMetric)
4545
{
4646
var tags = new TagList();
4747
InitializeRequestTags(ref tags, scheme, method);
@@ -52,7 +52,7 @@ public void RequestEnd(string protocol, string scheme, string method, string? ro
5252
_activeRequestsCounter.Add(-1, tags);
5353
}
5454

55-
if (_requestDuration.Enabled)
55+
if (!disableHttpRequestDurationMetric && _requestDuration.Enabled)
5656
{
5757
if (TryGetHttpVersion(protocol, out var httpVersion))
5858
{

src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
using System.Diagnostics.Metrics;
66
using System.Diagnostics.Tracing;
77
using System.Reflection;
8+
using Microsoft.AspNetCore.Builder;
89
using Microsoft.AspNetCore.Http;
910
using Microsoft.AspNetCore.Http.Features;
11+
using Microsoft.AspNetCore.Http.Metadata;
1012
using Microsoft.AspNetCore.Internal;
1113
using Microsoft.AspNetCore.InternalTesting;
1214
using Microsoft.Extensions.Diagnostics.Metrics;
@@ -182,7 +184,6 @@ public void Metrics_RequestChanges_OriginalValuesUsed()
182184

183185
var testMeterFactory = new TestMeterFactory();
184186
using var activeRequestsCollector = new MetricCollector<long>(testMeterFactory, HostingMetrics.MeterName, "http.server.active_requests");
185-
using var requestDurationCollector = new MetricCollector<double>(testMeterFactory, HostingMetrics.MeterName, "http.server.request.duration");
186187

187188
// Act
188189
var hostingApplication = CreateApplication(out var features, eventSource: hostingEventSource, meterFactory: testMeterFactory, configure: c =>
@@ -233,6 +234,126 @@ public void Metrics_RequestChanges_OriginalValuesUsed()
233234
Assert.Null(context.MetricsTagsFeature.Protocol);
234235
}
235236

237+
[Fact]
238+
public void Metrics_Route_RouteTagReported()
239+
{
240+
// Arrange
241+
var hostingEventSource = new HostingEventSource(Guid.NewGuid().ToString());
242+
243+
var testMeterFactory = new TestMeterFactory();
244+
using var activeRequestsCollector = new MetricCollector<long>(testMeterFactory, HostingMetrics.MeterName, "http.server.active_requests");
245+
using var requestDurationCollector = new MetricCollector<double>(testMeterFactory, HostingMetrics.MeterName, "http.server.request.duration");
246+
247+
// Act
248+
var hostingApplication = CreateApplication(out var features, eventSource: hostingEventSource, meterFactory: testMeterFactory, configure: c =>
249+
{
250+
c.Request.Protocol = "1.1";
251+
c.Request.Scheme = "http";
252+
c.Request.Method = "POST";
253+
c.Request.Host = new HostString("localhost");
254+
c.Request.Path = "/hello";
255+
c.Request.ContentType = "text/plain";
256+
c.Request.ContentLength = 1024;
257+
});
258+
var context = hostingApplication.CreateContext(features);
259+
260+
Assert.Collection(activeRequestsCollector.GetMeasurementSnapshot(),
261+
m =>
262+
{
263+
Assert.Equal(1, m.Value);
264+
Assert.Equal("http", m.Tags["url.scheme"]);
265+
Assert.Equal("POST", m.Tags["http.request.method"]);
266+
});
267+
268+
context.HttpContext.SetEndpoint(new Endpoint(
269+
c => Task.CompletedTask,
270+
new EndpointMetadataCollection(new TestRouteDiagnosticsMetadata()),
271+
"Test endpoint"));
272+
273+
hostingApplication.DisposeContext(context, null);
274+
275+
// Assert
276+
Assert.Collection(activeRequestsCollector.GetMeasurementSnapshot(),
277+
m =>
278+
{
279+
Assert.Equal(1, m.Value);
280+
Assert.Equal("http", m.Tags["url.scheme"]);
281+
Assert.Equal("POST", m.Tags["http.request.method"]);
282+
},
283+
m =>
284+
{
285+
Assert.Equal(-1, m.Value);
286+
Assert.Equal("http", m.Tags["url.scheme"]);
287+
Assert.Equal("POST", m.Tags["http.request.method"]);
288+
});
289+
Assert.Collection(requestDurationCollector.GetMeasurementSnapshot(),
290+
m =>
291+
{
292+
Assert.True(m.Value > 0);
293+
Assert.Equal("hello/{name}", m.Tags["http.route"]);
294+
});
295+
}
296+
297+
[Fact]
298+
public void Metrics_DisableHttpMetrics_NoMetrics()
299+
{
300+
// Arrange
301+
var hostingEventSource = new HostingEventSource(Guid.NewGuid().ToString());
302+
303+
var testMeterFactory = new TestMeterFactory();
304+
using var activeRequestsCollector = new MetricCollector<long>(testMeterFactory, HostingMetrics.MeterName, "http.server.active_requests");
305+
using var requestDurationCollector = new MetricCollector<double>(testMeterFactory, HostingMetrics.MeterName, "http.server.request.duration");
306+
307+
// Act
308+
var hostingApplication = CreateApplication(out var features, eventSource: hostingEventSource, meterFactory: testMeterFactory, configure: c =>
309+
{
310+
c.Request.Protocol = "1.1";
311+
c.Request.Scheme = "http";
312+
c.Request.Method = "POST";
313+
c.Request.Host = new HostString("localhost");
314+
c.Request.Path = "/hello";
315+
c.Request.ContentType = "text/plain";
316+
c.Request.ContentLength = 1024;
317+
});
318+
var context = hostingApplication.CreateContext(features);
319+
320+
Assert.Collection(activeRequestsCollector.GetMeasurementSnapshot(),
321+
m =>
322+
{
323+
Assert.Equal(1, m.Value);
324+
Assert.Equal("http", m.Tags["url.scheme"]);
325+
Assert.Equal("POST", m.Tags["http.request.method"]);
326+
});
327+
328+
context.HttpContext.SetEndpoint(new Endpoint(
329+
c => Task.CompletedTask,
330+
new EndpointMetadataCollection(new TestRouteDiagnosticsMetadata(), new DisableHttpMetricsAttribute()),
331+
"Test endpoint"));
332+
333+
hostingApplication.DisposeContext(context, null);
334+
335+
// Assert
336+
Assert.Collection(activeRequestsCollector.GetMeasurementSnapshot(),
337+
m =>
338+
{
339+
Assert.Equal(1, m.Value);
340+
Assert.Equal("http", m.Tags["url.scheme"]);
341+
Assert.Equal("POST", m.Tags["http.request.method"]);
342+
},
343+
m =>
344+
{
345+
Assert.Equal(-1, m.Value);
346+
Assert.Equal("http", m.Tags["url.scheme"]);
347+
Assert.Equal("POST", m.Tags["http.request.method"]);
348+
});
349+
Assert.Empty(requestDurationCollector.GetMeasurementSnapshot());
350+
}
351+
352+
private sealed class TestRouteDiagnosticsMetadata : IRouteDiagnosticsMetadata
353+
{
354+
public string Route { get; } = "hello/{name}";
355+
}
356+
236357
[Fact]
237358
public void DisposeContextDoesNotThrowWhenContextScopeIsNull()
238359
{
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.Http.Metadata;
5+
6+
public interface IDisableHttpMetricsMetadata
7+
{
8+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Http.Metadata;
5+
6+
namespace Microsoft.AspNetCore.Http;
7+
8+
public sealed class DisableHttpMetricsAttribute : Attribute, IDisableHttpMetricsMetadata
9+
{
10+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Http;
5+
6+
namespace Microsoft.AspNetCore.Builder;
7+
8+
/// <summary>
9+
/// HTTP metrics extension methods for <see cref="IEndpointConventionBuilder"/>.
10+
/// </summary>
11+
public static class HttpMetricsEndpointConventionBuilderExtensions
12+
{
13+
public static IEndpointConventionBuilder DisableHttpMetrics(this IEndpointConventionBuilder builder, int? statusCode = null)
14+
{
15+
builder.Add(b => b.Metadata.Add(new DisableHttpMetricsAttribute()));
16+
return builder;
17+
}
18+
}

src/Http/Routing/src/EndpointNameAttribute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using Microsoft.AspNetCore.Http;

0 commit comments

Comments
 (0)