diff --git a/global.json b/global.json index 0a37afd..2be3e86 100644 --- a/global.json +++ b/global.json @@ -2,4 +2,4 @@ "sdk": { "version": "2.2.105" } -} +} \ No newline at end of file diff --git a/samples/InlineInitializationSample/Startup.cs b/samples/InlineInitializationSample/Startup.cs index 87d3a0c..8de3f04 100644 --- a/samples/InlineInitializationSample/Startup.cs +++ b/samples/InlineInitializationSample/Startup.cs @@ -1,10 +1,12 @@ -using Microsoft.AspNetCore.Builder; +using System.Net; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Serilog; +using Serilog.Events; namespace InlineInitializationSample { diff --git a/src/Serilog.AspNetCore/AspNetCore/RequestLoggingMiddleware.cs b/src/Serilog.AspNetCore/AspNetCore/RequestLoggingMiddleware.cs index db32cbe..9089efd 100644 --- a/src/Serilog.AspNetCore/AspNetCore/RequestLoggingMiddleware.cs +++ b/src/Serilog.AspNetCore/AspNetCore/RequestLoggingMiddleware.cs @@ -12,15 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Serilog.Events; using Serilog.Extensions.Hosting; using Serilog.Parsing; +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; namespace Serilog.AspNetCore { @@ -29,7 +29,7 @@ class RequestLoggingMiddleware readonly RequestDelegate _next; readonly DiagnosticContext _diagnosticContext; readonly MessageTemplate _messageTemplate; - + readonly Func _getLevel; static readonly LogEventProperty[] NoProperties = new LogEventProperty[0]; public RequestLoggingMiddleware(RequestDelegate next, DiagnosticContext diagnosticContext, RequestLoggingOptions options) @@ -38,6 +38,7 @@ public RequestLoggingMiddleware(RequestDelegate next, DiagnosticContext diagnost _next = next ?? throw new ArgumentNullException(nameof(next)); _diagnosticContext = diagnosticContext ?? throw new ArgumentNullException(nameof(diagnosticContext)); + _getLevel = options.GetLevel; _messageTemplate = new MessageTemplateParser().Parse(options.MessageTemplate); } @@ -71,10 +72,10 @@ public async Task Invoke(HttpContext httpContext) bool LogCompletion(HttpContext httpContext, DiagnosticContextCollector collector, int statusCode, double elapsedMs, Exception ex) { var logger = Log.ForContext(); - var level = statusCode > 499 ? LogEventLevel.Error : LogEventLevel.Information; + var level = _getLevel(httpContext, ex); if (!logger.IsEnabled(level)) return false; - + if (!collector.TryComplete(out var collectedProperties)) collectedProperties = NoProperties; @@ -97,7 +98,7 @@ static double GetElapsedMilliseconds(long start, long stop) { return (stop - start) * 1000 / (double)Stopwatch.Frequency; } - + static string GetPath(HttpContext httpContext) { return httpContext.Features.Get()?.RawTarget ?? httpContext.Request.Path.ToString(); diff --git a/src/Serilog.AspNetCore/AspNetCore/RequestLoggingOptions.cs b/src/Serilog.AspNetCore/AspNetCore/RequestLoggingOptions.cs new file mode 100644 index 0000000..c353e05 --- /dev/null +++ b/src/Serilog.AspNetCore/AspNetCore/RequestLoggingOptions.cs @@ -0,0 +1,48 @@ +// Copyright 2019 Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Microsoft.AspNetCore.Http; +using Serilog.Events; +using System; + +namespace Serilog.AspNetCore +{ + /// + /// Contains options for the . + /// + public class RequestLoggingOptions + { + /// + /// Gets or sets the message template. The default value is + /// "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms". The + /// template can contain any of the placeholders from the default template, names of properties + /// added by ASP.NET Core, and names of properties added to the . + /// + /// + /// The message template. + /// + public string MessageTemplate { get; set; } + + /// + /// Gets or sets the function returning the based on the and on the if something wrong happend + /// The default behavior returns LogEventLevel.Error when HttpStatusCode is greater than 499 or if Exception is not null. + /// + /// + /// The function returning the . + /// + public Func GetLevel { get; set; } + + internal RequestLoggingOptions() { } + } +} \ No newline at end of file diff --git a/src/Serilog.AspNetCore/RequestLoggingOptions.cs b/src/Serilog.AspNetCore/RequestLoggingOptions.cs deleted file mode 100644 index f68cf43..0000000 --- a/src/Serilog.AspNetCore/RequestLoggingOptions.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2019 Serilog Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System; - -namespace Serilog -{ - class RequestLoggingOptions - { - public string MessageTemplate { get; } - - public RequestLoggingOptions(string messageTemplate) - { - MessageTemplate = messageTemplate ?? throw new ArgumentNullException(nameof(messageTemplate)); - } - } -} diff --git a/src/Serilog.AspNetCore/SerilogApplicationBuilderExtensions.cs b/src/Serilog.AspNetCore/SerilogApplicationBuilderExtensions.cs index 91c7106..e1e2f53 100644 --- a/src/Serilog.AspNetCore/SerilogApplicationBuilderExtensions.cs +++ b/src/Serilog.AspNetCore/SerilogApplicationBuilderExtensions.cs @@ -14,7 +14,9 @@ using System; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; using Serilog.AspNetCore; +using Serilog.Events; namespace Serilog { @@ -26,6 +28,13 @@ public static class SerilogApplicationBuilderExtensions const string DefaultRequestCompletionMessageTemplate = "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms"; + static Func DefaultGetLevel = + (ctx, ex) => ex != null + ? LogEventLevel.Error + : ctx.Response.StatusCode > 499 + ? LogEventLevel.Error + : LogEventLevel.Information; + /// /// Adds middleware for streamlined request logging. Instead of writing HTTP request information /// like method, path, timing, status code and exception details @@ -43,11 +52,38 @@ public static class SerilogApplicationBuilderExtensions /// The application builder. public static IApplicationBuilder UseSerilogRequestLogging( this IApplicationBuilder app, - string messageTemplate = DefaultRequestCompletionMessageTemplate) + string messageTemplate) + => app.UseSerilogRequestLogging(opts => opts.MessageTemplate = messageTemplate); + + /// + /// Adds middleware for streamlined request logging. Instead of writing HTTP request information + /// like method, path, timing, status code and exception details + /// in several events, this middleware collects information during the request (including from + /// ), and writes a single event at request completion. Add this + /// in Startup.cs before any handlers whose activities should be logged. + /// + /// The application builder. + /// A to configure the provided . + /// The application builder. + public static IApplicationBuilder UseSerilogRequestLogging( + this IApplicationBuilder app, + Action configureOptions = null) { if (app == null) throw new ArgumentNullException(nameof(app)); - if (messageTemplate == null) throw new ArgumentNullException(nameof(messageTemplate)); - return app.UseMiddleware(new RequestLoggingOptions(messageTemplate)); + + var opts = new RequestLoggingOptions + { + GetLevel = DefaultGetLevel, + MessageTemplate = DefaultRequestCompletionMessageTemplate + }; + configureOptions?.Invoke(opts); + + if (opts.MessageTemplate == null) + throw new ArgumentException($"{nameof(opts.MessageTemplate)} cannot be null."); + if (opts.GetLevel == null) + throw new ArgumentException($"{nameof(opts.GetLevel)} cannot be null."); + + return app.UseMiddleware(opts); } } -} +} \ No newline at end of file