Merge pull request #70 from MarcDenman/feature/updateLogging

Update logging
This commit is contained in:
Tom Pallister 2017-03-19 15:57:28 +00:00 committed by GitHub
commit f58da434ba
17 changed files with 357 additions and 165 deletions

View File

@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Http;
using Ocelot.Authentication.Handler.Factory;
using Ocelot.Configuration;
using Ocelot.Errors;
using Ocelot.Infrastructure.Extensions;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging;
using Ocelot.Middleware;
@ -33,47 +34,57 @@ namespace Ocelot.Authentication.Middleware
public async Task Invoke(HttpContext context)
{
_logger.LogDebug("started authentication");
_logger.TraceMiddlewareEntry();
if (IsAuthenticatedRoute(DownstreamRoute.ReRoute))
{
_logger.LogDebug($"{context.Request.Path} is an authenticated route. {MiddlwareName} checking if client is authenticated");
var authenticationHandler = _authHandlerFactory.Get(_app, DownstreamRoute.ReRoute.AuthenticationOptions);
if (!authenticationHandler.IsError)
if (authenticationHandler.IsError)
{
_logger.LogDebug("calling authentication handler for ReRoute");
_logger.LogError($"Error getting authentication handler for {context.Request.Path}. {authenticationHandler.Errors.ToErrorString()}");
SetPipelineError(authenticationHandler.Errors);
_logger.TraceMiddlewareCompleted();
return;
}
await authenticationHandler.Data.Handler.Handle(context);
}
else
{
_logger.LogDebug("there was an error getting authentication handler for ReRoute");
SetPipelineError(authenticationHandler.Errors);
}
if (context.User.Identity.IsAuthenticated)
{
_logger.LogDebug("the user was authenticated");
_logger.LogDebug($"Client has been authenticated for {context.Request.Path}");
_logger.TraceInvokeNext();
await _next.Invoke(context);
_logger.LogDebug("succesfully called next middleware");
_logger.TraceInvokeNextCompleted();
_logger.TraceMiddlewareCompleted();
}
else
{
_logger.LogDebug("the user was not authenticated");
var error = new List<Error>
{
new UnauthenticatedError(
$"Request for authenticated route {context.Request.Path} by {context.User.Identity.Name} was unauthenticated")
};
SetPipelineError(new List<Error> { new UnauthenticatedError($"Request for authenticated route {context.Request.Path} by {context.User.Identity.Name} was unauthenticated") });
_logger.LogError($"Client has NOT been authenticated for {context.Request.Path} and pipeline error set. {error.ToErrorString()}");
SetPipelineError(error);
_logger.TraceMiddlewareCompleted();
return;
}
}
else
{
_logger.LogDebug("calling next middleware");
_logger.LogTrace($"No authentication needed for {context.Request.Path}");
_logger.TraceInvokeNext();
await _next.Invoke(context);
_logger.LogDebug("succesfully called next middleware");
_logger.TraceInvokeNextCompleted();
_logger.TraceMiddlewareCompleted();
}
}
@ -83,3 +94,4 @@ namespace Ocelot.Authentication.Middleware
}
}
}

View File

