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 <buchholz.bastian@googlemail.com>

* PR feedback and test fixes
This commit is contained in:
David Fowler 2019-07-01 00:13:37 -07:00 committed by GitHub
parent 661fdedfd4
commit d3640d59a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 82 additions and 31 deletions

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -16,12 +17,7 @@ namespace Microsoft.AspNetCore.Components.Server.Tests
public void MapBlazorHub_WiresUp_UnderlyingHub() public void MapBlazorHub_WiresUp_UnderlyingHub()
{ {
// Arrange // Arrange
var applicationBuilder = new ApplicationBuilder( var applicationBuilder = CreateAppBuilder();
new ServiceCollection()
.AddLogging()
.AddSingleton(Mock.Of<IHostApplicationLifetime>())
.AddSignalR().Services
.AddServerSideBlazor().Services.BuildServiceProvider());
var called = false; var called = false;
// Act // Act
@ -40,12 +36,7 @@ namespace Microsoft.AspNetCore.Components.Server.Tests
public void MapBlazorHub_MostGeneralOverload_MapsUnderlyingHub() public void MapBlazorHub_MostGeneralOverload_MapsUnderlyingHub()
{ {
// Arrange // Arrange
var applicationBuilder = new ApplicationBuilder( var applicationBuilder = CreateAppBuilder();
new ServiceCollection()
.AddLogging()
.AddSingleton(Mock.Of<IHostApplicationLifetime>())
.AddSignalR().Services
.AddServerSideBlazor().Services.BuildServiceProvider());
var called = false; var called = false;
// Act // Act
@ -59,5 +50,23 @@ namespace Microsoft.AspNetCore.Components.Server.Tests
// Assert // Assert
Assert.True(called); Assert.True(called);
} }
private IApplicationBuilder CreateAppBuilder()
{
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<IHostApplicationLifetime>());
services.AddLogging();
services.AddOptions();
var listener = new DiagnosticListener("Microsoft.AspNetCore");
services.AddSingleton(listener);
services.AddSingleton<DiagnosticSource>(listener);
services.AddRouting();
services.AddSignalR();
services.AddServerSideBlazor();
var serviceProvder = services.BuildServiceProvider();
return new ApplicationBuilder(serviceProvder);
}
} }
} }

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -13,9 +14,12 @@ namespace Microsoft.AspNetCore.Routing
{ {
internal sealed class EndpointRoutingMiddleware internal sealed class EndpointRoutingMiddleware
{ {
private const string DiagnosticsEndpointMatchedKey = "Microsoft.AspNetCore.Routing.EndpointMatched";
private readonly MatcherFactory _matcherFactory; private readonly MatcherFactory _matcherFactory;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly EndpointDataSource _endpointDataSource; private readonly EndpointDataSource _endpointDataSource;
private readonly DiagnosticListener _diagnosticListener;
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private Task<Matcher> _initializationTask; private Task<Matcher> _initializationTask;
@ -24,31 +28,18 @@ namespace Microsoft.AspNetCore.Routing
MatcherFactory matcherFactory, MatcherFactory matcherFactory,
ILogger<EndpointRoutingMiddleware> logger, ILogger<EndpointRoutingMiddleware> logger,
IEndpointRouteBuilder endpointRouteBuilder, IEndpointRouteBuilder endpointRouteBuilder,
DiagnosticListener diagnosticListener,
RequestDelegate next) RequestDelegate next)
{ {
if (matcherFactory == null)
{
throw new ArgumentNullException(nameof(matcherFactory));
}
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
if (endpointRouteBuilder == null) if (endpointRouteBuilder == null)
{ {
throw new ArgumentNullException(nameof(endpointRouteBuilder)); throw new ArgumentNullException(nameof(endpointRouteBuilder));
} }
if (next == null) _matcherFactory = matcherFactory ?? throw new ArgumentNullException(nameof(matcherFactory));
{ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
throw new ArgumentNullException(nameof(next)); _diagnosticListener = diagnosticListener ?? throw new ArgumentNullException(nameof(diagnosticListener));
} _next = next ?? throw new ArgumentNullException(nameof(next));
_matcherFactory = matcherFactory;
_logger = logger;
_next = next;
_endpointDataSource = new CompositeEndpointDataSource(endpointRouteBuilder.DataSources); _endpointDataSource = new CompositeEndpointDataSource(endpointRouteBuilder.DataSources);
} }
@ -106,6 +97,13 @@ namespace Microsoft.AspNetCore.Routing
} }
else 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); Log.MatchSuccess(_logger, endpoint);
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
@ -311,6 +312,9 @@ namespace Microsoft.AspNetCore.Builder
services.AddLogging(); services.AddLogging();
services.AddOptions(); services.AddOptions();
services.AddRouting(); services.AddRouting();
var listener = new DiagnosticListener("Microsoft.AspNetCore");
services.AddSingleton(listener);
services.AddSingleton<DiagnosticSource>(listener);
var serviceProvder = services.BuildServiceProvider(); var serviceProvder = services.BuildServiceProvider();

