Skip to content

Add a diagnostic source event that fires when a route is matched #11685

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -16,12 +17,7 @@ public class ComponentEndpointRouteBuilderExtensionsTest
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
Expand All @@ -40,12 +36,7 @@ public void MapBlazorHub_WiresUp_UnderlyingHub()
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
Expand All @@ -59,5 +50,23 @@ public void MapBlazorHub_MostGeneralOverload_MapsUnderlyingHub()
// 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);
}
}
}
34 changes: 16 additions & 18 deletions src/Http/Routing/src/EndpointRoutingMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -24,31 +28,18 @@ public EndpointRoutingMiddleware(
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);
}
Expand Down Expand Up @@ -106,6 +97,13 @@ private Task SetRoutingAndContinue(HttpContext httpContext)
}
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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -311,6 +312,9 @@ private IServiceProvider CreateServices(MatcherFactory matcherFactory)
services.AddLogging();
services.AddOptions();
services.AddRouting();
var listener = new DiagnosticListener("Microsoft.AspNetCore");
services.AddSingleton(listener);
services.AddSingleton<DiagnosticSource>(listener);

var serviceProvder = services.BuildServiceProvider();

Expand Down
42 changes: 41 additions & 1 deletion src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -57,16 +59,26 @@ public async Task Invoke_OnCall_WritesToConfiguredLogger()
{
// 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);
Expand All @@ -75,6 +87,7 @@ public async Task Invoke_OnCall_WritesToConfiguredLogger()
Assert.Empty(sink.Scopes);
var write = Assert.Single(sink.Writes);
Assert.Equal(expectedMessage, write.State?.ToString());
Assert.True(eventFired);
}

[Fact]
Expand Down Expand Up @@ -159,19 +172,46 @@ private HttpContext CreateHttpContext()
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);
}
}
}
}