@ -9,6 +9,7 @@ using Ocelot.Configuration.File;
using Ocelot.Configuration.Parser;
using Ocelot.Configuration.Validator;
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Logging;
using Ocelot.Requester.QoS;
using Ocelot.Responses;
using Ocelot.Utilities;
@ -22,7 +23,7 @@ namespace Ocelot.Configuration.Creator
{
private readonly IOptions<FileConfiguration> _options;
private readonly IConfigurationValidator _configurationValidator;
private readonly ILogger<FileOcelotConfigurationCreator> _logger;
private readonly IOcelotLogger _logger;
private readonly ILoadBalancerFactory _loadBalanceFactory;
private readonly ILoadBalancerHouse _loadBalancerHouse;
private readonly IQoSProviderFactory _qoSProviderFactory;
@ -39,7 +40,7 @@ namespace Ocelot.Configuration.Creator
public FileOcelotConfigurationCreator(
IOptions<FileConfiguration> options,
IConfigurationValidator configurationValidator,
ILogger<FileOcelotConfigurationCreator> logger,
IOcelotLoggerFactory loggerFactory,
ILoadBalancerFactory loadBalancerFactory,
ILoadBalancerHouse loadBalancerHouse,
IQoSProviderFactory qoSProviderFactory,
@ -64,7 +65,7 @@ namespace Ocelot.Configuration.Creator
_qosProviderHouse = qosProviderHouse;
_options = options;
_configurationValidator = configurationValidator;
_logger = logger;
_logger = loggerFactory.CreateLogger<FileOcelotConfigurationCreator>();
_claimsToThingCreator = claimsToThingCreator;
_serviceProviderConfigCreator = serviceProviderConfigCreator;
_qosOptionsCreator = qosOptionsCreator;

View File

@ -1,6 +1,9 @@
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Ocelot.DownstreamRouteFinder.Finder;
using Ocelot.Infrastructure.Extensions;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging;
using Ocelot.Middleware;
@ -27,7 +30,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
public async Task Invoke(HttpContext context)
{
_logger.LogDebug("started calling downstream route finder middleware");
_logger.TraceMiddlewareEntry();
var upstreamUrlPath = context.Request.Path.ToString().SetLastCharacterAs('/');
@ -37,9 +40,11 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
if (downstreamRoute.IsError)
{
_logger.LogDebug("IDownstreamRouteFinder returned an error, setting pipeline error");
_logger.LogError($"{MiddlwareName} setting pipeline errors. IDownstreamRouteFinder returned {downstreamRoute.Errors.ToErrorString()}");
SetPipelineError(downstreamRoute.Errors);
_logger.TraceMiddlewareCompleted();
return;
}
@ -47,12 +52,12 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
SetDownstreamRouteForThisRequest(downstreamRoute.Data);
_logger.LogDebug("calling next middleware");
_logger.TraceInvokeNext();
await _next.Invoke(context);
_logger.LogDebug("succesfully called next middleware");
_logger.TraceInvokeNextCompleted();
_logger.TraceMiddlewareCompleted();
}
}
}

View File

