构造函数注入 ILogger 是唯一推荐方式,禁用 new LoggerFactory 和静态 logger;结构化日志需用 {FieldName} 占位符传参;容器中必须输出到 stdout;配置需匹配泛型类型全名且注意 Serilog 覆盖问题。

别自己 new ILoggerFactory,也别在静态上下文里硬造 logger 实例——这会丢请求 ID、绕过配置、无法按服务名统一调级。
构造函数注入 ILogger 是唯一推荐方式
只要类注册进了 DI 容器(比如 services.AddScoped),就能在构造函数里直接接 ILogger。Web API 控制器、后台服务、甚至中间件里都适用。
- 错误做法:
new LoggerFactory().CreateLogger→ 丢失作用域(如() RequestId)、不读appsettings.json中的日志级别配置 - 正确做法:声明
private readonly ILogger,在构造函数中接收并赋值_logger; - 非 DI 场景(如
Program.cs顶层语句)必须用app.Services.GetRequiredService,而不是自己 new>()
LogInformation("xxx", arg) 不是字符串拼接
占位符 {UserId} 是结构化日志的字段名,不是模板语法糖。传参顺序和占位符一一对应,引擎会把值单独存为 JSON 字段,支持 Kibana/Seq 按字段过滤。
- 危险写法:
logger.LogInformation("User " + userId + " failed login")→ 日志变成纯文本,userId无法被提取 - 正确写法:
logger.LogInformation("Login failed for user {UserId}", userId) - 多个参数就挨个列:
logger.LogWarning("Order {OrderId} failed after {RetryCount} retries", orderId, retryCount) - 禁止在占位符里写表达式:
{user.Name}不会求值,应拆成{UserName}并传user.Name
Linux 容器里日志“消失”大概率是输出目标错了
容器环境不认文件路径,也不保留重启前的文件内容。默认写 .AddFile() 在 Docker 里基本等于丢日志。
- 生产容器必须用
builder.Logging.AddConsole(),让日志吐到stdout,由fluentd或loki采集 - 硬编码路径如
C:\logs\或/var/log/myapp/在跨平台时必然失败;要用Path.Combine(Environment.GetFolderPath(...), "logs") - 写文件前务必
Directory.CreateDirectory(),Linux 容器里目录常不存在且无自动创建逻辑 - 捕获
UnauthorizedAccessException并 fallback 到Console.WriteLine,比静默丢日志强
日志级别没生效?检查配置加载顺序和作用域
appsettings.json 里的日志配置只对通过 DI 获取的 ILogger 生效,且层级匹配才起作用。根配置 "Logging:LogLevel:Default" 管不住具体服务名下的细化设置。
- 配置示例:
"Logging": { "LogLevel": { "Default": "Information", "MyApp.Services.UserService": "Debug" } } - 服务名必须和
ILogger的泛型类型完全一致(包括命名空间) - 如果用了
builder.Host.UseSerilog(),原生Microsoft.Extensions.Logging配置会被覆盖,得用 Serilog 的WriteTo和MinimumLevel重配 - 开发时可临时加
builder.Logging.SetMinimumLevel(LogLevel.Debug)快速验证是否是配置问题
结构化字段名大小写敏感、占位符不支持嵌套表达式、容器里不写文件——这些点不注意,日志看着有,查起来全靠猜。










