This issue is driving me bonkers.
I'm not having an issue getting Serilog to output to a file. I am having issues with Serilog enrichment when the endpoint is mapped within a static class and also in my Mediatr handlers.
This is the configuration for the WebApplicationBuilder:
builder.Host
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureAutofac()
.UseSerilog((_, serviceProvider, configuration) => {
var applicationConfiguration = serviceProvider.GetService<IOptions<MyConfigurationClass>>();
var emailSettings = serviceProvider.GetService<IOptions<EmailSettingsOptions>>();
var usernameEnricher = serviceProvider.GetAutofacRoot().Resolve(typeof(UserNameEnricher)) as ILogEventEnricher;
configuration
.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.Enrich.FromLogContext()
.Enrich.WithCorrelationId()
.Enrich.WithClientIp()
.Enrich.WithMachineName()
.Enrich.WithEnvironmentUserName()
.Enrich.WithEnvironmentName()
.Enrich.WithExceptionDetails()
.Enrich.WithExceptionData()
.Enrich.WithEventType()
.Enrich.With(usernameEnricher!)
.WriteTo.Logger(logger => {
logger.Filter.ByIncludingOnly(logEvent => logEvent.Level == LogEventLevel.Error).WriteTo.Email(
new EmailConnectionInfo() {
EmailSubject = applicationConfiguration?.Value.EmailSubjectLine,
FromEmail = applicationConfiguration?.Value.WebmasterEmail.EmailAddress,
MailServer = emailSettings?.Value.Host,
Port = Convert.ToInt32(emailSettings?.Value.Port),
ToEmail = string.Join(",", (applicationConfiguration?.Value.DeveloperEmails ?? Array.Empty<DeveloperEmailOptions>()).Select(x => x.EmailAddress))
});
})
.Destructure.UsingAttributes()
.WriteTo.File(path: applicationConfiguration?.Value.LogPath.Path ?? string.Empty, outputTemplate: applicationConfiguration?.Value.MessageTemplate.TemplateString ?? string.Empty, shared: true, rollingInterval: RollingInterval.Day)
.WriteTo.Console(theme: AnsiConsoleTheme.Code);
});
Here is some of the configuration in the Program.cs:
app.UseSerilogRequestLogging(options => {
options.EnrichDiagnosticContext = (context, httpContext) => {
context.Set("Username", httpContext.User?.Identity?.Name);
context.Set("RequestScheme", httpContext.Request.Scheme);
context.Set("HttpRequestClientHostIP", httpContext.Connection.RemoteIpAddress);
context.Set("HttpRequestUserAgent", httpContext.Request.Headers["User-Agent"].ToString());
};
});
Here is my endpoint:
public static class MapExcelReportEndpointsExtensions {
/// <summary>
/// Maps the bae contract report endpoints.
/// </summary>
/// <param name="app">The application.</param>
public static void MapExcelReports(this WebApplication app) {
app.MapGet("/pdmsreportsapi", () => TypedResults.Ok()).RequireAuthorization(AuthorizationsConstants.AuthenticatedUser);
app.MapGet("/myApi/myReport/reportId/{reportId:guid}/excel", async ([FromServices] ILogger logger, [FromServices] IHttpContextAccessorWrapper contextAccessor, [FromServices] IMediator mediator, [FromServices] IOptions<MyConfigurationClass> configuration, [FromRoute] Guid reportId, [FromQuery(Name = "Variable1")] string variable1, [FromQuery(Name = "Variable2")] string variable2, [FromQuery(Name = "OrderByValue")] int orderByValue) => {
...code elided...
logger.Information("The Reports Excel endpoint");
...code elided...
}
The issue is that when I am logging in the endpoint...contextual information provided by the enrichers do not output to the logs.
While experimenting, I discovered a really bizarre case where if I added the following:
logger.Information("The Reports Excel endpoint {@Username}", contextAccess.httpContext?.User?.Identity?.Name);
The Username property that would normally be provided by the enricher now is added to the log entry as well as the text of the log output. Even more bizarre, if I did the following:
logger.Information("The Reports Excel endpoint {@Username}", contextAccess.httpContext?.User?.Identity?.Name);
logger.Information("The Reports Excel endpoint {@HttpRequestClientHostIP}", "1333.1333.1333.1333");
The HttpRequestClientHostIP would output, but the Username would not.
Can someone please illuminate what they think might be going on? Am I doing something wrong? Is this behavior normal and I'm just really understanding something correctly?
EDIT:
I resolved the issue with the Username property by moving the code to middleware like so:
public class SerilogUsernameMiddleware {
/// <summary>
/// The next
/// </summary>
private readonly RequestDelegate _next;
/// <summary>
/// Initializes a new instance of the <see cref="SerilogUsernameMiddleware"/> class.
/// </summary>
/// <param name="next">The next.</param>
public SerilogUsernameMiddleware(RequestDelegate next) {
Ensure.That(next, nameof(next)).IsNotNull();
_next = next;
}
/// <summary>
/// Invokes the specified HTTP context.
/// </summary>
/// <param name="httpContext">The HTTP context.</param>
public async Task Invoke(HttpContext httpContext) {
Ensure.That(httpContext, nameof(httpContext)).IsNotNull();
var username = httpContext.User?.Identity?.Name;
using (LogContext.PushProperty("Username", username)) {
await _next(httpContext);
}
}
}
I believe that this code was based on a blog post by Nicholas Blumhardt awhile back. I'm still perplexed as to why some of the other properties that I'm defining in the UseSerilogRequestLogging is not persisting to logs generated in my Mediatr handlers and the endpoint definition.