@ -6,6 +6,9 @@ using Ocelot.Logging;
namespace Ocelot.Errors.Middleware
{
/// <summary>
/// Catches all unhandled exceptions thrown by middleware, logs and returns a 500
/// </summary>
public class ExceptionHandlerMiddleware
{
private readonly RequestDelegate _next;

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Extensions.Primitives;
using Ocelot.Errors;
namespace Ocelot.Infrastructure.Extensions
{
public static class ErrorListExtensions
{
public static string ToErrorString(this List<Error> errors)
{
var listOfErrorStrings = errors.Select(x => "Error Code: " + x.Code.ToString() + " Message: " + x.Message);
return string.Join(" ", listOfErrorStrings);
}
}
}

View File

@ -0,0 +1,52 @@
using System;
using Microsoft.Extensions.Logging;
using Ocelot.Infrastructure.RequestData;
namespace Ocelot.Logging
{
public class AspDotNetLogger : IOcelotLogger
{
private readonly ILogger _logger;
private readonly IRequestScopedDataRepository _scopedDataRepository;
public string Name { get; }
public AspDotNetLogger(ILogger logger, IRequestScopedDataRepository scopedDataRepository, string typeName)
{
Name = typeName;
_logger = logger;
_scopedDataRepository = scopedDataRepository;
}
public void LogTrace(string message, params object[] args)
{
_logger.LogTrace(GetMessageWithOcelotRequestId(message), args);
}
public void LogDebug(string message, params object[] args)
{
_logger.LogDebug(GetMessageWithOcelotRequestId(message), args);
}
public void LogError(string message, Exception exception)
{
_logger.LogError(GetMessageWithOcelotRequestId(message), exception);
}
public void LogError(string message, params object[] args)
{
_logger.LogError(GetMessageWithOcelotRequestId(message), args);
}
private string GetMessageWithOcelotRequestId(string message)
{
var requestId = _scopedDataRepository.Get<string>("RequestId");
if (requestId == null || requestId.IsError)
{
return $"{message} : OcelotRequestId - not set";
}
return $"{message} : OcelotRequestId - {requestId.Data}";
}
}
}

View File

@ -0,0 +1,23 @@
using Microsoft.Extensions.Logging;
using Ocelot.Infrastructure.RequestData;
namespace Ocelot.Logging
{
public class AspDotNetLoggerFactory : IOcelotLoggerFactory
{
private readonly ILoggerFactory _loggerFactory;
private readonly IRequestScopedDataRepository _scopedDataRepository;
public AspDotNetLoggerFactory(ILoggerFactory loggerFactory, IRequestScopedDataRepository scopedDataRepository)
{
_loggerFactory = loggerFactory;
_scopedDataRepository = scopedDataRepository;
}
public IOcelotLogger CreateLogger<T>()
{
var logger = _loggerFactory.CreateLogger<T>();
return new AspDotNetLogger(logger, _scopedDataRepository, typeof(T).Name);
}
}
}

View File

@ -1,6 +1,4 @@
using System;
using Microsoft.Extensions.Logging;
using Ocelot.Infrastructure.RequestData;
namespace Ocelot.Logging
{
@ -8,62 +6,19 @@ namespace Ocelot.Logging
{
IOcelotLogger CreateLogger<T>();
}
public class AspDotNetLoggerFactory : IOcelotLoggerFactory
{
private readonly ILoggerFactory _loggerFactory;
private readonly IRequestScopedDataRepository _scopedDataRepository;
public AspDotNetLoggerFactory(ILoggerFactory loggerFactory, IRequestScopedDataRepository scopedDataRepository)
{
_loggerFactory = loggerFactory;
_scopedDataRepository = scopedDataRepository;
}
public IOcelotLogger CreateLogger<T>()
{
var logger = _loggerFactory.CreateLogger<T>();
return new AspDotNetLogger(logger, _scopedDataRepository);
}
}
/// <summary>
/// Thin wrapper around the DotNet core logging framework, used to allow the scopedDataRepository to be injected giving access to the Ocelot RequestId
/// </summary>
public interface IOcelotLogger
{
void LogTrace(string message, params object[] args);
void LogDebug(string message, params object[] args);
void LogError(string message, Exception exception);
}
void LogError(string message, params object[] args);
public class AspDotNetLogger : IOcelotLogger
{
private readonly ILogger _logger;
private readonly IRequestScopedDataRepository _scopedDataRepository;
public AspDotNetLogger(ILogger logger, IRequestScopedDataRepository scopedDataRepository)
{
_logger = logger;
_scopedDataRepository = scopedDataRepository;
}
public void LogDebug(string message, params object[] args)
{
_logger.LogDebug(GetMessageWithOcelotRequestId(message), args);
}
public void LogError(string message, Exception exception)
{
_logger.LogError(GetMessageWithOcelotRequestId(message), exception);
}
private string GetMessageWithOcelotRequestId(string message)
{
var requestId = _scopedDataRepository.Get<string>("RequestId");
if (requestId != null && !requestId.IsError)
{
return $"{message} : OcelotRequestId - {requestId.Data}";
}
return $"{message} : OcelotRequestId - not set";
}
/// <summary>
/// The name of the type the logger has been built for.
/// </summary>
string Name { get; }
}
}

View File

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Ocelot.Logging
{
public static class OcelotLoggerExtensions
{
public static void TraceMiddlewareEntry(this IOcelotLogger logger)
{
logger.LogTrace($"entered {logger.Name}");
}
public static void TraceInvokeNext(this IOcelotLogger logger)
{
logger.LogTrace($"invoking next middleware from {logger.Name}");
}
public static void TraceInvokeNextCompleted(this IOcelotLogger logger)
{
logger.LogTrace($"returned to {logger.Name} after next middleware completed");
}
public static void TraceMiddlewareCompleted(this IOcelotLogger logger)
{
logger.LogTrace($"completed {logger.Name}");
}
}
}

View File

@ -14,8 +14,11 @@ namespace Ocelot.Middleware
protected OcelotMiddleware(IRequestScopedDataRepository requestScopedDataRepository)
{
_requestScopedDataRepository = requestScopedDataRepository;
MiddlwareName = this.GetType().Name;
}
public string MiddlwareName { get; }
public bool PipelineError
{
get

View File

@ -31,12 +31,18 @@ namespace Ocelot.RateLimit.Middleware
public async Task Invoke(HttpContext context)
{
_logger.LogDebug("started calling RateLimit middleware");
_logger.TraceMiddlewareEntry();
var options = DownstreamRoute.ReRoute.RateLimitOptions;
// check if rate limiting is enabled
if (!DownstreamRoute.ReRoute.EnableEndpointRateLimiting)
{
_logger.LogDebug($"EndpointRateLimiting is not enabled for {DownstreamRoute.ReRoute.DownstreamPathTemplate}");
_logger.TraceInvokeNext();
await _next.Invoke(context);
_logger.TraceInvokeNextCompleted();
_logger.TraceMiddlewareCompleted();
return;
}
// compute identity from request
@ -45,7 +51,12 @@ namespace Ocelot.RateLimit.Middleware
// check white list
if (IsWhitelisted(identity, options))
{
_logger.LogDebug($"{DownstreamRoute.ReRoute.DownstreamPathTemplate} is white listed from rate limiting");
_logger.TraceInvokeNext();
await _next.Invoke(context);
_logger.TraceInvokeNextCompleted();
_logger.TraceMiddlewareCompleted();
return;
}
@ -66,6 +77,7 @@ namespace Ocelot.RateLimit.Middleware
// break execution
await ReturnQuotaExceededResponse(context, options, retryAfter);
_logger.TraceMiddlewareCompleted();
return;
}
}
@ -76,7 +88,10 @@ namespace Ocelot.RateLimit.Middleware
context.Response.OnStarting(SetRateLimitHeaders, state: headers);
}
_logger.TraceInvokeNext();
await _next.Invoke(context);
_logger.TraceInvokeNextCompleted();
_logger.TraceMiddlewareCompleted();
}
public virtual ClientRequestIdentity SetIdentity(HttpContext httpContext, RateLimitOptions option)

