diff --git a/src/Serilog.AspNetCore/AspNetCore/RequestLoggingMiddleware.cs b/src/Serilog.AspNetCore/AspNetCore/RequestLoggingMiddleware.cs index 0c53ec8..9b9f61c 100644 --- a/src/Serilog.AspNetCore/AspNetCore/RequestLoggingMiddleware.cs +++ b/src/Serilog.AspNetCore/AspNetCore/RequestLoggingMiddleware.cs @@ -18,6 +18,7 @@ using Serilog.Extensions.Hosting; using Serilog.Parsing; using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; @@ -32,6 +33,7 @@ class RequestLoggingMiddleware readonly MessageTemplate _messageTemplate; readonly Action _enrichDiagnosticContext; readonly Func _getLevel; + readonly Func> _getMessageTemplateProperties; readonly ILogger _logger; readonly bool _includeQueryInRequestPath; static readonly LogEventProperty[] NoProperties = new LogEventProperty[0]; @@ -47,6 +49,7 @@ public RequestLoggingMiddleware(RequestDelegate next, DiagnosticContext diagnost _messageTemplate = new MessageTemplateParser().Parse(options.MessageTemplate); _logger = options.Logger?.ForContext(); _includeQueryInRequestPath = options.IncludeQueryInRequestPath; + _getMessageTemplateProperties = options.GetMessageTemplateProperties; } // ReSharper disable once UnusedMember.Global @@ -90,13 +93,7 @@ bool LogCompletion(HttpContext httpContext, DiagnosticContextCollector collector collectedProperties = NoProperties; // Last-in (correctly) wins... - var properties = collectedProperties.Concat(new[] - { - new LogEventProperty("RequestMethod", new ScalarValue(httpContext.Request.Method)), - new LogEventProperty("RequestPath", new ScalarValue(GetPath(httpContext, _includeQueryInRequestPath))), - new LogEventProperty("StatusCode", new ScalarValue(statusCode)), - new LogEventProperty("Elapsed", new ScalarValue(elapsedMs)) - }); + var properties = collectedProperties.Concat(_getMessageTemplateProperties(httpContext, GetPath(httpContext, _includeQueryInRequestPath), elapsedMs, statusCode)); var evt = new LogEvent(DateTimeOffset.Now, level, ex ?? collectedException, _messageTemplate, properties); logger.Write(evt); @@ -123,7 +120,7 @@ static string GetPath(HttpContext httpContext, bool includeQueryInRequestPath) { requestPath = httpContext.Request.Path.ToString(); } - + return requestPath; } } diff --git a/src/Serilog.AspNetCore/AspNetCore/RequestLoggingOptions.cs b/src/Serilog.AspNetCore/AspNetCore/RequestLoggingOptions.cs index bd6fead..2227388 100644 --- a/src/Serilog.AspNetCore/AspNetCore/RequestLoggingOptions.cs +++ b/src/Serilog.AspNetCore/AspNetCore/RequestLoggingOptions.cs @@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Http; using Serilog.Events; using System; +using System.Collections.Generic; // ReSharper disable UnusedAutoPropertyAccessor.Global @@ -34,6 +35,15 @@ public class RequestLoggingOptions : ctx.Response.StatusCode > 499 ? LogEventLevel.Error : LogEventLevel.Information; + + IEnumerable DefaultGetMessageTemplateProperties(HttpContext httpContext, string requestPath, double elapsedMs, int statusCode) => + new[] + { + new LogEventProperty("RequestMethod", new ScalarValue(httpContext.Request.Method)), + new LogEventProperty("RequestPath", new ScalarValue(requestPath)), + new LogEventProperty("StatusCode", new ScalarValue(statusCode)), + new LogEventProperty("Elapsed", new ScalarValue(elapsedMs)) + }; /// /// Gets or sets the message template. The default value is @@ -74,6 +84,11 @@ public class RequestLoggingOptions /// public bool IncludeQueryInRequestPath { get; set; } + /// + /// A function to specify the values of the MessageTemplateProperties. + /// + public Func> GetMessageTemplateProperties { get; set; } + /// /// Constructor /// @@ -81,6 +96,7 @@ public RequestLoggingOptions() { GetLevel = DefaultGetLevel; MessageTemplate = DefaultRequestCompletionMessageTemplate; + GetMessageTemplateProperties = DefaultGetMessageTemplateProperties; } } } diff --git a/test/Serilog.AspNetCore.Tests/SerilogWebHostBuilderExtensionsTests.cs b/test/Serilog.AspNetCore.Tests/SerilogWebHostBuilderExtensionsTests.cs index 0193263..09f6c1b 100644 --- a/test/Serilog.AspNetCore.Tests/SerilogWebHostBuilderExtensionsTests.cs +++ b/test/Serilog.AspNetCore.Tests/SerilogWebHostBuilderExtensionsTests.cs @@ -12,6 +12,8 @@ using Microsoft.AspNetCore.Http; using Serilog.Filters; using Serilog.AspNetCore.Tests.Support; +using Serilog.Events; +using System.Net.Http; // Newer frameworks provide IHostBuilder #pragma warning disable CS0618 @@ -66,6 +68,34 @@ public async Task RequestLoggingMiddlewareShouldEnrich() Assert.True(completionEvent.Properties.ContainsKey("Elapsed")); } + [Fact] + public async Task RequestLoggingMiddlewareShouldEnrichWithCustomisedProperties() + { + var (sink, web) = Setup(options => + { + options.MessageTemplate = "HTTP {RequestMethod} responded {Status} in {ElapsedMilliseconds:0.0000} ms"; + options.GetMessageTemplateProperties = (ctx, path, elapsedMs, status) => + new[] + { + new LogEventProperty("RequestMethod", new ScalarValue(ctx.Request.Method)), + new LogEventProperty("Status", new ScalarValue(status)), + new LogEventProperty("ElapsedMilliseconds", new ScalarValue(elapsedMs)) + }; + }); + + await web.CreateClient().GetAsync("/resource"); + + Assert.NotEmpty(sink.Writes); + + var completionEvent = sink.Writes.First(logEvent => Matching.FromSource()(logEvent)); + + Assert.Equal("string", completionEvent.Properties["SomeString"].LiteralValue()); + Assert.Equal(200, completionEvent.Properties["Status"].LiteralValue()); + Assert.Equal("GET", completionEvent.Properties["RequestMethod"].LiteralValue()); + Assert.True(completionEvent.Properties.ContainsKey("ElapsedMilliseconds")); + Assert.False(completionEvent.Properties.ContainsKey("Elapsed")); + } + [Fact] public async Task RequestLoggingMiddlewareShouldEnrichWithCollectedExceptionIfNoUnhandledException() {