Skip to content

Commit d3640d5

Browse files
authored
Add a diagnostic source event that fires when a route is matched (#11685)
* Add a diagnostic source event that fires when a route is matched - Usually more information becomes available about a request once route is matched. This event shoud allow diagnositc systems to enlighten the typical "begin request" metadata to include more information about the matched route and more importantly the selected endpoint and associated metadata. * Update src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs Co-Authored-By: campersau <[email protected]> * PR feedback and test fixes
1 parent 661fded commit d3640d5

File tree

4 files changed

+82
-31
lines changed

4 files changed

+82
-31
lines changed

src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System.Diagnostics;
45
using Microsoft.AspNetCore.Builder;
56
using Microsoft.AspNetCore.Http;
67
using Microsoft.Extensions.DependencyInjection;
@@ -16,12 +17,7 @@ public class ComponentEndpointRouteBuilderExtensionsTest
1617
public void MapBlazorHub_WiresUp_UnderlyingHub()
1718
{
1819
// Arrange
19-
var applicationBuilder = new ApplicationBuilder(
20-
new ServiceCollection()
21-
.AddLogging()
22-
.AddSingleton(Mock.Of<IHostApplicationLifetime>())
23-
.AddSignalR().Services
24-
.AddServerSideBlazor().Services.BuildServiceProvider());
20+
var applicationBuilder = CreateAppBuilder();
2521
var called = false;
2622

2723
// Act
@@ -40,12 +36,7 @@ public void MapBlazorHub_WiresUp_UnderlyingHub()
4036
public void MapBlazorHub_MostGeneralOverload_MapsUnderlyingHub()
4137
{
4238
// Arrange
43-
var applicationBuilder = new ApplicationBuilder(
44-
new ServiceCollection()
45-
.AddLogging()
46-
.AddSingleton(Mock.Of<IHostApplicationLifetime>())
47-
.AddSignalR().Services
48-
.AddServerSideBlazor().Services.BuildServiceProvider());
39+
var applicationBuilder = CreateAppBuilder();
4940
var called = false;
5041

5142
// Act
@@ -59,5 +50,23 @@ public void MapBlazorHub_MostGeneralOverload_MapsUnderlyingHub()
5950
// Assert
6051
Assert.True(called);
6152
}
53+
54+
private IApplicationBuilder CreateAppBuilder()
55+
{
56+
var services = new ServiceCollection();
57+
services.AddSingleton(Mock.Of<IHostApplicationLifetime>());
58+
services.AddLogging();
59+
services.AddOptions();
60+
var listener = new DiagnosticListener("Microsoft.AspNetCore");
61+
services.AddSingleton(listener);
62+
services.AddSingleton<DiagnosticSource>(listener);
63+
services.AddRouting();
64+
services.AddSignalR();
65+
services.AddServerSideBlazor();
66+
67+
var serviceProvder = services.BuildServiceProvider();
68+
69+
return new ApplicationBuilder(serviceProvder);
70+
}
6271
}
6372
}

