diff --git a/CHANGES.md b/CHANGES.md deleted file mode 100644 index be62715..0000000 --- a/CHANGES.md +++ /dev/null @@ -1,4 +0,0 @@ -2.0.0 - - * Initial version for ASP.NET Core 2. - diff --git a/README.md b/README.md index 8732504..4c324f6 100644 --- a/README.md +++ b/README.md @@ -109,10 +109,10 @@ Or [as JSON](https://github.com/serilog/serilog-formatting-compact): } ``` -To enable the middleware, first change the minimum level for `Microsoft` to `Warning` in your logger configuration or _appsettings.json_ file: +To enable the middleware, first change the minimum level for `Microsoft.AspNetCore` to `Warning` in your logger configuration or _appsettings.json_ file: ```csharp - .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) ``` Then, in your application's _Startup.cs_, add the middleware with `UseSerilogRequestLogging()`: @@ -205,7 +205,7 @@ Finally, pass the provider collection into `UseSerilog()`: Providers registered in _Startup.cs_ with `AddLogging()` will then receive events from Serilog. -**Using iniline initialization:** +**Using inline initialization:** If [inline initialization](#inline-initialization) is used, providers can be enabled by adding `writeToProviders: true` to the `UseSerilog()` method call: diff --git a/Setup.ps1 b/Setup.ps1 new file mode 100644 index 0000000..880a07a --- /dev/null +++ b/Setup.ps1 @@ -0,0 +1,11 @@ +$ErrorActionPreference = "Stop" + +$RequiredDotnetVersion = $(cat ./global.json | convertfrom-json).sdk.version + +mkdir "./build" + +Invoke-WebRequest "https://dot.net/v1/dotnet-install.ps1" -OutFile "./build/installcli.ps1" +& ./build/installcli.ps1 -InstallDir "$pwd/.dotnetcli" -NoPath -Version $RequiredDotnetVersion +if ($LASTEXITCODE) { exit 1 } + +$env:Path = "$pwd/.dotnetcli;$env:Path" diff --git a/appveyor.yml b/appveyor.yml index 8889af6..ef82a65 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,7 +2,7 @@ version: '{build}' skip_tags: true image: Visual Studio 2017 install: - - ps: mkdir -Force ".\build\" | Out-Null +- ps: ./Setup.ps1 build_script: - ps: ./Build.ps1 test: off diff --git a/global.json b/global.json index 0a37afd..2223a05 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "2.2.105" + "version": "3.0.100" } -} +} \ No newline at end of file diff --git a/samples/EarlyInitializationSample/EarlyInitializationSample.csproj b/samples/EarlyInitializationSample/EarlyInitializationSample.csproj index aa91d7f..0b71549 100644 --- a/samples/EarlyInitializationSample/EarlyInitializationSample.csproj +++ b/samples/EarlyInitializationSample/EarlyInitializationSample.csproj @@ -1,17 +1,11 @@  - netcoreapp2.2 - InProcess + netcoreapp3.0 - - - - - diff --git a/samples/EarlyInitializationSample/Program.cs b/samples/EarlyInitializationSample/Program.cs index 407d45e..284a26b 100644 --- a/samples/EarlyInitializationSample/Program.cs +++ b/samples/EarlyInitializationSample/Program.cs @@ -1,8 +1,8 @@ using System; using System.IO; -using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; using Serilog; namespace EarlyInitializationSample @@ -23,16 +23,14 @@ public static int Main(string[] args) .Enrich.FromLogContext() .WriteTo.Debug() .WriteTo.Console( - // {Properties:j} added: - outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} " + - "{Properties:j}{NewLine}{Exception}") + outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}") .CreateLogger(); try { Log.Information("Getting the motors running..."); - BuildWebHost(args).Run(); + CreateHostBuilder(args).Build().Run(); return 0; } @@ -47,11 +45,12 @@ public static int Main(string[] args) } } - public static IWebHost BuildWebHost(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseStartup() - .UseConfiguration(Configuration) - .UseSerilog() - .Build(); + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }) + .UseSerilog(); } } diff --git a/samples/EarlyInitializationSample/Startup.cs b/samples/EarlyInitializationSample/Startup.cs index 42e25e0..502c133 100644 --- a/samples/EarlyInitializationSample/Startup.cs +++ b/samples/EarlyInitializationSample/Startup.cs @@ -1,38 +1,19 @@ 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 Microsoft.Extensions.Hosting; using Serilog; namespace EarlyInitializationSample { public class Startup { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.Configure(options => - { - // This lambda determines whether user consent for non-essential cookies is needed for a given request. - options.CheckConsentNeeded = context => true; - options.MinimumSameSitePolicy = SameSiteMode.None; - }); - - - services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + services.AddControllersWithViews(); } - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IHostingEnvironment env) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { @@ -41,21 +22,23 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) else { app.UseExceptionHandler("/Home/Error"); + app.UseHsts(); } + app.UseStaticFiles(); + // Write streamlined request completion events, instead of the more verbose ones from the framework. // To use the default framework request logging instead, remove this line and set the "Microsoft" // level in appsettings.json to "Information". app.UseSerilogRequestLogging(); - app.UseStaticFiles(); - app.UseCookiePolicy(); + app.UseRouting(); - app.UseMvc(routes => + app.UseEndpoints(endpoints => { - routes.MapRoute( + endpoints.MapControllerRoute( name: "default", - template: "{controller=Home}/{action=Index}/{id?}"); + pattern: "{controller=Home}/{action=Index}/{id?}"); }); } } diff --git a/samples/InlineInitializationSample/InlineInitializationSample.csproj b/samples/InlineInitializationSample/InlineInitializationSample.csproj index aa91d7f..0b71549 100644 --- a/samples/InlineInitializationSample/InlineInitializationSample.csproj +++ b/samples/InlineInitializationSample/InlineInitializationSample.csproj @@ -1,17 +1,11 @@  - netcoreapp2.2 - InProcess + netcoreapp3.0 - - - - - diff --git a/samples/InlineInitializationSample/Program.cs b/samples/InlineInitializationSample/Program.cs index 18bca7d..9cf2e42 100644 --- a/samples/InlineInitializationSample/Program.cs +++ b/samples/InlineInitializationSample/Program.cs @@ -1,5 +1,5 @@ -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; using Serilog; namespace InlineInitializationSample @@ -8,19 +8,20 @@ public class Program { public static void Main(string[] args) { - CreateWebHostBuilder(args).Build().Run(); + CreateHostBuilder(args).Build().Run(); } - public static IWebHostBuilder CreateWebHostBuilder(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseStartup() + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }) .UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration .ReadFrom.Configuration(hostingContext.Configuration) .Enrich.FromLogContext() .WriteTo.Debug() .WriteTo.Console( - // {Properties:j} added: - outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} " + - "{Properties:j}{NewLine}{Exception}")); + outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}")); } } diff --git a/samples/InlineInitializationSample/Startup.cs b/samples/InlineInitializationSample/Startup.cs index 87d3a0c..82f3bcd 100644 --- a/samples/InlineInitializationSample/Startup.cs +++ b/samples/InlineInitializationSample/Startup.cs @@ -1,38 +1,21 @@ -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 Microsoft.Extensions.Hosting; using Serilog; +using Serilog.Events; namespace InlineInitializationSample { public class Startup { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.Configure(options => - { - // This lambda determines whether user consent for non-essential cookies is needed for a given request. - options.CheckConsentNeeded = context => true; - options.MinimumSameSitePolicy = SameSiteMode.None; - }); - - - services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + services.AddControllersWithViews(); } - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IHostingEnvironment env) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { @@ -41,21 +24,23 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) else { app.UseExceptionHandler("/Home/Error"); + app.UseHsts(); } + app.UseStaticFiles(); + // Write streamlined request completion events, instead of the more verbose ones from the framework. // To use the default framework request logging instead, remove this line and set the "Microsoft" // level in appsettings.json to "Information". app.UseSerilogRequestLogging(); - app.UseStaticFiles(); - app.UseCookiePolicy(); + app.UseRouting(); - app.UseMvc(routes => + app.UseEndpoints(endpoints => { - routes.MapRoute( + endpoints.MapControllerRoute( name: "default", - template: "{controller=Home}/{action=Index}/{id?}"); + pattern: "{controller=Home}/{action=Index}/{id?}"); }); } } diff --git a/serilog-aspnetcore.sln b/serilog-aspnetcore.sln index a50ef98..920326e 100644 --- a/serilog-aspnetcore.sln +++ b/serilog-aspnetcore.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.12 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29209.62 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A1893BD1-333D-4DFE-A0F0-DDBB2FE526E0}" EndProject @@ -16,6 +16,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{9C21B9 global.json = global.json README.md = README.md assets\Serilog.snk = assets\Serilog.snk + Setup.ps1 = Setup.ps1 EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.AspNetCore", "src\Serilog.AspNetCore\Serilog.AspNetCore.csproj", "{0549D23F-986B-4FB2-BACE-16FD7A7BC9EF}" diff --git a/src/Serilog.AspNetCore/AspNetCore/RequestLoggingMiddleware.cs b/src/Serilog.AspNetCore/AspNetCore/RequestLoggingMiddleware.cs index 797d896..954b326 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); } @@ -70,10 +71,11 @@ public async Task Invoke(HttpContext httpContext) bool LogCompletion(HttpContext httpContext, DiagnosticContextCollector collector, int statusCode, double elapsedMs, Exception ex) { - var level = statusCode > 499 ? LogEventLevel.Error : LogEventLevel.Information; + var logger = Log.ForContext(); + var level = _getLevel(httpContext, elapsedMs, ex); + + if (!logger.IsEnabled(level)) return false; - if (!Log.IsEnabled(level)) return false; - if (!collector.TryComplete(out var collectedProperties)) collectedProperties = NoProperties; @@ -87,7 +89,7 @@ bool LogCompletion(HttpContext httpContext, DiagnosticContextCollector collector }); var evt = new LogEvent(DateTimeOffset.Now, level, ex, _messageTemplate, properties); - Log.Write(evt); + logger.Write(evt); return false; } @@ -96,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..3a4127a --- /dev/null +++ b/src/Serilog.AspNetCore/AspNetCore/RequestLoggingOptions.cs @@ -0,0 +1,50 @@ +// 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 , the number of + /// elapsed milliseconds required for handling the request, and an if one was thrown. + /// The default behavior returns when the response status code is greater than 499 or if the + /// 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/Serilog.AspNetCore.csproj b/src/Serilog.AspNetCore/Serilog.AspNetCore.csproj index b3bc048..ed44508 100644 --- a/src/Serilog.AspNetCore/Serilog.AspNetCore.csproj +++ b/src/Serilog.AspNetCore/Serilog.AspNetCore.csproj @@ -2,7 +2,7 @@ Serilog support for ASP.NET Core logging - 3.0.0 + 3.1.0 Microsoft;Serilog Contributors netstandard2.0 true diff --git a/src/Serilog.AspNetCore/SerilogApplicationBuilderExtensions.cs b/src/Serilog.AspNetCore/SerilogApplicationBuilderExtensions.cs index 91c7106..838fd94 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 LogEventLevel DefaultGetLevel(HttpContext ctx, double _, Exception 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