使用Serilog在日志中添加Identifier属性及代码有效性验证
问题解答
你的代码无法实现预期需求,原因有三点:
LogContext.PushProperty返回的是IDisposable对象,你没有用using块包裹,会导致属性泄露到后续的日志请求中,无法正确控制属性的作用域。- 默认情况下,Serilog会把
LogContext中添加的属性放到日志的Properties嵌套对象里,而不是你期望的跟Timestamp同级的顶级字段。 - 你只在
CustomizeProblemDetails回调里添加了属性,只有这个回调范围内输出的日志才会带上Identifier,其他位置的日志不会包含该属性。
正确实现方案
要达到你想要的日志格式,需要三步操作:
1. 启用Serilog的LogContext增强
配置Serilog时必须添加.Enrich.FromLogContext(),否则LogContext的属性不会被记录到日志中:
Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() // 启用LogContext属性增强 // 其他配置(如日志输出目标、级别等) .CreateLogger();
2. 正确使用LogContext.PushProperty
用using块包裹属性推送,确保属性仅在指定作用域内生效,避免泄露:
builder.Services.AddProblemDetails(options => options.CustomizeProblemDetails = (context) => { var mathErrorFeature = context.HttpContext.Features.Get<MathErrorFeature>(); if (mathErrorFeature is not null) { // 原有问题详情设置逻辑... var identifier = $"Iden:{Guid.NewGuid()}"; // 用using包裹,限定属性作用域 using (LogContext.PushProperty("Identifier", identifier)) { // 这里输出的日志会带上Identifier属性 Log.Information("处理数学错误类型: {ErrorType}", mathErrorFeature.MathError); } } } );
3. 配置日志输出模板,将Identifier设为顶级字段
默认的JSON格式化器会把LogContext属性放进Properties,所以需要自定义输出结构,让Identifier成为顶级字段。可以用Serilog.Expressions包来实现(先通过NuGet安装该包):
Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() .WriteTo.File( new ExpressionTemplate("{ { Timestamp: @t, Identifier: Identifier, Level: @l, MessageTemplate: @mt, Properties: @p } }\n"), "logs/log.json" ) .CreateLogger();
可选:全局请求级别的Identifier
如果希望每个HTTP请求都带上唯一的Identifier,可以通过中间件实现,这样整个请求生命周期内的所有日志都会包含该属性:
// 在中间件管道中添加(需放在日志相关中间件之后) app.Use(async (httpContext, next) => { var requestId = $"Iden:{Guid.NewGuid()}"; using (LogContext.PushProperty("Identifier", requestId)) { await next(); } });
内容的提问来源于stack exchange,提问作者Yeasin Abedin
相关产品推荐
相关产品推荐