View File

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -57,16 +59,26 @@ namespace Microsoft.AspNetCore.Routing
{ {
// Arrange // Arrange
var expectedMessage = "Request matched endpoint 'Test endpoint'"; var expectedMessage = "Request matched endpoint 'Test endpoint'";
bool eventFired = false;
var sink = new TestSink( var sink = new TestSink(
TestSink.EnableWithTypeName<EndpointRoutingMiddleware>, TestSink.EnableWithTypeName<EndpointRoutingMiddleware>,
TestSink.EnableWithTypeName<EndpointRoutingMiddleware>); TestSink.EnableWithTypeName<EndpointRoutingMiddleware>);
var loggerFactory = new TestLoggerFactory(sink, enabled: true); 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<HttpContext>(pair.Value);
}));
var httpContext = CreateHttpContext(); var httpContext = CreateHttpContext();
var logger = new Logger<EndpointRoutingMiddleware>(loggerFactory); var logger = new Logger<EndpointRoutingMiddleware>(loggerFactory);
var middleware = CreateMiddleware(logger); var middleware = CreateMiddleware(logger, listener: listener);
// Act // Act
await middleware.Invoke(httpContext); await middleware.Invoke(httpContext);
@ -75,6 +87,7 @@ namespace Microsoft.AspNetCore.Routing
Assert.Empty(sink.Scopes); Assert.Empty(sink.Scopes);
var write = Assert.Single(sink.Writes); var write = Assert.Single(sink.Writes);
Assert.Equal(expectedMessage, write.State?.ToString()); Assert.Equal(expectedMessage, write.State?.ToString());
Assert.True(eventFired);
} }
[Fact] [Fact]
@ -159,19 +172,46 @@ namespace Microsoft.AspNetCore.Routing
private EndpointRoutingMiddleware CreateMiddleware( private EndpointRoutingMiddleware CreateMiddleware(
Logger<EndpointRoutingMiddleware> logger = null, Logger<EndpointRoutingMiddleware> logger = null,
MatcherFactory matcherFactory = null, MatcherFactory matcherFactory = null,
DiagnosticListener listener = null,
RequestDelegate next = null) RequestDelegate next = null)
{ {
next ??= c => Task.CompletedTask; next ??= c => Task.CompletedTask;
logger ??= new Logger<EndpointRoutingMiddleware>(NullLoggerFactory.Instance); logger ??= new Logger<EndpointRoutingMiddleware>(NullLoggerFactory.Instance);
matcherFactory ??= new TestMatcherFactory(true); matcherFactory ??= new TestMatcherFactory(true);
listener ??= new DiagnosticListener("Microsoft.AspNetCore");
var middleware = new EndpointRoutingMiddleware( var middleware = new EndpointRoutingMiddleware(
matcherFactory, matcherFactory,
logger, logger,
new DefaultEndpointRouteBuilder(Mock.Of<IApplicationBuilder>()), new DefaultEndpointRouteBuilder(Mock.Of<IApplicationBuilder>()),
listener,
next); next);
return middleware; return middleware;
} }
private class DelegateObserver : IObserver<KeyValuePair<string, object>>
{
private readonly Action<KeyValuePair<string, object>> _onNext;
public DelegateObserver(Action<KeyValuePair<string, object>> onNext)
{
_onNext = onNext;
}
public void OnCompleted()
{
}
public void OnError(Exception error)
{
}
public void OnNext(KeyValuePair<string, object> value)
{
_onNext(value);
}
}
} }
} }