Fix #629 - Add logging to DfaMatcther
Adds logging for the most common things that can prevent an endpoint from matching. Note that we already have good logging in other parts of the system, the stuff here completes the story by providing details at the debug level.
This commit is contained in:
parent
b7939328b8
commit
3227de8c0b
|
|
@ -421,8 +421,8 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
EventIds.TemplateFailedExpansion,
|
EventIds.TemplateFailedExpansion,
|
||||||
"Failed to process the template {Template} for {Endpoint}. " +
|
"Failed to process the template {Template} for {Endpoint}. " +
|
||||||
"The failure occured while expanding the template with values {Values}. " +
|
"The failure occured while expanding the template with values {Values} " +
|
||||||
"This is usually due to a missing or empty value in a complex segment.");
|
"This is usually due to a missing or empty value in a complex segment");
|
||||||
|
|
||||||
private static readonly Action<ILogger, IEnumerable<string>, string, Exception> _linkGenerationSucceeded = LoggerMessage.Define<IEnumerable<string>, string>(
|
private static readonly Action<ILogger, IEnumerable<string>, string, Exception> _linkGenerationSucceeded = LoggerMessage.Define<IEnumerable<string>, string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
|
|
|
||||||
|
|
@ -57,12 +57,12 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
private static readonly Action<ILogger, string, Exception> _executingEndpoint = LoggerMessage.Define<string>(
|
private static readonly Action<ILogger, string, Exception> _executingEndpoint = LoggerMessage.Define<string>(
|
||||||
LogLevel.Information,
|
LogLevel.Information,
|
||||||
new EventId(0, "ExecutingEndpoint"),
|
new EventId(0, "ExecutingEndpoint"),
|
||||||
"Executing endpoint '{EndpointName}'.");
|
"Executing endpoint '{EndpointName}'");
|
||||||
|
|
||||||
private static readonly Action<ILogger, string, Exception> _executedEndpoint = LoggerMessage.Define<string>(
|
private static readonly Action<ILogger, string, Exception> _executedEndpoint = LoggerMessage.Define<string>(
|
||||||
LogLevel.Information,
|
LogLevel.Information,
|
||||||
new EventId(1, "ExecutedEndpoint"),
|
new EventId(1, "ExecutedEndpoint"),
|
||||||
"Executed endpoint '{EndpointName}'.");
|
"Executed endpoint '{EndpointName}'");
|
||||||
|
|
||||||
public static void ExecutingEndpoint(ILogger logger, Endpoint endpoint)
|
public static void ExecutingEndpoint(ILogger logger, Endpoint endpoint)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -119,12 +119,12 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
private static readonly Action<ILogger, string, Exception> _matchSuccess = LoggerMessage.Define<string>(
|
private static readonly Action<ILogger, string, Exception> _matchSuccess = LoggerMessage.Define<string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(1, "MatchSuccess"),
|
new EventId(1, "MatchSuccess"),
|
||||||
"Request matched endpoint '{EndpointName}'.");
|
"Request matched endpoint '{EndpointName}'");
|
||||||
|
|
||||||
private static readonly Action<ILogger, Exception> _matchFailure = LoggerMessage.Define(
|
private static readonly Action<ILogger, Exception> _matchFailure = LoggerMessage.Define(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(2, "MatchFailure"),
|
new EventId(2, "MatchFailure"),
|
||||||
"Request did not match any endpoints.");
|
"Request did not match any endpoints");
|
||||||
|
|
||||||
public static void MatchSuccess(ILogger logger, EndpointSelectorContext context)
|
public static void MatchSuccess(ILogger logger, EndpointSelectorContext context)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Routing.Logging
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
1,
|
1,
|
||||||
"Route value '{RouteValue}' with key '{RouteKey}' did not match " +
|
"Route value '{RouteValue}' with key '{RouteKey}' did not match " +
|
||||||
"the constraint '{RouteConstraint}'.");
|
"the constraint '{RouteConstraint}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RouteValueDoesNotMatchConstraint(
|
public static void RouteValueDoesNotMatchConstraint(
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Routing.Logging
|
||||||
_requestDidNotMatchRoutes = LoggerMessage.Define(
|
_requestDidNotMatchRoutes = LoggerMessage.Define(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
1,
|
1,
|
||||||
"Request did not match any routes.");
|
"Request did not match any routes");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RequestDidNotMatchRoutes(this ILogger logger)
|
public static void RequestDidNotMatchRoutes(this ILogger logger)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Routing.Logging
|
||||||
_matchedRoute = LoggerMessage.Define<string, string>(
|
_matchedRoute = LoggerMessage.Define<string, string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
1,
|
1,
|
||||||
"Request successfully matched the route with name '{RouteName}' and template '{RouteTemplate}'.");
|
"Request successfully matched the route with name '{RouteName}' and template '{RouteTemplate}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MatchedRoute(
|
public static void MatchedRoute(
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,20 @@ using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Matching
|
namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
{
|
{
|
||||||
internal sealed class DfaMatcher : Matcher
|
internal sealed class DfaMatcher : Matcher
|
||||||
{
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
private readonly EndpointSelector _selector;
|
private readonly EndpointSelector _selector;
|
||||||
private readonly DfaState[] _states;
|
private readonly DfaState[] _states;
|
||||||
private readonly int _maxSegmentCount;
|
private readonly int _maxSegmentCount;
|
||||||
|
|
||||||
public DfaMatcher(EndpointSelector selector, DfaState[] states, int maxSegmentCount)
|
public DfaMatcher(ILogger<DfaMatcher> logger, EndpointSelector selector, DfaState[] states, int maxSegmentCount)
|
||||||
{
|
{
|
||||||
|
_logger = logger;
|
||||||
_selector = selector;
|
_selector = selector;
|
||||||
_states = states;
|
_states = states;
|
||||||
_maxSegmentCount = maxSegmentCount;
|
_maxSegmentCount = maxSegmentCount;
|
||||||
|
|
@ -34,6 +37,9 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
throw new ArgumentNullException(nameof(context));
|
throw new ArgumentNullException(nameof(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All of the logging we do here is at level debug, so we can get away with doing a single check.
|
||||||
|
var log = _logger.IsEnabled(LogLevel.Debug);
|
||||||
|
|
||||||
// The sequence of actions we take is optimized to avoid doing expensive work
|
// The sequence of actions we take is optimized to avoid doing expensive work
|
||||||
// like creating substrings, creating route value dictionaries, and calling
|
// like creating substrings, creating route value dictionaries, and calling
|
||||||
// into policies like versioning.
|
// into policies like versioning.
|
||||||
|
|
@ -49,9 +55,19 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
var candidates = FindCandidateSet(httpContext, path, segments);
|
var candidates = FindCandidateSet(httpContext, path, segments);
|
||||||
if (candidates.Length == 0)
|
if (candidates.Length == 0)
|
||||||
{
|
{
|
||||||
|
if (log)
|
||||||
|
{
|
||||||
|
Logger.CandidatesNotFound(_logger, path);
|
||||||
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (log)
|
||||||
|
{
|
||||||
|
Logger.CandidatesFound(_logger, path, candidates);
|
||||||
|
}
|
||||||
|
|
||||||
// At this point we have a candidate set, defined as a list of endpoints in
|
// At this point we have a candidate set, defined as a list of endpoints in
|
||||||
// priority order.
|
// priority order.
|
||||||
//
|
//
|
||||||
|
|
@ -117,21 +133,34 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
// Now that we have the route values, we need to process complex segments.
|
// Now that we have the route values, we need to process complex segments.
|
||||||
// Complex segments go through an old API that requires a fully-materialized
|
// Complex segments go through an old API that requires a fully-materialized
|
||||||
// route value dictionary.
|
// route value dictionary.
|
||||||
|
var isMatch = true;
|
||||||
if ((flags & Candidate.CandidateFlags.HasComplexSegments) != 0)
|
if ((flags & Candidate.CandidateFlags.HasComplexSegments) != 0)
|
||||||
{
|
{
|
||||||
if (!ProcessComplexSegments(candidate.ComplexSegments, path, segments, values))
|
if (!ProcessComplexSegments(candidate.Endpoint, candidate.ComplexSegments, path, segments, values))
|
||||||
{
|
{
|
||||||
candidateSet.SetValidity(i, false);
|
candidateSet.SetValidity(i, false);
|
||||||
continue;
|
isMatch = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((flags & Candidate.CandidateFlags.HasConstraints) != 0)
|
if ((flags & Candidate.CandidateFlags.HasConstraints) != 0)
|
||||||
{
|
{
|
||||||
if (!ProcessConstraints(candidate.Constraints, httpContext, values))
|
if (!ProcessConstraints(candidate.Endpoint, candidate.Constraints, httpContext, values))
|
||||||
{
|
{
|
||||||
candidateSet.SetValidity(i, false);
|
candidateSet.SetValidity(i, false);
|
||||||
continue;
|
isMatch = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log)
|
||||||
|
{
|
||||||
|
if (isMatch)
|
||||||
|
{
|
||||||
|
Logger.CandidateValid(_logger, path, candidate.Endpoint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.CandidateNotValid(_logger, path, candidate.Endpoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -202,6 +231,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ProcessComplexSegments(
|
private bool ProcessComplexSegments(
|
||||||
|
Endpoint endpoint,
|
||||||
(RoutePatternPathSegment pathSegment, int segmentIndex)[] complexSegments,
|
(RoutePatternPathSegment pathSegment, int segmentIndex)[] complexSegments,
|
||||||
string path,
|
string path,
|
||||||
ReadOnlySpan<PathSegment> segments,
|
ReadOnlySpan<PathSegment> segments,
|
||||||
|
|
@ -209,10 +239,12 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
{
|
{
|
||||||
for (var i = 0; i < complexSegments.Length; i++)
|
for (var i = 0; i < complexSegments.Length; i++)
|
||||||
{
|
{
|
||||||
var segment = segments[complexSegments[i].segmentIndex];
|
(var complexSegment, var segmentIndex) = complexSegments[i];
|
||||||
|
var segment = segments[segmentIndex];
|
||||||
var text = path.Substring(segment.Start, segment.Length);
|
var text = path.Substring(segment.Start, segment.Length);
|
||||||
if (!RoutePatternMatcher.MatchComplexSegment(complexSegments[i].pathSegment, text, values))
|
if (!RoutePatternMatcher.MatchComplexSegment(complexSegment, text, values))
|
||||||
{
|
{
|
||||||
|
Logger.CandidateRejectedByComplexSegment(_logger, path, endpoint, complexSegment);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -221,6 +253,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ProcessConstraints(
|
private bool ProcessConstraints(
|
||||||
|
Endpoint endpoint,
|
||||||
KeyValuePair<string, IRouteConstraint>[] constraints,
|
KeyValuePair<string, IRouteConstraint>[] constraints,
|
||||||
HttpContext httpContext,
|
HttpContext httpContext,
|
||||||
RouteValueDictionary values)
|
RouteValueDictionary values)
|
||||||
|
|
@ -230,11 +263,112 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
var constraint = constraints[i];
|
var constraint = constraints[i];
|
||||||
if (!constraint.Value.Match(httpContext, NullRouter.Instance, constraint.Key, values, RouteDirection.IncomingRequest))
|
if (!constraint.Value.Match(httpContext, NullRouter.Instance, constraint.Key, values, RouteDirection.IncomingRequest))
|
||||||
{
|
{
|
||||||
|
Logger.CandidateRejectedByConstraint(_logger, httpContext.Request.Path, endpoint, constraint.Key, constraint.Value, values[constraint.Key]);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static class EventIds
|
||||||
|
{
|
||||||
|
public static readonly EventId CandidatesNotFound = new EventId(1000, "CandidatesNotFound");
|
||||||
|
public static readonly EventId CandidatesFound = new EventId(1001, "CandidatesFound");
|
||||||
|
|
||||||
|
public static readonly EventId CandidateRejectedByComplexSegment = new EventId(1002, "CandidateRejectedByComplexSegment");
|
||||||
|
public static readonly EventId CandidateRejectedByConstraint = new EventId(1003, "CandidateRejectedByConstraint");
|
||||||
|
|
||||||
|
public static readonly EventId CandidateNotValid = new EventId(1004, "CandiateNotValid");
|
||||||
|
public static readonly EventId CandidateValid = new EventId(1005, "CandiateValid");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Logger
|
||||||
|
{
|
||||||
|
private static readonly Action<ILogger, string, Exception> _candidatesNotFound = LoggerMessage.Define<string>(
|
||||||
|
LogLevel.Debug,
|
||||||
|
EventIds.CandidatesNotFound,
|
||||||
|
"No candidates found for the request path '{Path}'");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, int, string, Exception> _candidatesFound = LoggerMessage.Define<int, string>(
|
||||||
|
LogLevel.Debug,
|
||||||
|
EventIds.CandidatesFound,
|
||||||
|
"{CandidateCount} candidate(s) found for the request path '{Path}'");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, string, string, string, Exception> _candidateRejectedByComplexSegment = LoggerMessage.Define<string, string, string, string>(
|
||||||
|
LogLevel.Debug,
|
||||||
|
EventIds.CandidateRejectedByComplexSegment,
|
||||||
|
"Endpoint '{Endpoint}' with route pattern '{RoutePattern}' was rejected by complex segment '{Segment}' for the request path '{Path}'");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, string, string, string, object, string, Exception> _candidateRejectedByConstraint = LoggerMessage.Define<string, string, string, string, object, string>(
|
||||||
|
LogLevel.Debug,
|
||||||
|
EventIds.CandidateRejectedByConstraint,
|
||||||
|
"Endpoint '{Endpoint}' with route pattern '{RoutePattern}' was rejected by constraint '{ConstraintName}':'{Constraint}' with value '{RouteValue}' for the request path '{Path}'");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, string, string, Exception> _candidateNotValid = LoggerMessage.Define<string, string, string>(
|
||||||
|
LogLevel.Debug,
|
||||||
|
EventIds.CandidateNotValid,
|
||||||
|
"Endpoint '{Endpoint}' with route pattern '{RoutePattern}' is not valid for the request path '{Path}'");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, string, string, Exception> _candidateValid = LoggerMessage.Define<string, string, string>(
|
||||||
|
LogLevel.Debug,
|
||||||
|
EventIds.CandidateValid,
|
||||||
|
"Endpoint '{Endpoint}' with route pattern '{RoutePattern}' is valid for the request path '{Path}'");
|
||||||
|
|
||||||
|
public static void CandidatesNotFound(ILogger logger, string path)
|
||||||
|
{
|
||||||
|
_candidatesNotFound(logger, path, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CandidatesFound(ILogger logger, string path, Candidate[] candidates)
|
||||||
|
{
|
||||||
|
_candidatesFound(logger, candidates.Length, path, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CandidateRejectedByComplexSegment(ILogger logger, string path, Endpoint endpoint, RoutePatternPathSegment segment)
|
||||||
|
{
|
||||||
|
// This should return a real pattern since we're processing complex segments.... but just in case.
|
||||||
|
if (logger.IsEnabled(LogLevel.Debug))
|
||||||
|
{
|
||||||
|
var routePattern = GetRoutePattern(endpoint);
|
||||||
|
_candidateRejectedByComplexSegment(logger, endpoint.DisplayName, routePattern, segment.DebuggerToString(), path, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CandidateRejectedByConstraint(ILogger logger, string path, Endpoint endpoint, string constraintName, IRouteConstraint constraint, object value)
|
||||||
|
{
|
||||||
|
// This should return a real pattern since we're processing constraints.... but just in case.
|
||||||
|
if (logger.IsEnabled(LogLevel.Debug))
|
||||||
|
{
|
||||||
|
var routePattern = GetRoutePattern(endpoint);
|
||||||
|
_candidateRejectedByConstraint(logger, endpoint.DisplayName, routePattern, constraintName, constraint.ToString(), value, path, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CandidateNotValid(ILogger logger, string path, Endpoint endpoint)
|
||||||
|
{
|
||||||
|
// This can be the fallback value because it really might not be a route endpoint
|
||||||
|
if (logger.IsEnabled(LogLevel.Debug))
|
||||||
|
{
|
||||||
|
var routePattern = GetRoutePattern(endpoint);
|
||||||
|
_candidateNotValid(logger, endpoint.DisplayName, routePattern, path, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CandidateValid(ILogger logger, string path, Endpoint endpoint)
|
||||||
|
{
|
||||||
|
// This can be the fallback value because it really might not be a route endpoint
|
||||||
|
if (logger.IsEnabled(LogLevel.Debug))
|
||||||
|
{
|
||||||
|
var routePattern = GetRoutePattern(endpoint);
|
||||||
|
_candidateValid(logger, endpoint.DisplayName, routePattern, path, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetRoutePattern(Endpoint endpoint)
|
||||||
|
{
|
||||||
|
return (endpoint as RouteEndpoint)?.RoutePattern?.RawText ?? "(none)";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Matching
|
namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
{
|
{
|
||||||
|
|
@ -13,6 +14,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
{
|
{
|
||||||
private readonly List<RouteEndpoint> _endpoints = new List<RouteEndpoint>();
|
private readonly List<RouteEndpoint> _endpoints = new List<RouteEndpoint>();
|
||||||
|
|
||||||
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
private readonly ParameterPolicyFactory _parameterPolicyFactory;
|
private readonly ParameterPolicyFactory _parameterPolicyFactory;
|
||||||
private readonly EndpointSelector _selector;
|
private readonly EndpointSelector _selector;
|
||||||
private readonly MatcherPolicy[] _policies;
|
private readonly MatcherPolicy[] _policies;
|
||||||
|
|
@ -29,10 +31,12 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
private int _stateIndex;
|
private int _stateIndex;
|
||||||
|
|
||||||
public DfaMatcherBuilder(
|
public DfaMatcherBuilder(
|
||||||
|
ILoggerFactory loggerFactory,
|
||||||
ParameterPolicyFactory parameterPolicyFactory,
|
ParameterPolicyFactory parameterPolicyFactory,
|
||||||
EndpointSelector selector,
|
EndpointSelector selector,
|
||||||
IEnumerable<MatcherPolicy> policies)
|
IEnumerable<MatcherPolicy> policies)
|
||||||
{
|
{
|
||||||
|
_loggerFactory = loggerFactory;
|
||||||
_parameterPolicyFactory = parameterPolicyFactory;
|
_parameterPolicyFactory = parameterPolicyFactory;
|
||||||
_selector = selector;
|
_selector = selector;
|
||||||
_policies = policies.OrderBy(p => p.Order).ToArray();
|
_policies = policies.OrderBy(p => p.Order).ToArray();
|
||||||
|
|
@ -304,7 +308,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
JumpTableBuilder.Build(exitDestination, exitDestination, null),
|
JumpTableBuilder.Build(exitDestination, exitDestination, null),
|
||||||
null);
|
null);
|
||||||
|
|
||||||
return new DfaMatcher(_selector, states, maxSegmentCount);
|
return new DfaMatcher(_loggerFactory.CreateLogger<DfaMatcher>(), _selector, states, maxSegmentCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int AddNode(
|
private int AddNode(
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
};
|
};
|
||||||
var sink = SetUpMatch(constraints, loggerEnabled: true);
|
var sink = SetUpMatch(constraints, loggerEnabled: true);
|
||||||
var expectedMessage = "Route value 'value' with key 'b' did not match the constraint " +
|
var expectedMessage = "Route value 'value' with key 'b' did not match the constraint " +
|
||||||
$"'{typeof(FailConstraint).FullName}'.";
|
$"'{typeof(FailConstraint).FullName}'";
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Empty(sink.Scopes);
|
Assert.Empty(sink.Scopes);
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
public async Task Invoke_OnCall_WritesToConfiguredLogger()
|
public async Task Invoke_OnCall_WritesToConfiguredLogger()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expectedMessage = "Request matched endpoint 'Test endpoint'.";
|
var expectedMessage = "Request matched endpoint 'Test endpoint'";
|
||||||
|
|
||||||
var sink = new TestSink(
|
var sink = new TestSink(
|
||||||
TestSink.EnableWithTypeName<EndpointRoutingMiddleware>,
|
TestSink.EnableWithTypeName<EndpointRoutingMiddleware>,
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -117,6 +118,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
{
|
{
|
||||||
var dataSource = new CompositeEndpointDataSource(Array.Empty<EndpointDataSource>());
|
var dataSource = new CompositeEndpointDataSource(Array.Empty<EndpointDataSource>());
|
||||||
return new DfaMatcherBuilder(
|
return new DfaMatcherBuilder(
|
||||||
|
NullLoggerFactory.Instance,
|
||||||
Mock.Of<ParameterPolicyFactory>(),
|
Mock.Of<ParameterPolicyFactory>(),
|
||||||
Mock.Of<EndpointSelector>(),
|
Mock.Of<EndpointSelector>(),
|
||||||
policies);
|
policies);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing.Constraints;
|
using Microsoft.AspNetCore.Routing.Constraints;
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -972,6 +973,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
{
|
{
|
||||||
var dataSource = new CompositeEndpointDataSource(Array.Empty<EndpointDataSource>());
|
var dataSource = new CompositeEndpointDataSource(Array.Empty<EndpointDataSource>());
|
||||||
return new DfaMatcherBuilder(
|
return new DfaMatcherBuilder(
|
||||||
|
NullLoggerFactory.Instance,
|
||||||
new DefaultParameterPolicyFactory(Options.Create(new RouteOptions()), Mock.Of<IServiceProvider>()),
|
new DefaultParameterPolicyFactory(Options.Create(new RouteOptions()), Mock.Of<IServiceProvider>()),
|
||||||
Mock.Of<EndpointSelector>(),
|
Mock.Of<EndpointSelector>(),
|
||||||
policies);
|
policies);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Logging.Testing;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -26,7 +28,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
template);
|
template);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Matcher CreateDfaMatcher(EndpointDataSource dataSource, EndpointSelector endpointSelector = null)
|
private Matcher CreateDfaMatcher(EndpointDataSource dataSource, EndpointSelector endpointSelector = null, ILoggerFactory loggerFactory = null)
|
||||||
{
|
{
|
||||||
var serviceCollection = new ServiceCollection()
|
var serviceCollection = new ServiceCollection()
|
||||||
.AddLogging()
|
.AddLogging()
|
||||||
|
|
@ -38,6 +40,11 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
serviceCollection.AddSingleton<EndpointSelector>(endpointSelector);
|
serviceCollection.AddSingleton<EndpointSelector>(endpointSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (loggerFactory != null)
|
||||||
|
{
|
||||||
|
serviceCollection.AddSingleton<ILoggerFactory>(loggerFactory);
|
||||||
|
}
|
||||||
|
|
||||||
var services = serviceCollection.BuildServiceProvider();
|
var services = serviceCollection.BuildServiceProvider();
|
||||||
|
|
||||||
var factory = services.GetRequiredService<MatcherFactory>();
|
var factory = services.GetRequiredService<MatcherFactory>();
|
||||||
|
|
@ -76,14 +83,14 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
|
|
||||||
var matcher = CreateDfaMatcher(endpointDataSource);
|
var matcher = CreateDfaMatcher(endpointDataSource);
|
||||||
|
|
||||||
var (httpContext, endpointFeature) = CreateContext();
|
var (httpContext, context) = CreateContext();
|
||||||
httpContext.Request.Path = "/One";
|
httpContext.Request.Path = "/One";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await matcher.MatchAsync(httpContext, endpointFeature);
|
await matcher.MatchAsync(httpContext, context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Null(endpointFeature.Endpoint);
|
Assert.Null(context.Endpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -101,14 +108,14 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
|
|
||||||
var matcher = CreateDfaMatcher(endpointDataSource);
|
var matcher = CreateDfaMatcher(endpointDataSource);
|
||||||
|
|
||||||
var (httpContext, endpointFeature) = CreateContext();
|
var (httpContext, context) = CreateContext();
|
||||||
httpContext.Request.Path = "/Teams";
|
httpContext.Request.Path = "/Teams";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await matcher.MatchAsync(httpContext, endpointFeature);
|
await matcher.MatchAsync(httpContext, context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(lowerOrderEndpoint, endpointFeature.Endpoint);
|
Assert.Equal(lowerOrderEndpoint, context.Endpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -147,14 +154,181 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
|
|
||||||
var matcher = CreateDfaMatcher(endpointDataSource, endpointSelector.Object);
|
var matcher = CreateDfaMatcher(endpointDataSource, endpointSelector.Object);
|
||||||
|
|
||||||
var (httpContext, endpointFeature) = CreateContext();
|
var (httpContext, context) = CreateContext();
|
||||||
httpContext.Request.Path = "/Teams";
|
httpContext.Request.Path = "/Teams";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await matcher.MatchAsync(httpContext, endpointFeature);
|
await matcher.MatchAsync(httpContext, context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(endpoint2, endpointFeature.Endpoint);
|
Assert.Equal(endpoint2, context.Endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MatchAsync_NoCandidates_Logging()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
|
||||||
|
{
|
||||||
|
CreateEndpoint("/{p:int}", 0)
|
||||||
|
});
|
||||||
|
|
||||||
|
var sink = new TestSink();
|
||||||
|
var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true));
|
||||||
|
|
||||||
|
var (httpContext, context) = CreateContext();
|
||||||
|
httpContext.Request.Path = "/";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await matcher.MatchAsync(httpContext, context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(context.Endpoint);
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
sink.Writes,
|
||||||
|
(log) =>
|
||||||
|
{
|
||||||
|
Assert.Equal(DfaMatcher.EventIds.CandidatesNotFound, log.EventId);
|
||||||
|
Assert.Equal("No candidates found for the request path '/'", log.Message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MatchAsync_ConstraintRejectsEndpoint_Logging()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
|
||||||
|
{
|
||||||
|
CreateEndpoint("/{p:int}", 0)
|
||||||
|
});
|
||||||
|
|
||||||
|
var sink = new TestSink();
|
||||||
|
var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true));
|
||||||
|
|
||||||
|
var (httpContext, context) = CreateContext();
|
||||||
|
httpContext.Request.Path = "/One";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await matcher.MatchAsync(httpContext, context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(context.Endpoint);
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
sink.Writes,
|
||||||
|
(log) =>
|
||||||
|
{
|
||||||
|
Assert.Equal(DfaMatcher.EventIds.CandidatesFound, log.EventId);
|
||||||
|
Assert.Equal("1 candidate(s) found for the request path '/One'", log.Message);
|
||||||
|
},
|
||||||
|
(log) =>
|
||||||
|
{
|
||||||
|
Assert.Equal(DfaMatcher.EventIds.CandidateRejectedByConstraint, log.EventId);
|
||||||
|
Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' was rejected by constraint 'p':'Microsoft.AspNetCore.Routing.Constraints.IntRouteConstraint' with value 'One' for the request path '/One'", log.Message);
|
||||||
|
},
|
||||||
|
(log) =>
|
||||||
|
{
|
||||||
|
Assert.Equal(DfaMatcher.EventIds.CandidateNotValid, log.EventId);
|
||||||
|
Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' is not valid for the request path '/One'", log.Message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MatchAsync_ComplexSegmentRejectsEndpoint_Logging()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
|
||||||
|
{
|
||||||
|
CreateEndpoint("/x-{id}-y", 0)
|
||||||
|
});
|
||||||
|
|
||||||
|
var sink = new TestSink();
|
||||||
|
var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true));
|
||||||
|
|
||||||
|
var (httpContext, context) = CreateContext();
|
||||||
|
httpContext.Request.Path = "/One";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await matcher.MatchAsync(httpContext, context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(context.Endpoint);
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
sink.Writes,
|
||||||
|
(log) =>
|
||||||
|
{
|
||||||
|
Assert.Equal(DfaMatcher.EventIds.CandidatesFound, log.EventId);
|
||||||
|
Assert.Equal("1 candidate(s) found for the request path '/One'", log.Message);
|
||||||
|
},
|
||||||
|
(log) =>
|
||||||
|
{
|
||||||
|
Assert.Equal(DfaMatcher.EventIds.CandidateRejectedByComplexSegment, log.EventId);
|
||||||
|
Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' was rejected by complex segment 'x-{id}-y' for the request path '/One'", log.Message);
|
||||||
|
},
|
||||||
|
(log) =>
|
||||||
|
{
|
||||||
|
Assert.Equal(DfaMatcher.EventIds.CandidateNotValid, log.EventId);
|
||||||
|
Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' is not valid for the request path '/One'", log.Message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MatchAsync_MultipleCandidates_Logging()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
|
||||||
|
{
|
||||||
|
CreateEndpoint("/One", 0),
|
||||||
|
CreateEndpoint("/{p:int}", 0),
|
||||||
|
CreateEndpoint("/x-{id}-y", 0),
|
||||||
|
});
|
||||||
|
|
||||||
|
var sink = new TestSink();
|
||||||
|
var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true));
|
||||||
|
|
||||||
|
var (httpContext, context) = CreateContext();
|
||||||
|
httpContext.Request.Path = "/One";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await matcher.MatchAsync(httpContext, context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(endpointDataSource.Endpoints[0], context.Endpoint);
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
sink.Writes,
|
||||||
|
(log) =>
|
||||||
|
{
|
||||||
|
Assert.Equal(DfaMatcher.EventIds.CandidatesFound, log.EventId);
|
||||||
|
Assert.Equal("3 candidate(s) found for the request path '/One'", log.Message);
|
||||||
|
},
|
||||||
|
(log) =>
|
||||||
|
{
|
||||||
|
Assert.Equal(DfaMatcher.EventIds.CandidateValid, log.EventId);
|
||||||
|
Assert.Equal("Endpoint '/One' with route pattern '/One' is valid for the request path '/One'", log.Message);
|
||||||
|
},
|
||||||
|
(log) =>
|
||||||
|
{
|
||||||
|
Assert.Equal(DfaMatcher.EventIds.CandidateRejectedByConstraint, log.EventId);
|
||||||
|
Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' was rejected by constraint 'p':'Microsoft.AspNetCore.Routing.Constraints.IntRouteConstraint' with value 'One' for the request path '/One'", log.Message);
|
||||||
|
},
|
||||||
|
(log) =>
|
||||||
|
{
|
||||||
|
Assert.Equal(DfaMatcher.EventIds.CandidateNotValid, log.EventId);
|
||||||
|
Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' is not valid for the request path '/One'", log.Message);
|
||||||
|
},
|
||||||
|
(log) =>
|
||||||
|
{
|
||||||
|
Assert.Equal(DfaMatcher.EventIds.CandidateRejectedByComplexSegment, log.EventId);
|
||||||
|
Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' was rejected by complex segment 'x-{id}-y' for the request path '/One'", log.Message);
|
||||||
|
},
|
||||||
|
(log) =>
|
||||||
|
{
|
||||||
|
Assert.Equal(DfaMatcher.EventIds.CandidateNotValid, log.EventId);
|
||||||
|
Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' is not valid for the request path '/One'", log.Message);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private (HttpContext httpContext, EndpointSelectorContext context) CreateContext()
|
private (HttpContext httpContext, EndpointSelectorContext context) CreateContext()
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Routing
|
||||||
public async Task Invoke_LogsCorrectValues_WhenNotHandled()
|
public async Task Invoke_LogsCorrectValues_WhenNotHandled()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expectedMessage = "Request did not match any routes.";
|
var expectedMessage = "Request did not match any routes";
|
||||||
var isHandled = false;
|
var isHandled = false;
|
||||||
|
|
||||||
var sink = new TestSink(
|
var sink = new TestSink(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue