使用Serilog日志时,无法向PostgreSQL自定义列写入用户名
问题分析与修复方案
核心问题点
- 中间件顺序错误:
UseSerilogRequestLogging需要在推送LogContext属性的中间件之后,否则无法捕获到user_name属性。 - 用户名判断逻辑bug:原代码中
context.User?.Identity?.IsAuthenticated != null || true的条件永远为true,会导致未认证时也尝试取Identity.Name(此时为null),逻辑完全错误。 - 未启用LogContext enrichment:Serilog默认不会从
LogContext提取属性,需要在配置中显式开启。 - 自定义列Writer取值逻辑错误:直接调用
value?.ToString()会返回带引号的序列化字符串(如"admin"),且解构FirstOrDefault的写法有误。
修复后的代码
1. 修正Serilog配置(添加LogContext enrichment)
Logger log = new LoggerConfiguration() .Enrich.FromLogContext() // 关键:启用从LogContext提取属性 .WriteTo.PostgreSQL(builder.Configuration.GetConnectionString("ConnectionString"), "Logs", needAutoCreateTable: true, columnOptions: new Dictionary<string, ColumnWriterBase> { {"message", new RenderedMessageColumnWriter(NpgsqlDbType.Text)}, {"message_template", new MessageTemplateColumnWriter(NpgsqlDbType.Text)}, {"level", new LevelColumnWriter(true , NpgsqlDbType.Varchar)}, {"time_stamp", new TimestampColumnWriter(NpgsqlDbType.Timestamp)}, {"exception", new ExceptionColumnWriter(NpgsqlDbType.Text)}, {"log_event", new LogEventSerializedColumnWriter(NpgsqlDbType.Json)}, {"user_name", new UsernameColumnWriter()} }) .WriteTo.Debug() .WriteTo.Console() .MinimumLevel.Information() .CreateLogger(); builder.Host.UseSerilog(log);
2. 修正中间件顺序与逻辑
app.UseHttpLogging(); app.UseAuthentication(); app.UseAuthorization(); // 先推送LogContext属性,再执行SerilogRequestLogging app.Use(async (context, next) => { var username = context.User?.Identity?.IsAuthenticated == true ? context.User.Identity.Name : null; using (LogContext.PushProperty("user_name", username)) // 使用using确保属性在请求结束后释放 { await next(); } }); app.UseSerilogRequestLogging(); // 移到自定义中间件之后
3. 修正UsernameColumnWriter取值逻辑
public class UsernameColumnWriter : ColumnWriterBase { public UsernameColumnWriter() : base(NpgsqlDbType.Varchar) { } public override object GetValue(LogEvent logEvent, IFormatProvider formatProvider = null) { // 直接从Properties字典取值,比FirstOrDefault更高效 if (logEvent.Properties.TryGetValue("user_name", out var value)) { // 处理字符串类型的属性值,去掉引号 if (value is ScalarValue scalar && scalar.Value is string username) { return username; } // 其他类型直接返回原始值的字符串形式(可选) return value?.ToString(null, formatProvider); } return null; } }
额外说明
- 使用
using (LogContext.PushProperty(...))可以确保在请求结束后自动清除属性,避免上下文污染。 - 直接从
logEvent.Properties字典取值比FirstOrDefault更高效,因为字典是键值对结构。 - 提取
ScalarValue的Value属性可以得到原始字符串,避免序列化后的引号问题。
内容的提问来源于stack exchange,提问作者caner0zkan
相关产品推荐
相关产品推荐

