C#日志通过ILogger+DI+提供商实现,核心是级别过滤与结构化写入;不能new ILogger因它是接口,须DI注入;日志输出需满足配置级别阈值;结构化日志用占位符保留参数结构;文件日志需第三方提供者且注意路径权限与缓冲策略。

直接说结论:C# 日志记录不靠“自己造轮子”,而是用 ILogger<t></t> + 依赖注入 + 提供商(如 ConsoleLoggerProvider、FileLoggerProvider 或第三方 Serilog)组合实现;所谓“底层原理”核心就两点——日志级别过滤机制和日志消息的结构化写入管道。
为什么不能直接 new ILogger?
ILogger<t></t> 是一个接口,没有具体实现。.NET 内置的日志系统是基于抽象设计的,必须通过 IServiceCollection 注册后由 DI 容器提供实例。
- 手动
new ConsoleLogger<myclass>()</myclass>看似可行,但会绕过日志配置(如最小级别、作用域、过滤规则),且无法参与作用域生命周期管理 - 在 ASP.NET Core 中,
Program.cs的builder.Services.AddLogging()才真正启用日志管道,它默认注册了ConsoleLoggerProvider和基础过滤器 - 自定义日志类若想被统一管理,必须实现
ILoggerProvider接口并注册,否则 DI 不识别
如何控制日志输出级别和过滤?
日志是否写出,取决于两个地方的联合判断:全局最小级别(LogLevel)和每条日志的 LogLevel 值。不是“写了就一定出”。
- 默认情况下,ASP.NET Core 的
appsettings.json中"Logging": { "LogLevel": { "Default": "Information" } }表示只有Information及以上(Warning、Error、Critical)才会输出 - 代码中调用
logger.LogWarning("msg")时,框架会先比对当前作用域的LogLevel是否 >= 配置的最小值,不满足则直接跳过后续格式化与写入 - 可为特定命名空间或类型单独设级别,例如:
"Microsoft.Hosting.Lifetime": "Warning",这比全局设Debug更安全,避免海量启动日志刷屏
结构化日志(Structured Logging)是怎么回事?
传统字符串拼接日志(如 "User " + id + " logged in at " + DateTime.Now)难解析、难查询;结构化日志把参数作为键值对保留,底层靠 LogValues 和 ILogValuesFormatter 实现。
- 推荐写法:
logger.LogInformation("User {UserId} logged in at {LoginTime}", userId, DateTime.UtcNow)—— 大括号内是占位符名,不是变量名,但必须与参数顺序/数量一致 - 如果用了 Serilog,这些占位符会变成 JSON 字段;用 Microsoft.Extensions.Logging.Console 默认输出时,也会显示为
User 123 logged in at 2024-05-20T14:22:01.123Z,但底层仍保有结构信息 - 错误写法:
logger.LogInformation($"User {userId} ...")—— 这是字符串插值,日志系统收不到原始参数,丢失结构,也丧失延迟格式化能力(比如日志被禁用时,插值表达式仍会执行)
文件日志为什么没写出来?常见卡点在哪?
原生 Microsoft.Extensions.Logging 不带文件写入器,必须引入第三方(如 Microsoft.Extensions.Logging.File 已废弃)或 Serilog + Serilog.Sinks.File。
- 用 Serilog 时,
.WriteTo.File("logs/myapp.txt")必须在Host.CreateDefaultBuilder()之后、Build()之前调用,否则日志管道已冻结 - 路径权限问题:Windows 下若写入
C:\logs\,而应用没管理员权限,会静默失败(无异常抛出),建议用Path.Combine(AppContext.BaseDirectory, "logs") - 异步写入缓冲:Serilog 默认启用
buffered: true,程序崩溃时可能丢最后几条日志;生产环境可设buffered: false或加.CreateLogger().CloseAndFlush()在退出前强制刷盘
真正容易被忽略的是日志作用域(BeginScope)和上下文透传——比如 HTTP 请求 ID、用户 ID 怎么自动附加到每条日志里,这需要中间件配合 ILogger.BeginScope() 和 LogValues 的嵌套处理,不是加一句 logger.LogInformation 就能解决的。


















