这个问题让我发疯。
我在将 Serilog 输出到文件时没有遇到问题。当端点映射到静态类以及我的 Mediatr 处理程序中时,我遇到了 Serilog 丰富的问题。
这是 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);
});
以下是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());
};
});
这是我的终点:
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...
}
问题是,当我登录端点时...丰富器提供的上下文信息不会输出到日志中。
在实验时,我发现了一个非常奇怪的情况,如果我添加以下内容:
logger.Information("The Reports Excel endpoint {@Username}", contextAccess.httpContext?.User?.Identity?.Name);
通常由丰富器提供的用户名属性现在添加到日志条目以及日志输出的文本中。更奇怪的是,如果我做了以下事情:
logger.Information("The Reports Excel endpoint {@Username}", contextAccess.httpContext?.User?.Identity?.Name);
logger.Information("The Reports Excel endpoint {@HttpRequestClientHostIP}", "1333.1333.1333.1333");
会输出 HttpRequestClientHostIP,但不会输出用户名。
有人可以阐明他们认为可能发生的情况吗?难道我做错了什么?这种行为正常吗?我只是真正正确地理解了某些事情?
编辑:
我通过将代码移至中间件解决了 Username 属性的问题,如下所示:
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);
}
}
}
我相信这段代码是基于 Nicholas Blumhardt 不久前的一篇博客文章。我仍然很困惑为什么我在 UseSerilogRequestLogging 中定义的一些其他属性没有保留在我的 Mediatr 处理程序和端点定义中生成的日志中。
我几乎解决了我的问题。然而,虽然我得到了我想要的结果,但我不确定它在技术上是否正确。
我做的第一件事是添加另一个中间件:
/// <summary>
/// Class HttpMiddlewareExtensions.
/// </summary>
public static class HttpMiddlewareExtensions {
/// <summary>
/// Uses the serilog request information.
/// </summary>
/// <param name="app">The application.</param>
/// <returns>IApplicationBuilder.</returns>
public static IApplicationBuilder UseSerilogRequestInformation(this IApplicationBuilder app) {
return app.UseMiddleware<SerilogRequestInformationMiddleware>();
}
}
/// <summary>
/// Class SerilogRequestInformationMiddleware.
/// </summary>
public class SerilogRequestInformationMiddleware {
/// <summary>
/// The next
/// </summary>
private readonly RequestDelegate _next;
/// <summary>
/// Initializes a new instance of the <see cref="SerilogRequestInformationMiddleware"/> class.
/// </summary>
/// <param name="next">The next.</param>
public SerilogRequestInformationMiddleware(RequestDelegate next) {
Ensure.That(next, nameof(next)).IsNotNull();
_next = next;
}
/// <summary>
/// Invokes the specified context.
/// </summary>
/// <param name="context">The context.</param>
public async Task Invoke(HttpContext context) {
Ensure.That(context, nameof(context)).IsNotNull();
var request = context.Request;
using (LogContext.PushProperty("EndpointName", context.ContextHasEndpoint() ? context.GetEndpoint()?.DisplayName : string.Empty))
using (LogContext.PushProperty("QueryString", request.QueryString.HasValue ? request.QueryString : string.Empty))
using (LogContext.PushProperty("HttpRequestClientHostIP", context.GetHttpRequestClientHostIp()))
using (LogContext.PushProperty("HttpRequestUserAgent", request.GetUserAgent()))
using (LogContext.PushProperty("StatusCode", context.GetHttpStatusCode()))
using (LogContext.PushProperty("RequestMethod", request.Method))
using (LogContext.PushProperty("RequestScheme", request.Scheme)) {
await _next(context);
}
}
}
我还添加了一个辅助类:
public static class LogHelper {
public static void EnrichFromRequest(IDiagnosticContext diagnosticContext, HttpContext httpContext) {
var request = httpContext.Request;
diagnosticContext.Set("Username", httpContext.GetUsername());
diagnosticContext.Set("Scheme", request.Scheme);
diagnosticContext.Set("EndpointNAme", httpContext.ContextHasEndpoint() ? httpContext.GetEndpoint()?.DisplayName : string.Empty);
diagnosticContext.Set("QueryString", request.QueryString.HasValue ? request.QueryString : string.Empty);
diagnosticContext.Set("HttpRequestClientHostIP", httpContext.GetHttpRequestClientHostIp());
diagnosticContext.Set("HttpRequestUserAgent", request.GetUserAgent());
diagnosticContext.Set("StatusCode", httpContext.GetHttpStatusCode());
diagnosticContext.Set("RequestMethod", request.Method);
diagnosticContext.Set("RequestScheme", request.Scheme);
}
}
感谢 Andrew Locke:在 ASP.NET Core 3.0 中使用 Serilog.AspNetCore
我将 Program.cs 更新为以下内容:
var app = AppBuilder.GetApp(args);
app.UseSerilogRequestLogging(options => options.EnrichDiagnosticContext = LogHelper.EnrichFromRequest);
if (!app.Environment.IsDevelopment()) {
app.UseExceptionHandler();
}
app.ConfigureExceptionHandler(Log.Logger);
app.UseStaticFiles();
app.UseSerilogRequestInformation();
app.UseAuthentication();
app.UseAuthorization();
app.UseSerilogUsername();
app.UseAdminSafelist(app.Configuration.GetSection("AdminSafeList").Value!);
app.MapExcelReports();
app.MapPdfReports();
Log.Logger.Information("Getting ready to run!");
app.Run();
但是,等等!还有更多!
我需要更新一些代码,例如端点授权的需求处理程序,以使用 LogContext.PushProperty 填充 Username 属性。
完成这些更改后,日志中的所有属性都会根据需要填充。我确信这些属性未填充的原因与请求管道以及处理程序是否被视为该上下文的一部分有关。 LogContext 可能会添加 Serilog 在请求之外跟踪的值。需求处理程序之类的值之所以为空,是因为 HttpContext 在其中不可用。我必须通过其他方式提供用户名的值,具体取决于上下文。
我希望其中一些对某人有所帮助,并且我愿意接受任何建议。