View File

@ -26,17 +26,16 @@ namespace Ocelot.RequestId.Middleware
public async Task Invoke(HttpContext context)
{
_logger.LogDebug("started calling request id middleware");
_logger.TraceMiddlewareEntry();
SetOcelotRequestId(context);
_logger.LogDebug("set request id");
_logger.LogDebug("calling next middleware");
_logger.LogDebug("set requestId");
_logger.TraceInvokeNext();
await _next.Invoke(context);
_logger.LogDebug("succesfully called next middleware");
_logger.TraceInvokeNextCompleted();
_logger.TraceMiddlewareCompleted();
}
private void SetOcelotRequestId(HttpContext context)

View File

@ -26,6 +26,11 @@ namespace Ocelot.Responder
return 503;
}
if (errors.Any(e => e.Code == OcelotErrorCode.UnableToFindDownstreamRouteError))
{
return 404;
}
return 404;
}
}

View File

@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Ocelot.Errors;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging;
@ -9,6 +8,9 @@ using Ocelot.Middleware;
namespace Ocelot.Responder.Middleware
{
/// <summary>
/// Completes and returns the request and request body, if any pipeline errors occured then sets the appropriate HTTP status code instead.
/// </summary>
public class ResponderMiddleware : OcelotMiddleware
{
private readonly RequestDelegate _next;
@ -32,28 +34,24 @@ namespace Ocelot.Responder.Middleware
public async Task Invoke(HttpContext context)
{
_logger.LogDebug("started error responder middleware");
_logger.TraceMiddlewareEntry();
_logger.TraceInvokeNext();
await _next.Invoke(context);
_logger.LogDebug("calling next middleware");
_logger.TraceInvokeNextCompleted();
if (PipelineError)
{
_logger.LogDebug("there is a pipeline error, getting errors");
var errors = PipelineErrors;
_logger.LogDebug("received errors setting error response");
_logger.LogError($"{errors.Count} pipeline errors found in {MiddlwareName}. Setting error response status code");
SetErrorResponse(context, errors);
}
else
{
_logger.LogDebug("no pipeline error, setting response");
_logger.LogDebug("no pipeline errors, setting and returning completed response");
await _responder.SetResponseOnHttpContext(context, HttpResponseMessage);
}
_logger.TraceMiddlewareCompleted();
}
private void SetErrorResponse(HttpContext context, List<Error> errors)

View File

@ -2,7 +2,7 @@
"Logging": {
"IncludeScopes": true,
"LogLevel": {
"Default": "Debug",
"Default": "Trace",
"System": "Information",
"Microsoft": "Information"
}

View File

@ -8,6 +8,7 @@ using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Ocelot.Configuration.Validator;
using Ocelot.LoadBalancer.LoadBalancers;
using Ocelot.Logging;
using Ocelot.Requester.QoS;
using Ocelot.Responses;
using Shouldly;
@ -22,7 +23,7 @@ namespace Ocelot.UnitTests.Configuration
private readonly Mock<IConfigurationValidator> _validator;
private Response<IOcelotConfiguration> _config;
private FileConfiguration _fileConfiguration;
private readonly Mock<ILogger<FileOcelotConfigurationCreator>> _logger;
private readonly Mock<IOcelotLoggerFactory> _logger;
private readonly FileOcelotConfigurationCreator _ocelotConfigurationCreator;
private readonly Mock<ILoadBalancerFactory> _loadBalancerFactory;
private readonly Mock<ILoadBalancerHouse> _loadBalancerHouse;
@ -44,7 +45,7 @@ namespace Ocelot.UnitTests.Configuration
_qosProviderFactory = new Mock<IQoSProviderFactory>();
_qosProviderHouse = new Mock<IQosProviderHouse>();
_qosProvider = new Mock<IQoSProvider>();
_logger = new Mock<ILogger<FileOcelotConfigurationCreator>>();
_logger = new Mock<IOcelotLoggerFactory>();
_validator = new Mock<IConfigurationValidator>();
_fileConfig = new Mock<IOptions<FileConfiguration>>();
_loadBalancerFactory = new Mock<ILoadBalancerFactory>();

View File

@ -0,0 +1,73 @@
using Microsoft.AspNetCore.Http;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Responses;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Infrastructure
{
public class HttpDataRepositoryTests
{
private HttpContext _httpContext;
private IHttpContextAccessor _httpContextAccessor;
private HttpDataRepository _httpDataRepository;
private object _result;
public HttpDataRepositoryTests()
{
_httpContext = new DefaultHttpContext();
_httpContextAccessor = new HttpContextAccessor{HttpContext = _httpContext};
_httpDataRepository = new HttpDataRepository(_httpContextAccessor);
}
//TODO - Additional tests -> Type mistmatch aka Add string, request int
//TODO - Additional tests -> HttpContent null. This should never happen
[Fact]
public void Get_returns_correct_key_from_http_context()
{
this.Given(x => x.GivenAHttpContextContaining("key", "string"))
.When(x => x.GetIsCalledWithKey<string>("key"))
.Then(x => x.ThenTheResultIsAnOkResponse<string>("string"))
.BDDfy();
}
[Fact]
public void Get_returns_error_response_if_the_key_is_not_found() //Therefore does not return null
{
this.Given(x => x.GivenAHttpContextContaining("key", "string"))
.When(x => x.GetIsCalledWithKey<string>("keyDoesNotExist"))
.Then(x => x.ThenTheResultIsAnErrorReposnse<string>("string1"))
.BDDfy();
}
private void GivenAHttpContextContaining(string key, object o)
{
_httpContext.Items.Add(key, o);
}
private void GetIsCalledWithKey<T>(string key)
{
_result = _httpDataRepository.Get<T>(key);
}
private void ThenTheResultIsAnErrorReposnse<T>(object resultValue)
{
_result.ShouldBeOfType<ErrorResponse<T>>();
((ErrorResponse<T>) _result).Data.ShouldBeNull();
((ErrorResponse<T>)_result).IsError.ShouldBe(true);
((ErrorResponse<T>) _result).Errors.ShouldHaveSingleItem()
.ShouldBeOfType<CannotFindDataError>()
.Message.ShouldStartWith("Unable to find data for key: ");
}
private void ThenTheResultIsAnOkResponse<T>(object resultValue)
{
_result.ShouldBeOfType<OkResponse<T>>();
((OkResponse<T>)_result).Data.ShouldBe(resultValue);
}
}
}