diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..3dcd183 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,12 @@ + + + latest + True + true + ../../assets/Serilog.snk + true + false + enable + enable + + diff --git a/global.json b/global.json index 207e9f1..99ee408 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { "allowPrerelease": false, - "version": "5.0.201", + "version": "6.0.401", "rollForward": "latestFeature" } } diff --git a/samples/Sample/Controllers/HomeController.cs b/samples/Sample/Controllers/HomeController.cs index 9f1308d..ec4ab25 100644 --- a/samples/Sample/Controllers/HomeController.cs +++ b/samples/Sample/Controllers/HomeController.cs @@ -1,47 +1,40 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; +using System.Diagnostics; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; using Sample.Models; using Serilog; -namespace Sample.Controllers +namespace Sample.Controllers; + +public class HomeController : Controller { - public class HomeController : Controller + static int _callCount; + + readonly ILogger _logger; + readonly IDiagnosticContext _diagnosticContext; + + public HomeController(ILogger logger, IDiagnosticContext diagnosticContext) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _diagnosticContext = diagnosticContext ?? throw new ArgumentNullException(nameof(diagnosticContext)); + } + + public IActionResult Index() + { + _logger.LogInformation("Hello, world!"); + + _diagnosticContext.Set("IndexCallCount", Interlocked.Increment(ref _callCount)); + + return View(); + } + + public IActionResult Privacy() + { + throw new InvalidOperationException("Something went wrong."); + } + + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult Error() { - static int _callCount; - - readonly ILogger _logger; - readonly IDiagnosticContext _diagnosticContext; - - public HomeController(ILogger logger, IDiagnosticContext diagnosticContext) - { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _diagnosticContext = diagnosticContext ?? throw new ArgumentNullException(nameof(diagnosticContext)); - } - - public IActionResult Index() - { - _logger.LogInformation("Hello, world!"); - - _diagnosticContext.Set("IndexCallCount", Interlocked.Increment(ref _callCount)); - - return View(); - } - - public IActionResult Privacy() - { - throw new InvalidOperationException("Something went wrong."); - } - - [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - public IActionResult Error() - { - return View(new ErrorViewModel {RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier}); - } + return View(new ErrorViewModel {RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier}); } -} +} \ No newline at end of file diff --git a/samples/Sample/Models/ErrorViewModel.cs b/samples/Sample/Models/ErrorViewModel.cs index 68f449e..3fc26f0 100644 --- a/samples/Sample/Models/ErrorViewModel.cs +++ b/samples/Sample/Models/ErrorViewModel.cs @@ -1,11 +1,8 @@ -using System; +namespace Sample.Models; -namespace Sample.Models +public class ErrorViewModel { - public class ErrorViewModel - { - public string RequestId { get; set; } + public string? RequestId { get; set; } - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - } + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); } \ No newline at end of file diff --git a/samples/Sample/Program.cs b/samples/Sample/Program.cs index 2bd5e57..c80e108 100644 --- a/samples/Sample/Program.cs +++ b/samples/Sample/Program.cs @@ -1,53 +1,44 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using Serilog; -namespace Sample +namespace Sample; + +public static class Program { - public static class Program + public static int Main(string[] args) { - public static int Main(string[] args) - { - // The initial "bootstrap" logger is able to log errors during start-up. It's completely replaced by the - // logger configured in `UseSerilog()` below, once configuration and dependency-injection have both been - // set up successfully. - Log.Logger = new LoggerConfiguration() - .WriteTo.Console() - .CreateBootstrapLogger(); - - Log.Information("Starting up!"); + // The initial "bootstrap" logger is able to log errors during start-up. It's completely replaced by the + // logger configured in `UseSerilog()` below, once configuration and dependency-injection have both been + // set up successfully. + Log.Logger = new LoggerConfiguration() + .WriteTo.Console() + .CreateBootstrapLogger(); - try - { - CreateHostBuilder(args).Build().Run(); + Log.Information("Starting up!"); - Log.Information("Stopped cleanly"); - return 0; - } - catch (Exception ex) - { - Log.Fatal(ex, "An unhandled exception occured during bootstrapping"); - return 1; - } - finally - { - Log.CloseAndFlush(); - } - } + try + { + CreateHostBuilder(args).Build().Run(); - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .UseSerilog((context, services, configuration) => configuration - .ReadFrom.Configuration(context.Configuration) - .ReadFrom.Services(services) - .Enrich.FromLogContext() - .WriteTo.Console()) - .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); + Log.Information("Stopped cleanly"); + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "An unhandled exception occured during bootstrapping"); + return 1; + } + finally + { + Log.CloseAndFlush(); + } } -} + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .UseSerilog((context, services, configuration) => configuration + .ReadFrom.Configuration(context.Configuration) + .ReadFrom.Services(services) + .Enrich.FromLogContext() + .WriteTo.Console()) + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); +} \ No newline at end of file diff --git a/samples/Sample/Sample.csproj b/samples/Sample/Sample.csproj index dff37ac..f32c84b 100644 --- a/samples/Sample/Sample.csproj +++ b/samples/Sample/Sample.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 diff --git a/samples/Sample/Startup.cs b/samples/Sample/Startup.cs index 6c29268..67200ce 100644 --- a/samples/Sample/Startup.cs +++ b/samples/Sample/Startup.cs @@ -1,64 +1,53 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.HttpsPolicy; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using Serilog; -namespace Sample +namespace Sample; + +public class Startup { - public class Startup + public Startup(IConfiguration configuration) { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } + Configuration = configuration; + } - public IConfiguration Configuration { get; } + 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.AddControllersWithViews(); + } - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) { - services.AddControllersWithViews(); + app.UseDeveloperExceptionPage(); } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + else { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Home/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - } + app.UseExceptionHandler("/Home/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } - app.UseHttpsRedirection(); - 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.UseHttpsRedirection(); + app.UseStaticFiles(); - app.UseRouting(); + // 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.UseAuthorization(); + app.UseRouting(); - app.UseEndpoints(endpoints => - { - endpoints.MapControllerRoute( - name: "default", - pattern: "{controller=Home}/{action=Index}/{id?}"); - }); - } + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}"); + }); } } \ No newline at end of file diff --git a/serilog-aspnetcore.sln b/serilog-aspnetcore.sln index bc01e54..a9b5e42 100644 --- a/serilog-aspnetcore.sln +++ b/serilog-aspnetcore.sln @@ -17,6 +17,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{9C21B9 README.md = README.md assets\Serilog.snk = assets\Serilog.snk Setup.ps1 = Setup.ps1 + Directory.Build.props = Directory.Build.props 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 9b9f61c..0b0b3eb 100644 --- a/src/Serilog.AspNetCore/AspNetCore/RequestLoggingMiddleware.cs +++ b/src/Serilog.AspNetCore/AspNetCore/RequestLoggingMiddleware.cs @@ -17,111 +17,106 @@ using Serilog.Events; using Serilog.Extensions.Hosting; using Serilog.Parsing; -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -namespace Serilog.AspNetCore +namespace Serilog.AspNetCore; + +// ReSharper disable once ClassNeverInstantiated.Global +class RequestLoggingMiddleware { - // ReSharper disable once ClassNeverInstantiated.Global - class RequestLoggingMiddleware + readonly RequestDelegate _next; + readonly DiagnosticContext _diagnosticContext; + 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]; + + public RequestLoggingMiddleware(RequestDelegate next, DiagnosticContext diagnosticContext, RequestLoggingOptions options) { - readonly RequestDelegate _next; - readonly DiagnosticContext _diagnosticContext; - 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]; - - public RequestLoggingMiddleware(RequestDelegate next, DiagnosticContext diagnosticContext, RequestLoggingOptions options) + if (options == null) throw new ArgumentNullException(nameof(options)); + _next = next ?? throw new ArgumentNullException(nameof(next)); + _diagnosticContext = diagnosticContext ?? throw new ArgumentNullException(nameof(diagnosticContext)); + + _getLevel = options.GetLevel; + _enrichDiagnosticContext = options.EnrichDiagnosticContext; + _messageTemplate = new MessageTemplateParser().Parse(options.MessageTemplate); + _logger = options.Logger?.ForContext(); + _includeQueryInRequestPath = options.IncludeQueryInRequestPath; + _getMessageTemplateProperties = options.GetMessageTemplateProperties; + } + + // ReSharper disable once UnusedMember.Global + public async Task Invoke(HttpContext httpContext) + { + if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); + + var start = Stopwatch.GetTimestamp(); + + var collector = _diagnosticContext.BeginCollection(); + try { - if (options == null) throw new ArgumentNullException(nameof(options)); - _next = next ?? throw new ArgumentNullException(nameof(next)); - _diagnosticContext = diagnosticContext ?? throw new ArgumentNullException(nameof(diagnosticContext)); - - _getLevel = options.GetLevel; - _enrichDiagnosticContext = options.EnrichDiagnosticContext; - _messageTemplate = new MessageTemplateParser().Parse(options.MessageTemplate); - _logger = options.Logger?.ForContext(); - _includeQueryInRequestPath = options.IncludeQueryInRequestPath; - _getMessageTemplateProperties = options.GetMessageTemplateProperties; + await _next(httpContext); + var elapsedMs = GetElapsedMilliseconds(start, Stopwatch.GetTimestamp()); + var statusCode = httpContext.Response.StatusCode; + LogCompletion(httpContext, collector, statusCode, elapsedMs, null); } - - // ReSharper disable once UnusedMember.Global - public async Task Invoke(HttpContext httpContext) + catch (Exception ex) + // Never caught, because `LogCompletion()` returns false. This ensures e.g. the developer exception page is still + // shown, although it does also mean we see a duplicate "unhandled exception" event from ASP.NET Core. + when (LogCompletion(httpContext, collector, 500, GetElapsedMilliseconds(start, Stopwatch.GetTimestamp()), ex)) { - if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); - - var start = Stopwatch.GetTimestamp(); - - var collector = _diagnosticContext.BeginCollection(); - try - { - await _next(httpContext); - var elapsedMs = GetElapsedMilliseconds(start, Stopwatch.GetTimestamp()); - var statusCode = httpContext.Response.StatusCode; - LogCompletion(httpContext, collector, statusCode, elapsedMs, null); - } - catch (Exception ex) - // Never caught, because `LogCompletion()` returns false. This ensures e.g. the developer exception page is still - // shown, although it does also mean we see a duplicate "unhandled exception" event from ASP.NET Core. - when (LogCompletion(httpContext, collector, 500, GetElapsedMilliseconds(start, Stopwatch.GetTimestamp()), ex)) - { - } - finally - { - collector.Dispose(); - } } - - bool LogCompletion(HttpContext httpContext, DiagnosticContextCollector collector, int statusCode, double elapsedMs, Exception ex) + finally { - var logger = _logger ?? Log.ForContext(); - var level = _getLevel(httpContext, elapsedMs, ex); + collector.Dispose(); + } + } - if (!logger.IsEnabled(level)) return false; + bool LogCompletion(HttpContext httpContext, DiagnosticContextCollector collector, int statusCode, double elapsedMs, Exception? ex) + { + var logger = _logger ?? Log.ForContext(); + var level = _getLevel(httpContext, elapsedMs, ex); - // Enrich diagnostic context - _enrichDiagnosticContext?.Invoke(_diagnosticContext, httpContext); + if (!logger.IsEnabled(level)) return false; - if (!collector.TryComplete(out var collectedProperties, out var collectedException)) - collectedProperties = NoProperties; + // Enrich diagnostic context + _enrichDiagnosticContext?.Invoke(_diagnosticContext, httpContext); - // Last-in (correctly) wins... - var properties = collectedProperties.Concat(_getMessageTemplateProperties(httpContext, GetPath(httpContext, _includeQueryInRequestPath), elapsedMs, statusCode)); + if (!collector.TryComplete(out var collectedProperties, out var collectedException)) + collectedProperties = NoProperties; - var evt = new LogEvent(DateTimeOffset.Now, level, ex ?? collectedException, _messageTemplate, properties); - logger.Write(evt); + // Last-in (correctly) wins... + var properties = collectedProperties.Concat(_getMessageTemplateProperties(httpContext, GetPath(httpContext, _includeQueryInRequestPath), elapsedMs, statusCode)); - return false; - } + var evt = new LogEvent(DateTimeOffset.Now, level, ex ?? collectedException, _messageTemplate, properties); + logger.Write(evt); - static double GetElapsedMilliseconds(long start, long stop) - { - return (stop - start) * 1000 / (double)Stopwatch.Frequency; - } + return false; + } - static string GetPath(HttpContext httpContext, bool includeQueryInRequestPath) + static double GetElapsedMilliseconds(long start, long stop) + { + return (stop - start) * 1000 / (double)Stopwatch.Frequency; + } + + static string GetPath(HttpContext httpContext, bool includeQueryInRequestPath) + { + /* + In some cases, like when running integration tests with WebApplicationFactory + the Path returns an empty string instead of null, in that case we can't use + ?? as fallback. + */ + var requestPath = includeQueryInRequestPath + ? httpContext.Features.Get()?.RawTarget + : httpContext.Features.Get()?.Path; + if (string.IsNullOrEmpty(requestPath)) { - /* - In some cases, like when running integration tests with WebApplicationFactory - the Path returns an empty string instead of null, in that case we can't use - ?? as fallback. - */ - var requestPath = includeQueryInRequestPath - ? httpContext.Features.Get()?.RawTarget - : httpContext.Features.Get()?.Path; - if (string.IsNullOrEmpty(requestPath)) - { - requestPath = httpContext.Request.Path.ToString(); - } - - return requestPath; + requestPath = httpContext.Request.Path.ToString(); } + + return requestPath!; } -} +} \ No newline at end of file diff --git a/src/Serilog.AspNetCore/AspNetCore/RequestLoggingOptions.cs b/src/Serilog.AspNetCore/AspNetCore/RequestLoggingOptions.cs index 2227388..d782e6d 100644 --- a/src/Serilog.AspNetCore/AspNetCore/RequestLoggingOptions.cs +++ b/src/Serilog.AspNetCore/AspNetCore/RequestLoggingOptions.cs @@ -14,89 +14,86 @@ using Microsoft.AspNetCore.Http; using Serilog.Events; -using System; -using System.Collections.Generic; // ReSharper disable UnusedAutoPropertyAccessor.Global -namespace Serilog.AspNetCore +namespace Serilog.AspNetCore; + +/// +/// Contains options for the . +/// +public class RequestLoggingOptions { - /// - /// Contains options for the . - /// - public class RequestLoggingOptions - { - const string DefaultRequestCompletionMessageTemplate = - "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms"; + const string DefaultRequestCompletionMessageTemplate = + "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms"; - static LogEventLevel DefaultGetLevel(HttpContext ctx, double _, Exception ex) => - ex != null + static LogEventLevel DefaultGetLevel(HttpContext ctx, double _, Exception? ex) => + ex != null + ? LogEventLevel.Error + : ctx.Response.StatusCode > 499 ? LogEventLevel.Error - : 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)) - }; + : LogEventLevel.Information; - /// - /// 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; } + static 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)) + }; - /// - /// A 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. - /// - /// - /// A function returning the . - /// - public Func GetLevel { get; set; } + /// + /// 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; } - /// - /// A callback that can be used to set additional properties on the request completion event. - /// - public Action EnrichDiagnosticContext { get; set; } + /// + /// A 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. + /// + /// + /// A function returning the . + /// + public Func GetLevel { get; set; } + + /// + /// A callback that can be used to set additional properties on the request completion event. + /// + public Action? EnrichDiagnosticContext { get; set; } - /// - /// The logger through which request completion events will be logged. The default is to use the - /// static class. - /// - public ILogger Logger { get; set; } + /// + /// The logger through which request completion events will be logged. The default is to use the + /// static class. + /// + public ILogger? Logger { get; set; } - /// - /// Include the full URL query string in the RequestPath property - /// that is attached to request log events. The default is false. - /// - public bool IncludeQueryInRequestPath { get; set; } + /// + /// Include the full URL query string in the RequestPath property + /// that is attached to request log events. The default is false. + /// + public bool IncludeQueryInRequestPath { get; set; } - /// - /// A function to specify the values of the MessageTemplateProperties. - /// - public Func> GetMessageTemplateProperties { get; set; } + /// + /// A function to specify the values of the MessageTemplateProperties. + /// + public Func> GetMessageTemplateProperties { get; set; } - /// - /// Constructor - /// - public RequestLoggingOptions() - { - GetLevel = DefaultGetLevel; - MessageTemplate = DefaultRequestCompletionMessageTemplate; - GetMessageTemplateProperties = DefaultGetMessageTemplateProperties; - } + /// + /// Constructor + /// + public RequestLoggingOptions() + { + GetLevel = DefaultGetLevel; + MessageTemplate = DefaultRequestCompletionMessageTemplate; + GetMessageTemplateProperties = DefaultGetMessageTemplateProperties; } -} +} \ No newline at end of file diff --git a/src/Serilog.AspNetCore/AspNetCore/SerilogLoggerFactory.cs b/src/Serilog.AspNetCore/AspNetCore/SerilogLoggerFactory.cs index b468dea..01b3686 100644 --- a/src/Serilog.AspNetCore/AspNetCore/SerilogLoggerFactory.cs +++ b/src/Serilog.AspNetCore/AspNetCore/SerilogLoggerFactory.cs @@ -12,62 +12,60 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using System.ComponentModel; using Microsoft.Extensions.Logging; using Serilog.Debugging; using Serilog.Extensions.Logging; -namespace Serilog.AspNetCore +namespace Serilog.AspNetCore; + +/// +/// Implements so that we can inject Serilog Logger. +/// +[Obsolete("Replaced with Serilog.Extensions.Logging.SerilogLoggerFactory")] +[EditorBrowsable(EditorBrowsableState.Never)] +public class SerilogLoggerFactory : ILoggerFactory { + private readonly SerilogLoggerProvider _provider; + /// - /// Implements so that we can inject Serilog Logger. + /// Initializes a new instance of the class. /// - [Obsolete("Replaced with Serilog.Extensions.Logging.SerilogLoggerFactory")] - [EditorBrowsable(EditorBrowsableState.Never)] - public class SerilogLoggerFactory : ILoggerFactory + /// The Serilog logger; if not supplied, the static will be used. + /// When true, dispose when the framework disposes the provider. If the + /// logger is not specified but is true, the method will be + /// called on the static class instead. + public SerilogLoggerFactory(ILogger? logger = null, bool dispose = false) { - private readonly SerilogLoggerProvider _provider; - - /// - /// Initializes a new instance of the class. - /// - /// The Serilog logger; if not supplied, the static will be used. - /// When true, dispose when the framework disposes the provider. If the - /// logger is not specified but is true, the method will be - /// called on the static class instead. - public SerilogLoggerFactory(ILogger logger = null, bool dispose = false) - { - _provider = new SerilogLoggerProvider(logger, dispose); - } + _provider = new(logger, dispose); + } - /// - /// Disposes the provider. - /// - public void Dispose() - { - _provider.Dispose(); - } + /// + /// Disposes the provider. + /// + public void Dispose() + { + _provider.Dispose(); + } - /// - /// Creates a new instance. - /// - /// The category name for messages produced by the logger. - /// - /// The . - /// - public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) - { - return _provider.CreateLogger(categoryName); - } + /// + /// Creates a new instance. + /// + /// The category name for messages produced by the logger. + /// + /// The . + /// + public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) + { + return _provider.CreateLogger(categoryName); + } - /// - /// Adds an to the logging system. - /// - /// The . - public void AddProvider(ILoggerProvider provider) - { - SelfLog.WriteLine("Ignoring added logger provider {0}", provider); - } + /// + /// Adds an to the logging system. + /// + /// The . + public void AddProvider(ILoggerProvider provider) + { + SelfLog.WriteLine("Ignoring added logger provider {0}", provider); } -} +} \ No newline at end of file diff --git a/src/Serilog.AspNetCore/Serilog.AspNetCore.csproj b/src/Serilog.AspNetCore/Serilog.AspNetCore.csproj index 1635423..d468a42 100644 --- a/src/Serilog.AspNetCore/Serilog.AspNetCore.csproj +++ b/src/Serilog.AspNetCore/Serilog.AspNetCore.csproj @@ -5,11 +5,7 @@ 6.1.0 Microsoft;Serilog Contributors netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0 - true true - ../../assets/Serilog.snk - true - true serilog;aspnet;aspnetcore icon.png https://github.com/serilog/serilog-aspnetcore diff --git a/src/Serilog.AspNetCore/SerilogApplicationBuilderExtensions.cs b/src/Serilog.AspNetCore/SerilogApplicationBuilderExtensions.cs index b163fc7..6427a6d 100644 --- a/src/Serilog.AspNetCore/SerilogApplicationBuilderExtensions.cs +++ b/src/Serilog.AspNetCore/SerilogApplicationBuilderExtensions.cs @@ -12,67 +12,64 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; - using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Serilog.AspNetCore; -namespace Serilog +namespace Serilog; + +/// +/// Extends with methods for configuring Serilog features. +/// +public static class SerilogApplicationBuilderExtensions { /// - /// Extends with methods for configuring Serilog features. + /// 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. /// - public static class SerilogApplicationBuilderExtensions - { - /// - /// 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. - /// The message template to use when logging request completion - /// events. The default 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 application builder. - public static IApplicationBuilder UseSerilogRequestLogging( - this IApplicationBuilder app, - string messageTemplate) - => app.UseSerilogRequestLogging(opts => opts.MessageTemplate = messageTemplate); + /// The application builder. + /// The message template to use when logging request completion + /// events. The default 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 application builder. + public static IApplicationBuilder UseSerilogRequestLogging( + this IApplicationBuilder app, + 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)); + /// + /// 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)); - var opts = app.ApplicationServices.GetService>()?.Value ?? new RequestLoggingOptions(); + var opts = app.ApplicationServices.GetService>()?.Value ?? new RequestLoggingOptions(); - configureOptions?.Invoke(opts); + 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."); + 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); - } + return app.UseMiddleware(opts); } } \ No newline at end of file diff --git a/src/Serilog.AspNetCore/SerilogWebHostBuilderExtensions.cs b/src/Serilog.AspNetCore/SerilogWebHostBuilderExtensions.cs index 7de1bef..a144808 100644 --- a/src/Serilog.AspNetCore/SerilogWebHostBuilderExtensions.cs +++ b/src/Serilog.AspNetCore/SerilogWebHostBuilderExtensions.cs @@ -12,154 +12,152 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Logging; using Serilog.Extensions.Hosting; using Serilog.Extensions.Logging; -namespace Serilog +namespace Serilog; + +/// +/// Extends with Serilog configuration methods. +/// +public static class SerilogWebHostBuilderExtensions { /// - /// Extends with Serilog configuration methods. + /// Sets Serilog as the logging provider. /// - public static class SerilogWebHostBuilderExtensions - { - /// - /// Sets Serilog as the logging provider. - /// - /// The web host builder to configure. - /// The Serilog logger; if not supplied, the static will be used. - /// When true, dispose when the framework disposes the provider. If the - /// logger is not specified but is true, the method will be - /// called on the static class instead. - /// A registered in the Serilog pipeline using the - /// WriteTo.Providers() configuration method, enabling other s to receive events. By - /// default, only Serilog sinks will receive events. - /// The web host builder. + /// The web host builder to configure. + /// The Serilog logger; if not supplied, the static will be used. + /// When true, dispose when the framework disposes the provider. If the + /// logger is not specified but is true, the method will be + /// called on the static class instead. + /// A registered in the Serilog pipeline using the + /// WriteTo.Providers() configuration method, enabling other s to receive events. By + /// default, only Serilog sinks will receive events. + /// The web host builder. #if HOSTBUILDER [Obsolete("Prefer UseSerilog() on IHostBuilder")] #endif - public static IWebHostBuilder UseSerilog( - this IWebHostBuilder builder, - ILogger logger = null, - bool dispose = false, - LoggerProviderCollection providers = null) - { - if (builder == null) throw new ArgumentNullException(nameof(builder)); + public static IWebHostBuilder UseSerilog( + this IWebHostBuilder builder, + ILogger? logger = null, + bool dispose = false, + LoggerProviderCollection? providers = null) + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); - builder.ConfigureServices(collection => + builder.ConfigureServices(collection => + { + if (providers != null) { - if (providers != null) + collection.AddSingleton(services => { - collection.AddSingleton(services => - { - var factory = new SerilogLoggerFactory(logger, dispose, providers); + var factory = new SerilogLoggerFactory(logger, dispose, providers); - foreach (var provider in services.GetServices()) - factory.AddProvider(provider); + foreach (var provider in services.GetServices()) + factory.AddProvider(provider); - return factory; - }); - } - else - { - collection.AddSingleton(services => new SerilogLoggerFactory(logger, dispose)); - } + return factory; + }); + } + else + { + collection.AddSingleton(_ => new SerilogLoggerFactory(logger, dispose)); + } - ConfigureServices(collection, logger); - }); + ConfigureServices(collection, logger); + }); - return builder; - } + return builder; + } - /// Sets Serilog as the logging provider. - /// - /// A is supplied so that configuration and hosting information can be used. - /// The logger will be shut down when application services are disposed. - /// - /// The web host builder to configure. - /// The delegate for configuring the that will be used to construct a . - /// Indicates whether to preserve the value of . - /// By default, Serilog does not write events to s registered through - /// the Microsoft.Extensions.Logging API. Normally, equivalent Serilog sinks are used in place of providers. Specify - /// true to write events to all providers. - /// The web host builder. + /// Sets Serilog as the logging provider. + /// + /// A is supplied so that configuration and hosting information can be used. + /// The logger will be shut down when application services are disposed. + /// + /// The web host builder to configure. + /// The delegate for configuring the that will be used to construct a . + /// Indicates whether to preserve the value of . + /// By default, Serilog does not write events to s registered through + /// the Microsoft.Extensions.Logging API. Normally, equivalent Serilog sinks are used in place of providers. Specify + /// true to write events to all providers. + /// The web host builder. #if HOSTBUILDER [Obsolete("Prefer UseSerilog() on IHostBuilder")] #endif - public static IWebHostBuilder UseSerilog( - this IWebHostBuilder builder, - Action configureLogger, - bool preserveStaticLogger = false, - bool writeToProviders = false) + public static IWebHostBuilder UseSerilog( + this IWebHostBuilder builder, + Action configureLogger, + bool preserveStaticLogger = false, + bool writeToProviders = false) + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + if (configureLogger == null) throw new ArgumentNullException(nameof(configureLogger)); + + builder.ConfigureServices((context, collection) => { - if (builder == null) throw new ArgumentNullException(nameof(builder)); - if (configureLogger == null) throw new ArgumentNullException(nameof(configureLogger)); + var loggerConfiguration = new LoggerConfiguration(); - builder.ConfigureServices((context, collection) => + LoggerProviderCollection? loggerProviders = null; + if (writeToProviders) { - var loggerConfiguration = new LoggerConfiguration(); + loggerProviders = new(); + loggerConfiguration.WriteTo.Providers(loggerProviders); + } - LoggerProviderCollection loggerProviders = null; - if (writeToProviders) - { - loggerProviders = new LoggerProviderCollection(); - loggerConfiguration.WriteTo.Providers(loggerProviders); - } + configureLogger(context, loggerConfiguration); + var logger = loggerConfiguration.CreateLogger(); - configureLogger(context, loggerConfiguration); - var logger = loggerConfiguration.CreateLogger(); - - ILogger registeredLogger = null; - if (preserveStaticLogger) - { - registeredLogger = logger; - } - else + ILogger? registeredLogger = null; + if (preserveStaticLogger) + { + registeredLogger = logger; + } + else + { + // Passing a `null` logger to `SerilogLoggerFactory` results in disposal via + // `Log.CloseAndFlush()`, which additionally replaces the static logger with a no-op. + Log.Logger = logger; + } + + collection.AddSingleton(services => + { + var factory = new SerilogLoggerFactory(registeredLogger, true, loggerProviders); + + if (writeToProviders) { - // Passing a `null` logger to `SerilogLoggerFactory` results in disposal via - // `Log.CloseAndFlush()`, which additionally replaces the static logger with a no-op. - Log.Logger = logger; + foreach (var provider in services.GetServices()) + factory.AddProvider(provider); } - collection.AddSingleton(services => - { - var factory = new SerilogLoggerFactory(registeredLogger, true, loggerProviders); + return factory; + }); - if (writeToProviders) - { - foreach (var provider in services.GetServices()) - factory.AddProvider(provider); - } + ConfigureServices(collection, logger); + }); + return builder; + } - return factory; - }); + static void ConfigureServices(IServiceCollection collection, ILogger? logger) + { + if (collection == null) throw new ArgumentNullException(nameof(collection)); - ConfigureServices(collection, logger); - }); - return builder; - } - - static void ConfigureServices(IServiceCollection collection, ILogger logger) + if (logger != null) { - if (collection == null) throw new ArgumentNullException(nameof(collection)); - - if (logger != null) - { - // This won't (and shouldn't) take ownership of the logger. - collection.AddSingleton(logger); - } + // This won't (and shouldn't) take ownership of the logger. + collection.AddSingleton(logger); + } - // Registered to provide two services... - var diagnosticContext = new DiagnosticContext(logger); + // Registered to provide two services... + var diagnosticContext = new DiagnosticContext(logger); - // Consumed by e.g. middleware - collection.AddSingleton(diagnosticContext); + // Consumed by e.g. middleware + collection.AddSingleton(diagnosticContext); - // Consumed by user code - collection.AddSingleton(diagnosticContext); - } + // Consumed by user code + collection.AddSingleton(diagnosticContext); } -} +} \ No newline at end of file diff --git a/test/Serilog.AspNetCore.Tests/Serilog.AspNetCore.Tests.csproj b/test/Serilog.AspNetCore.Tests/Serilog.AspNetCore.Tests.csproj index c229883..240288c 100644 --- a/test/Serilog.AspNetCore.Tests/Serilog.AspNetCore.Tests.csproj +++ b/test/Serilog.AspNetCore.Tests/Serilog.AspNetCore.Tests.csproj @@ -1,11 +1,7 @@  - netcoreapp3.1;net5.0 - Serilog.AspNetCore.Tests - ../../assets/Serilog.snk - true - true + netcoreapp3.1;net6.0 true @@ -23,7 +19,7 @@ - + diff --git a/test/Serilog.AspNetCore.Tests/SerilogWebHostBuilderExtensionsTests.cs b/test/Serilog.AspNetCore.Tests/SerilogWebHostBuilderExtensionsTests.cs index 09f6c1b..842c2d1 100644 --- a/test/Serilog.AspNetCore.Tests/SerilogWebHostBuilderExtensionsTests.cs +++ b/test/Serilog.AspNetCore.Tests/SerilogWebHostBuilderExtensionsTests.cs @@ -1,9 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Linq; -using System.Threading.Tasks; using Xunit; using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Hosting; @@ -13,171 +10,173 @@ using Serilog.Filters; using Serilog.AspNetCore.Tests.Support; using Serilog.Events; -using System.Net.Http; // Newer frameworks provide IHostBuilder #pragma warning disable CS0618 -namespace Serilog.AspNetCore.Tests +namespace Serilog.AspNetCore.Tests; + +public class SerilogWebHostBuilderExtensionsTests : IClassFixture { - public class SerilogWebHostBuilderExtensionsTests : IClassFixture + readonly SerilogWebApplicationFactory _web; + + public SerilogWebHostBuilderExtensionsTests(SerilogWebApplicationFactory web) { - readonly SerilogWebApplicationFactory _web; + _web = web; + } - public SerilogWebHostBuilderExtensionsTests(SerilogWebApplicationFactory web) + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task DisposeShouldBeHandled(bool dispose) + { + var logger = new DisposeTrackingLogger(); + using (var web = Setup(logger, dispose)) { - _web = web; + await web.CreateClient().GetAsync("/"); } - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task DisposeShouldBeHandled(bool dispose) - { - var logger = new DisposeTrackingLogger(); - using (var web = Setup(logger, dispose)) - { - await web.CreateClient().GetAsync("/"); - } - - Assert.Equal(dispose, logger.IsDisposed); - } + Assert.Equal(dispose, logger.IsDisposed); + } - [Fact] - public async Task RequestLoggingMiddlewareShouldEnrich() + [Fact] + public async Task RequestLoggingMiddlewareShouldEnrich() + { + var (sink, web) = Setup(options => { - var (sink, web) = Setup(options => + options.EnrichDiagnosticContext += (diagnosticContext, _) => { - options.EnrichDiagnosticContext += (diagnosticContext, httpContext) => - { - diagnosticContext.Set("SomeInteger", 42); - }; - }); + diagnosticContext.Set("SomeInteger", 42); + }; + }); - await web.CreateClient().GetAsync("/resource"); + await web.CreateClient().GetAsync("/resource"); - Assert.NotEmpty(sink.Writes); + Assert.NotEmpty(sink.Writes); - var completionEvent = sink.Writes.First(logEvent => Matching.FromSource()(logEvent)); + var completionEvent = sink.Writes.First(logEvent => Matching.FromSource()(logEvent)); - Assert.Equal(42, completionEvent.Properties["SomeInteger"].LiteralValue()); - Assert.Equal("string", completionEvent.Properties["SomeString"].LiteralValue()); - Assert.Equal("/resource", completionEvent.Properties["RequestPath"].LiteralValue()); - Assert.Equal(200, completionEvent.Properties["StatusCode"].LiteralValue()); - Assert.Equal("GET", completionEvent.Properties["RequestMethod"].LiteralValue()); - Assert.True(completionEvent.Properties.ContainsKey("Elapsed")); - } + Assert.Equal(42, completionEvent.Properties["SomeInteger"].LiteralValue()); + Assert.Equal("string", completionEvent.Properties["SomeString"].LiteralValue()); + Assert.Equal("/resource", completionEvent.Properties["RequestPath"].LiteralValue()); + Assert.Equal(200, completionEvent.Properties["StatusCode"].LiteralValue()); + Assert.Equal("GET", completionEvent.Properties["RequestMethod"].LiteralValue()); + Assert.True(completionEvent.Properties.ContainsKey("Elapsed")); + } - [Fact] - public async Task RequestLoggingMiddlewareShouldEnrichWithCustomisedProperties() + [Fact] + public async Task RequestLoggingMiddlewareShouldEnrichWithCustomisedProperties() + { + var (sink, web) = Setup(options => { - 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)) - }; - }); + options.MessageTemplate = "HTTP {RequestMethod} responded {Status} in {ElapsedMilliseconds:0.0000} ms"; + options.GetMessageTemplateProperties = (ctx, _, 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"); + await web.CreateClient().GetAsync("/resource"); - Assert.NotEmpty(sink.Writes); + Assert.NotEmpty(sink.Writes); - var completionEvent = sink.Writes.First(logEvent => Matching.FromSource()(logEvent)); + 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")); - } + 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() + [Fact] + public async Task RequestLoggingMiddlewareShouldEnrichWithCollectedExceptionIfNoUnhandledException() + { + var diagnosticContextException = new Exception("Exception set in diagnostic context"); + var (sink, web) = Setup(options => { - var diagnosticContextException = new Exception("Exception set in diagnostic context"); - var (sink, web) = Setup(options => + options.EnrichDiagnosticContext += (diagnosticContext, _) => { - options.EnrichDiagnosticContext += (diagnosticContext, _) => - { - diagnosticContext.SetException(diagnosticContextException); - }; - }); + diagnosticContext.SetException(diagnosticContextException); + }; + }); - await web.CreateClient().GetAsync("/resource"); + await web.CreateClient().GetAsync("/resource"); - var completionEvent = sink.Writes.First(logEvent => Matching.FromSource()(logEvent)); + var completionEvent = sink.Writes.First(logEvent => Matching.FromSource()(logEvent)); - Assert.Same(diagnosticContextException, completionEvent.Exception); - } + Assert.Same(diagnosticContextException, completionEvent.Exception); + } - [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task RequestLoggingMiddlewareShouldEnrichWithUnhandledExceptionEvenIfExceptionIsSetInDiagnosticContext(bool setExceptionInDiagnosticContext) + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task RequestLoggingMiddlewareShouldEnrichWithUnhandledExceptionEvenIfExceptionIsSetInDiagnosticContext(bool setExceptionInDiagnosticContext) + { + var diagnosticContextException = new Exception("Exception set in diagnostic context"); + var unhandledException = new Exception("Unhandled exception thrown in API action"); + var (sink, web) = Setup(options => { - var diagnosticContextException = new Exception("Exception set in diagnostic context"); - var unhandledException = new Exception("Unhandled exception thrown in API action"); - var (sink, web) = Setup(options => + options.EnrichDiagnosticContext += (diagnosticContext, _) => { - options.EnrichDiagnosticContext += (diagnosticContext, _) => - { - if (setExceptionInDiagnosticContext) - diagnosticContext.SetException(diagnosticContextException); - }; - }, actionCallback: _ => throw unhandledException); + if (setExceptionInDiagnosticContext) + diagnosticContext.SetException(diagnosticContextException); + }; + }, actionCallback: _ => throw unhandledException); - Func act = () => web.CreateClient().GetAsync("/resource"); + Func act = () => web.CreateClient().GetAsync("/resource"); - Exception thrownException = await Assert.ThrowsAsync(act); - var completionEvent = sink.Writes.First(logEvent => Matching.FromSource()(logEvent)); - Assert.Same(unhandledException, completionEvent.Exception); - Assert.Same(unhandledException, thrownException); - } + var thrownException = await Assert.ThrowsAsync(act); + var completionEvent = sink.Writes.First(logEvent => Matching.FromSource()(logEvent)); + Assert.Same(unhandledException, completionEvent.Exception); + Assert.Same(unhandledException, thrownException); + } - WebApplicationFactory Setup(ILogger logger, bool dispose, Action configureOptions = null, - Action actionCallback = null) - { - var web = _web.WithWebHostBuilder( - builder => builder - .ConfigureServices(sc => sc.Configure(options => + WebApplicationFactory Setup( + ILogger logger, + bool dispose, + Action? configureOptions = null, + Action? actionCallback = null) + { + var web = _web.WithWebHostBuilder( + builder => builder + .ConfigureServices(sc => sc.Configure(options => + { + options.Logger = logger; + options.EnrichDiagnosticContext += (diagnosticContext, _) => { - options.Logger = logger; - options.EnrichDiagnosticContext += (diagnosticContext, httpContext) => - { - diagnosticContext.Set("SomeString", "string"); - }; - })) - .Configure(app => + diagnosticContext.Set("SomeString", "string"); + }; + })) + .Configure(app => + { + app.UseSerilogRequestLogging(configureOptions); + app.Run(ctx => { - app.UseSerilogRequestLogging(configureOptions); - app.Run(ctx => - { - actionCallback?.Invoke(ctx); - return Task.CompletedTask; - }); // 200 OK - }) - .UseSerilog(logger, dispose)); - - return web; - } + actionCallback?.Invoke(ctx); + return Task.CompletedTask; + }); // 200 OK + }) + .UseSerilog(logger, dispose)); - (SerilogSink, WebApplicationFactory) Setup(Action configureOptions = null, - Action actionCallback = null) - { - var sink = new SerilogSink(); - var logger = new LoggerConfiguration() - .Enrich.FromLogContext() - .WriteTo.Sink(sink) - .CreateLogger(); + return web; + } - var web = Setup(logger, true, configureOptions, actionCallback); + (SerilogSink, WebApplicationFactory) Setup( + Action? configureOptions = null, + Action? actionCallback = null) + { + var sink = new SerilogSink(); + var logger = new LoggerConfiguration() + .Enrich.FromLogContext() + .WriteTo.Sink(sink) + .CreateLogger(); - return (sink, web); - } + var web = Setup(logger, true, configureOptions, actionCallback); + + return (sink, web); } -} +} \ No newline at end of file diff --git a/test/Serilog.AspNetCore.Tests/Support/DisposeTrackingLogger.cs b/test/Serilog.AspNetCore.Tests/Support/DisposeTrackingLogger.cs index 70d3ef0..a441089 100644 --- a/test/Serilog.AspNetCore.Tests/Support/DisposeTrackingLogger.cs +++ b/test/Serilog.AspNetCore.Tests/Support/DisposeTrackingLogger.cs @@ -1,354 +1,354 @@ -using System; -using System.Collections.Generic; -using Serilog.Core; +using Serilog.Core; using Serilog.Events; -namespace Serilog.AspNetCore.Tests.Support +namespace Serilog.AspNetCore.Tests.Support; + +public class DisposeTrackingLogger : ILogger, IDisposable { - public class DisposeTrackingLogger : ILogger, IDisposable - { - public bool IsDisposed { get; set; } - - public ILogger ForContext(ILogEventEnricher enricher) - { - return new LoggerConfiguration().CreateLogger(); - } - - public ILogger ForContext(IEnumerable enrichers) - { - return new LoggerConfiguration().CreateLogger(); - } - - public ILogger ForContext(string propertyName, object value, bool destructureObjects = false) - { - return new LoggerConfiguration().CreateLogger(); - } - - public ILogger ForContext() - { - return new LoggerConfiguration().CreateLogger(); - } - - public ILogger ForContext(Type source) - { - return new LoggerConfiguration().CreateLogger(); - } - - public void Write(LogEvent logEvent) - { - } - - public void Write(LogEventLevel level, string messageTemplate) - { - } - - public void Write(LogEventLevel level, string messageTemplate, T propertyValue) - { - } - - public void Write(LogEventLevel level, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Write(LogEventLevel level, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - } - - public void Write(LogEventLevel level, string messageTemplate, params object[] propertyValues) - { - } - - public void Write(LogEventLevel level, Exception exception, string messageTemplate) - { - } - - public void Write(LogEventLevel level, Exception exception, string messageTemplate, T propertyValue) - { - } - - public void Write(LogEventLevel level, Exception exception, string messageTemplate, T0 propertyValue0, - T1 propertyValue1) - { - } - - public void Write(LogEventLevel level, Exception exception, string messageTemplate, T0 propertyValue0, - T1 propertyValue1, T2 propertyValue2) - { - } - - public void Write(LogEventLevel level, Exception exception, string messageTemplate, params object[] propertyValues) - { - } - - public bool IsEnabled(LogEventLevel level) - { - return false; - } - - public void Verbose(string messageTemplate) - { - } - - public void Verbose(string messageTemplate, T propertyValue) - { - } - - public void Verbose(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Verbose(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - } - - public void Verbose(string messageTemplate, params object[] propertyValues) - { - } - - public void Verbose(Exception exception, string messageTemplate) - { - } - - public void Verbose(Exception exception, string messageTemplate, T propertyValue) - { - } - - public void Verbose(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Verbose(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - } - - public void Verbose(Exception exception, string messageTemplate, params object[] propertyValues) - { - } - - public void Debug(string messageTemplate) - { - } - - public void Debug(string messageTemplate, T propertyValue) - { - } - - public void Debug(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Debug(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - } - - public void Debug(string messageTemplate, params object[] propertyValues) - { - } - - public void Debug(Exception exception, string messageTemplate) - { - } - - public void Debug(Exception exception, string messageTemplate, T propertyValue) - { - } - - public void Debug(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Debug(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - } - - public void Debug(Exception exception, string messageTemplate, params object[] propertyValues) - { - } - - public void Information(string messageTemplate) - { - } - - public void Information(string messageTemplate, T propertyValue) - { - } - - public void Information(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Information(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - } - - public void Information(string messageTemplate, params object[] propertyValues) - { - } - - public void Information(Exception exception, string messageTemplate) - { - } - - public void Information(Exception exception, string messageTemplate, T propertyValue) - { - } - - public void Information(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Information(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - } - - public void Information(Exception exception, string messageTemplate, params object[] propertyValues) - { - } - - public void Warning(string messageTemplate) - { - } - - public void Warning(string messageTemplate, T propertyValue) - { - } - - public void Warning(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Warning(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - } - - public void Warning(string messageTemplate, params object[] propertyValues) - { - } - - public void Warning(Exception exception, string messageTemplate) - { - } - - public void Warning(Exception exception, string messageTemplate, T propertyValue) - { - } - - public void Warning(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Warning(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - } - - public void Warning(Exception exception, string messageTemplate, params object[] propertyValues) - { - } - - public void Error(string messageTemplate) - { - } - - public void Error(string messageTemplate, T propertyValue) - { - } - - public void Error(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Error(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - } - - public void Error(string messageTemplate, params object[] propertyValues) - { - } - - public void Error(Exception exception, string messageTemplate) - { - } - - public void Error(Exception exception, string messageTemplate, T propertyValue) - { - } - - public void Error(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Error(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - } - - public void Error(Exception exception, string messageTemplate, params object[] propertyValues) - { - } - - public void Fatal(string messageTemplate) - { - } - - public void Fatal(string messageTemplate, T propertyValue) - { - } - - public void Fatal(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Fatal(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - } - - public void Fatal(string messageTemplate, params object[] propertyValues) - { - } - - public void Fatal(Exception exception, string messageTemplate) - { - } - - public void Fatal(Exception exception, string messageTemplate, T propertyValue) - { - } - - public void Fatal(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - } - - public void Fatal(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - } - - public void Fatal(Exception exception, string messageTemplate, params object[] propertyValues) - { - } - - public bool BindMessageTemplate(string messageTemplate, object[] propertyValues, out MessageTemplate parsedTemplate, - out IEnumerable boundProperties) - { - parsedTemplate = null; - boundProperties = null; - return false; - } - - public bool BindProperty(string propertyName, object value, bool destructureObjects, out LogEventProperty property) - { - property = null; - return false; - } - - public void Dispose() - { - IsDisposed = true; - } - } -} + public bool IsDisposed { get; set; } + + public ILogger ForContext(ILogEventEnricher enricher) + { + return new LoggerConfiguration().CreateLogger(); + } + + public ILogger ForContext(IEnumerable enrichers) + { + return new LoggerConfiguration().CreateLogger(); + } + + public ILogger ForContext(string propertyName, object value, bool destructureObjects = false) + { + return new LoggerConfiguration().CreateLogger(); + } + + public ILogger ForContext() + { + return new LoggerConfiguration().CreateLogger(); + } + + public ILogger ForContext(Type source) + { + return new LoggerConfiguration().CreateLogger(); + } + + public void Write(LogEvent logEvent) + { + } + + public void Write(LogEventLevel level, string messageTemplate) + { + } + + public void Write(LogEventLevel level, string messageTemplate, T propertyValue) + { + } + + public void Write(LogEventLevel level, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Write(LogEventLevel level, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + } + + public void Write(LogEventLevel level, string messageTemplate, params object[] propertyValues) + { + } + + public void Write(LogEventLevel level, Exception exception, string messageTemplate) + { + } + + public void Write(LogEventLevel level, Exception exception, string messageTemplate, T propertyValue) + { + } + + public void Write(LogEventLevel level, Exception exception, string messageTemplate, T0 propertyValue0, + T1 propertyValue1) + { + } + + public void Write(LogEventLevel level, Exception exception, string messageTemplate, T0 propertyValue0, + T1 propertyValue1, T2 propertyValue2) + { + } + + public void Write(LogEventLevel level, Exception exception, string messageTemplate, params object[] propertyValues) + { + } + + public bool IsEnabled(LogEventLevel level) + { + return false; + } + + public void Verbose(string messageTemplate) + { + } + + public void Verbose(string messageTemplate, T propertyValue) + { + } + + public void Verbose(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Verbose(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + } + + public void Verbose(string messageTemplate, params object[] propertyValues) + { + } + + public void Verbose(Exception exception, string messageTemplate) + { + } + + public void Verbose(Exception exception, string messageTemplate, T propertyValue) + { + } + + public void Verbose(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Verbose(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + } + + public void Verbose(Exception exception, string messageTemplate, params object[] propertyValues) + { + } + + public void Debug(string messageTemplate) + { + } + + public void Debug(string messageTemplate, T propertyValue) + { + } + + public void Debug(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Debug(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + } + + public void Debug(string messageTemplate, params object[] propertyValues) + { + } + + public void Debug(Exception exception, string messageTemplate) + { + } + + public void Debug(Exception exception, string messageTemplate, T propertyValue) + { + } + + public void Debug(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Debug(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + } + + public void Debug(Exception exception, string messageTemplate, params object[] propertyValues) + { + } + + public void Information(string messageTemplate) + { + } + + public void Information(string messageTemplate, T propertyValue) + { + } + + public void Information(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Information(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + } + + public void Information(string messageTemplate, params object[] propertyValues) + { + } + + public void Information(Exception exception, string messageTemplate) + { + } + + public void Information(Exception exception, string messageTemplate, T propertyValue) + { + } + + public void Information(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Information(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + } + + public void Information(Exception exception, string messageTemplate, params object[] propertyValues) + { + } + + public void Warning(string messageTemplate) + { + } + + public void Warning(string messageTemplate, T propertyValue) + { + } + + public void Warning(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Warning(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + } + + public void Warning(string messageTemplate, params object[] propertyValues) + { + } + + public void Warning(Exception exception, string messageTemplate) + { + } + + public void Warning(Exception exception, string messageTemplate, T propertyValue) + { + } + + public void Warning(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Warning(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + } + + public void Warning(Exception exception, string messageTemplate, params object[] propertyValues) + { + } + + public void Error(string messageTemplate) + { + } + + public void Error(string messageTemplate, T propertyValue) + { + } + + public void Error(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Error(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + } + + public void Error(string messageTemplate, params object[] propertyValues) + { + } + + public void Error(Exception exception, string messageTemplate) + { + } + + public void Error(Exception exception, string messageTemplate, T propertyValue) + { + } + + public void Error(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Error(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + } + + public void Error(Exception exception, string messageTemplate, params object[] propertyValues) + { + } + + public void Fatal(string messageTemplate) + { + } + + public void Fatal(string messageTemplate, T propertyValue) + { + } + + public void Fatal(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Fatal(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + } + + public void Fatal(string messageTemplate, params object[] propertyValues) + { + } + + public void Fatal(Exception exception, string messageTemplate) + { + } + + public void Fatal(Exception exception, string messageTemplate, T propertyValue) + { + } + + public void Fatal(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + } + + public void Fatal(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + } + + public void Fatal(Exception exception, string messageTemplate, params object[] propertyValues) + { + } + + public bool BindMessageTemplate( + string messageTemplate, + object[] propertyValues, + out MessageTemplate? parsedTemplate, + out IEnumerable? boundProperties) + { + parsedTemplate = null; + boundProperties = null; + return false; + } + + public bool BindProperty(string propertyName, object value, bool destructureObjects, out LogEventProperty? property) + { + property = null; + return false; + } + + public void Dispose() + { + IsDisposed = true; + } +} \ No newline at end of file diff --git a/test/Serilog.AspNetCore.Tests/Support/Extensions.cs b/test/Serilog.AspNetCore.Tests/Support/Extensions.cs index 54e6a1f..78ac692 100644 --- a/test/Serilog.AspNetCore.Tests/Support/Extensions.cs +++ b/test/Serilog.AspNetCore.Tests/Support/Extensions.cs @@ -1,12 +1,11 @@ using Serilog.Events; -namespace Serilog.AspNetCore.Tests.Support +namespace Serilog.AspNetCore.Tests.Support; + +public static class Extensions { - public static class Extensions + public static object LiteralValue(this LogEventPropertyValue @this) { - public static object LiteralValue(this LogEventPropertyValue @this) - { - return ((ScalarValue)@this).Value; - } + return ((ScalarValue)@this).Value; } -} +} \ No newline at end of file diff --git a/test/Serilog.AspNetCore.Tests/Support/SerilogSink.cs b/test/Serilog.AspNetCore.Tests/Support/SerilogSink.cs index 5af3d36..62e5966 100644 --- a/test/Serilog.AspNetCore.Tests/Support/SerilogSink.cs +++ b/test/Serilog.AspNetCore.Tests/Support/SerilogSink.cs @@ -1,19 +1,17 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; using Serilog.Core; using Serilog.Events; -namespace Serilog.AspNetCore.Tests.Support +namespace Serilog.AspNetCore.Tests.Support; + +public class SerilogSink : ILogEventSink { - public class SerilogSink : ILogEventSink - { - public List Writes { get; set; } = new List(); + public List Writes { get; set; } = new(); - public void Emit(LogEvent logEvent) - { - Writes.Add(logEvent); - } + public void Emit(LogEvent logEvent) + { + Writes.Add(logEvent); } } \ No newline at end of file diff --git a/test/Serilog.AspNetCore.Tests/Support/SerilogWebApplicationFactory.cs b/test/Serilog.AspNetCore.Tests/Support/SerilogWebApplicationFactory.cs index ac6a881..f4fe961 100644 --- a/test/Serilog.AspNetCore.Tests/Support/SerilogWebApplicationFactory.cs +++ b/test/Serilog.AspNetCore.Tests/Support/SerilogWebApplicationFactory.cs @@ -1,14 +1,13 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; -namespace Serilog.AspNetCore.Tests.Support -{ - // ReSharper disable once ClassNeverInstantiated.Global - public class SerilogWebApplicationFactory : WebApplicationFactory - { - protected override IWebHostBuilder CreateWebHostBuilder() => new WebHostBuilder().UseStartup(); - protected override void ConfigureWebHost(IWebHostBuilder builder) => builder.UseContentRoot("."); - } +namespace Serilog.AspNetCore.Tests.Support; - public class TestStartup { } +// ReSharper disable once ClassNeverInstantiated.Global +public class SerilogWebApplicationFactory : WebApplicationFactory +{ + protected override IWebHostBuilder CreateWebHostBuilder() => new WebHostBuilder().UseStartup(); + protected override void ConfigureWebHost(IWebHostBuilder builder) => builder.UseContentRoot("."); } + +public class TestStartup { } \ No newline at end of file