src/Http/Routing/src/EndpointRoutingMiddleware.cs

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Diagnostics;
56
using System.Runtime.CompilerServices;
67
using System.Threading;
78
using System.Threading.Tasks;
@@ -13,9 +14,12 @@ namespace Microsoft.AspNetCore.Routing
1314
{
1415
internal sealed class EndpointRoutingMiddleware
1516
{
17+
private const string DiagnosticsEndpointMatchedKey = "Microsoft.AspNetCore.Routing.EndpointMatched";
18+
1619
private readonly MatcherFactory _matcherFactory;
1720
private readonly ILogger _logger;
1821
private readonly EndpointDataSource _endpointDataSource;
22+
private readonly DiagnosticListener _diagnosticListener;
1923
private readonly RequestDelegate _next;
2024

2125
private Task<Matcher> _initializationTask;
@@ -24,31 +28,18 @@ public EndpointRoutingMiddleware(
2428
MatcherFactory matcherFactory,
2529
ILogger<EndpointRoutingMiddleware> logger,
2630
IEndpointRouteBuilder endpointRouteBuilder,
31+
DiagnosticListener diagnosticListener,
2732
RequestDelegate next)
2833
{
29-
if (matcherFactory == null)
30-
{
31-
throw new ArgumentNullException(nameof(matcherFactory));
32-
}
33-
34-
if (logger == null)
35-
{
36-
throw new ArgumentNullException(nameof(logger));
37-
}
38-
3934
if (endpointRouteBuilder == null)
4035
{
4136
throw new ArgumentNullException(nameof(endpointRouteBuilder));
4237
}
4338

44-
if (next == null)
45-
{
46-
throw new ArgumentNullException(nameof(next));
47-
}
48-
49-
_matcherFactory = matcherFactory;
50-
_logger = logger;
51-
_next = next;
39+
_matcherFactory = matcherFactory ?? throw new ArgumentNullException(nameof(matcherFactory));
40+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
41+
_diagnosticListener = diagnosticListener ?? throw new ArgumentNullException(nameof(diagnosticListener));
42+
_next = next ?? throw new ArgumentNullException(nameof(next));
5243

5344
_endpointDataSource = new CompositeEndpointDataSource(endpointRouteBuilder.DataSources);
5445
}
@@ -106,6 +97,13 @@ private Task SetRoutingAndContinue(HttpContext httpContext)
10697
}
10798
else
10899
{
100+
// Raise an event if the route matched
101+
if (_diagnosticListener.IsEnabled() && _diagnosticListener.IsEnabled(DiagnosticsEndpointMatchedKey))
102+
{
103+
// We're just going to send the HttpContext since it has all of the relevant information
104+
_diagnosticListener.Write(DiagnosticsEndpointMatchedKey, httpContext);
105+
}
106+
109107
Log.MatchSuccess(_logger, endpoint);
110108
}
111109

src/Http/Routing/test/UnitTests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Diagnostics;
67
using System.Threading.Tasks;
78
using Microsoft.AspNetCore.Http;
89
using Microsoft.AspNetCore.Http.Features;
@@ -311,6 +312,9 @@ private IServiceProvider CreateServices(MatcherFactory matcherFactory)
311312
services.AddLogging();
312313
services.AddOptions();
313314
services.AddRouting();
315+
var listener = new DiagnosticListener("Microsoft.AspNetCore");
316+
services.AddSingleton(listener);
317+
services.AddSingleton<DiagnosticSource>(listener);
314318

315319
var serviceProvder = services.BuildServiceProvider();
316320

src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics;
57
using System.Threading.Tasks;
68
using Microsoft.AspNetCore.Builder;
79
using Microsoft.AspNetCore.Http;
@@ -57,16 +59,26 @@ public async Task Invoke_OnCall_WritesToConfiguredLogger()
5759
{
5860
// Arrange
5961
var expectedMessage = "Request matched endpoint 'Test endpoint'";
62+
bool eventFired = false;
6063

6164
var sink = new TestSink(
6265
TestSink.EnableWithTypeName<EndpointRoutingMiddleware>,
6366
TestSink.EnableWithTypeName<EndpointRoutingMiddleware>);
6467
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
68+
var listener = new DiagnosticListener("TestListener");
69+
70+
using var subscription = listener.Subscribe(new DelegateObserver(pair =>
71+
{
72+
eventFired = true;
73+
74+
Assert.Equal("Microsoft.AspNetCore.Routing.EndpointMatched", pair.Key);
75+
Assert.IsAssignableFrom<HttpContext>(pair.Value);
76+
}));
6577

6678
var httpContext = CreateHttpContext();
6779

6880
var logger = new Logger<EndpointRoutingMiddleware>(loggerFactory);
69-
var middleware = CreateMiddleware(logger);
81+
var middleware = CreateMiddleware(logger, listener: listener);
7082

7183
// Act
7284
await middleware.Invoke(httpContext);
@@ -75,6 +87,7 @@ public async Task Invoke_OnCall_WritesToConfiguredLogger()
7587
Assert.Empty(sink.Scopes);
7688
var write = Assert.Single(sink.Writes);
7789
Assert.Equal(expectedMessage, write.State?.ToString());
90+
Assert.True(eventFired);
7891
}
7992

8093
[Fact]
@@ -159,19 +172,46 @@ private HttpContext CreateHttpContext()
159172
private EndpointRoutingMiddleware CreateMiddleware(
160173
Logger<EndpointRoutingMiddleware> logger = null,
161174
MatcherFactory matcherFactory = null,
175+
DiagnosticListener listener = null,
162176
RequestDelegate next = null)
163177
{
164178
next ??= c => Task.CompletedTask;
165179
logger ??= new Logger<EndpointRoutingMiddleware>(NullLoggerFactory.Instance);
166180
matcherFactory ??= new TestMatcherFactory(true);
181+
listener ??= new DiagnosticListener("Microsoft.AspNetCore");
167182

168183
var middleware = new EndpointRoutingMiddleware(
169184
matcherFactory,
170185
logger,
171186
new DefaultEndpointRouteBuilder(Mock.Of<IApplicationBuilder>()),
187+
listener,
172188
next);
173189

174190
return middleware;
175191
}
192+
193+
private class DelegateObserver : IObserver<KeyValuePair<string, object>>
194+
{
195+
private readonly Action<KeyValuePair<string, object>> _onNext;
196+
197+
public DelegateObserver(Action<KeyValuePair<string, object>> onNext)
198+
{
199+
_onNext = onNext;
200+
}
201+
public void OnCompleted()
202+
{
203+
204+
}
205+
206+
public void OnError(Exception error)
207+
{
208+
209+
}
210+
211+
public void OnNext(KeyValuePair<string, object> value)
212+
{
213+
_onNext(value);
214+
}
215+
}
176216
}
177217
}

0 commit comments

Comments
 (0)