From d3640d59a21e41318662c80f59bfc0887f2d77dd Mon Sep 17 00:00:00 2001 From: David Fowler Date: Mon, 1 Jul 2019 00:13:37 -0700 Subject: [PATCH] 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 * PR feedback and test fixes --- ...onentEndpointRouteBuilderExtensionsTest.cs | 33 +++++++++------ .../Routing/src/EndpointRoutingMiddleware.cs | 34 +++++++-------- ...RoutingApplicationBuilderExtensionsTest.cs | 4 ++ .../EndpointRoutingMiddlewareTest.cs | 42 ++++++++++++++++++- 4 files changed, 82 insertions(+), 31 deletions(-) diff --git a/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs b/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs index ac56285eef..456d9facd7 100644 --- a/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs +++ b/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Diagnostics; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; @@ -16,12 +17,7 @@ namespace Microsoft.AspNetCore.Components.Server.Tests public void MapBlazorHub_WiresUp_UnderlyingHub() { // Arrange - var applicationBuilder = new ApplicationBuilder( - new ServiceCollection() - .AddLogging() - .AddSingleton(Mock.Of()) - .AddSignalR().Services - .AddServerSideBlazor().Services.BuildServiceProvider()); + var applicationBuilder = CreateAppBuilder(); var called = false; // Act @@ -40,12 +36,7 @@ namespace Microsoft.AspNetCore.Components.Server.Tests public void MapBlazorHub_MostGeneralOverload_MapsUnderlyingHub() { // Arrange - var applicationBuilder = new ApplicationBuilder( - new ServiceCollection() - .AddLogging() - .AddSingleton(Mock.Of()) - .AddSignalR().Services - .AddServerSideBlazor().Services.BuildServiceProvider()); + var applicationBuilder = CreateAppBuilder(); var called = false; // Act @@ -59,5 +50,23 @@ namespace Microsoft.AspNetCore.Components.Server.Tests // Assert Assert.True(called); } + + private IApplicationBuilder CreateAppBuilder() + { + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of()); + services.AddLogging(); + services.AddOptions(); + var listener = new DiagnosticListener("Microsoft.AspNetCore"); + services.AddSingleton(listener); + services.AddSingleton(listener); + services.AddRouting(); + services.AddSignalR(); + services.AddServerSideBlazor(); + + var serviceProvder = services.BuildServiceProvider(); + + return new ApplicationBuilder(serviceProvder); + } } } diff --git a/src/Http/Routing/src/EndpointRoutingMiddleware.cs b/src/Http/Routing/src/EndpointRoutingMiddleware.cs index b1fcc00a42..482e86bb6d 100644 --- a/src/Http/Routing/src/EndpointRoutingMiddleware.cs +++ b/src/Http/Routing/src/EndpointRoutingMiddleware.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -13,9 +14,12 @@ namespace Microsoft.AspNetCore.Routing { internal sealed class EndpointRoutingMiddleware { + private const string DiagnosticsEndpointMatchedKey = "Microsoft.AspNetCore.Routing.EndpointMatched"; + private readonly MatcherFactory _matcherFactory; private readonly ILogger _logger; private readonly EndpointDataSource _endpointDataSource; + private readonly DiagnosticListener _diagnosticListener; private readonly RequestDelegate _next; private Task _initializationTask; @@ -24,31 +28,18 @@ namespace Microsoft.AspNetCore.Routing MatcherFactory matcherFactory, ILogger logger, IEndpointRouteBuilder endpointRouteBuilder, + DiagnosticListener diagnosticListener, RequestDelegate next) { - if (matcherFactory == null) - { - throw new ArgumentNullException(nameof(matcherFactory)); - } - - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } - if (endpointRouteBuilder == null) { throw new ArgumentNullException(nameof(endpointRouteBuilder)); } - if (next == null) - { - throw new ArgumentNullException(nameof(next)); - } - - _matcherFactory = matcherFactory; - _logger = logger; - _next = next; + _matcherFactory = matcherFactory ?? throw new ArgumentNullException(nameof(matcherFactory)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _diagnosticListener = diagnosticListener ?? throw new ArgumentNullException(nameof(diagnosticListener)); + _next = next ?? throw new ArgumentNullException(nameof(next)); _endpointDataSource = new CompositeEndpointDataSource(endpointRouteBuilder.DataSources); } @@ -106,6 +97,13 @@ namespace Microsoft.AspNetCore.Routing } else { + // Raise an event if the route matched + if (_diagnosticListener.IsEnabled() && _diagnosticListener.IsEnabled(DiagnosticsEndpointMatchedKey)) + { + // We're just going to send the HttpContext since it has all of the relevant information + _diagnosticListener.Write(DiagnosticsEndpointMatchedKey, httpContext); + } + Log.MatchSuccess(_logger, endpoint); } diff --git a/src/Http/Routing/test/UnitTests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs b/src/Http/Routing/test/UnitTests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs index c9dbbd4032..7f51f6e9a7 100644 --- a/src/Http/Routing/test/UnitTests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs +++ b/src/Http/Routing/test/UnitTests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; @@ -311,6 +312,9 @@ namespace Microsoft.AspNetCore.Builder services.AddLogging(); services.AddOptions(); services.AddRouting(); + var listener = new DiagnosticListener("Microsoft.AspNetCore"); + services.AddSingleton(listener); + services.AddSingleton(listener); var serviceProvder = services.BuildServiceProvider(); diff --git a/src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs b/src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs index 946fd7755f..48e1157195 100644 --- a/src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs +++ b/src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; @@ -57,16 +59,26 @@ namespace Microsoft.AspNetCore.Routing { // Arrange var expectedMessage = "Request matched endpoint 'Test endpoint'"; + bool eventFired = false; var sink = new TestSink( TestSink.EnableWithTypeName, TestSink.EnableWithTypeName); var loggerFactory = new TestLoggerFactory(sink, enabled: true); + var listener = new DiagnosticListener("TestListener"); + + using var subscription = listener.Subscribe(new DelegateObserver(pair => + { + eventFired = true; + + Assert.Equal("Microsoft.AspNetCore.Routing.EndpointMatched", pair.Key); + Assert.IsAssignableFrom(pair.Value); + })); var httpContext = CreateHttpContext(); var logger = new Logger(loggerFactory); - var middleware = CreateMiddleware(logger); + var middleware = CreateMiddleware(logger, listener: listener); // Act await middleware.Invoke(httpContext); @@ -75,6 +87,7 @@ namespace Microsoft.AspNetCore.Routing Assert.Empty(sink.Scopes); var write = Assert.Single(sink.Writes); Assert.Equal(expectedMessage, write.State?.ToString()); + Assert.True(eventFired); } [Fact] @@ -159,19 +172,46 @@ namespace Microsoft.AspNetCore.Routing private EndpointRoutingMiddleware CreateMiddleware( Logger logger = null, MatcherFactory matcherFactory = null, + DiagnosticListener listener = null, RequestDelegate next = null) { next ??= c => Task.CompletedTask; logger ??= new Logger(NullLoggerFactory.Instance); matcherFactory ??= new TestMatcherFactory(true); + listener ??= new DiagnosticListener("Microsoft.AspNetCore"); var middleware = new EndpointRoutingMiddleware( matcherFactory, logger, new DefaultEndpointRouteBuilder(Mock.Of()), + listener, next); return middleware; } + + private class DelegateObserver : IObserver> + { + private readonly Action> _onNext; + + public DelegateObserver(Action> onNext) + { + _onNext = onNext; + } + public void OnCompleted() + { + + } + + public void OnError(Exception error) + { + + } + + public void OnNext(KeyValuePair value) + { + _onNext(value); + } + } } }