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.
// 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<IHostApplicationLifetime>())
.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<IHostApplicationLifetime>())
.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<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.
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<Matcher> _initializationTask;
@ -24,31 +28,18 @@ namespace Microsoft.AspNetCore.Routing
MatcherFactory matcherFactory,
ILogger<EndpointRoutingMiddleware> 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);
}

View File

@ -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<DiagnosticSource>(listener);
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.
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<EndpointRoutingMiddleware>,
TestSink.EnableWithTypeName<EndpointRoutingMiddleware>);
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 logger = new Logger<EndpointRoutingMiddleware>(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<EndpointRoutingMiddleware> logger = null,
MatcherFactory matcherFactory = null,
DiagnosticListener listener = null,
RequestDelegate next = null)
{
next ??= c => Task.CompletedTask;
logger ??= new Logger<EndpointRoutingMiddleware>(NullLoggerFactory.Instance);
matcherFactory ??= new TestMatcherFactory(true);
listener ??= new DiagnosticListener("Microsoft.AspNetCore");
var middleware = new EndpointRoutingMiddleware(
matcherFactory,
logger,
new DefaultEndpointRouteBuilder(Mock.Of<IApplicationBuilder>()),
listener,
next);
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);
}
}
}
}