当前标准做法是使用 ILogger + Microsoft.Extensions.Logging:依赖注入获取泛型 ILogger 实例,避免手动 new;日志级别需合理选用(Debug 仅开发期、Error 必传 Exception);结构化日志用占位符而非字符串插值;文件日志推荐 Serilog 或 NLog,而非手写文件写入。

用 ILogger + Microsoft.Extensions.Logging 是当前标准做法
ASP.NET Core 项目默认内置,.NET 6+ 桌面应用也推荐直接用它,而不是手写文件写入或老式 Trace。核心是依赖注入 + 预置提供程序(如控制台、Debug、文件),不耦合具体输出方式。
关键点:
-
ILogger是泛型接口,T一般填当前类类型,用于日志分类和过滤 - 必须通过 DI 容器获取实例(
Program.cs中builder.Services.AddLogging()已默认注册) - 不要 new
Logger—— 手动构造会丢失作用域上下文、配置和性能优化
想写入文件?别自己拼 StreamWriter,用 FileLoggerProvider 或第三方包
.NET 原生不带文件日志提供程序,Microsoft.Extensions.Logging.Console 和 Debug 是自带的,但 File 需要额外引入。官方没推内置文件提供者,所以常见选择是:
- 用
Microsoft.Extensions.Logging.Configuration+NLog或Serilog(更推荐,配置灵活、支持滚动、结构化) - 轻量场景可用社区维护的
Microsoft.Extensions.Logging.File(注意 NuGet 包名是Microsoft.Extensions.Logging.File,不是官方第一方) - 自己封装
ILoggerProvider成本高,且容易在并发写入、日志轮转、编码错误上翻车
示例(Serilog):Log.Logger = new LoggerConfiguration().WriteTo.File("logs/log-.txt", rollingInterval: RollingInterval.Day).Create();
然后在 Program.cs 中调用 builder.Host.UseSerilog()
LogInformation / LogWarning / LogError 别乱用级别
日志级别不是“越详细越好”,它直接影响性能和可观测性:
-
LogDebug:仅开发期启用,发布后默认被过滤掉(除非显式配置最低级别为Debug) -
LogInformation:记录常规流程,比如“用户登录成功”,但别塞敏感字段(如密码明文、token) -
LogWarning:异常未抛出但可能有问题,比如重试三次后降级处理 -
LogError:必须伴随Exception对象传入(logger.LogError(ex, "msg")),否则堆栈丢失
错误示范:logger.LogError("Failed to connect: " + ex.Message) → 堆栈没了,无法定位根本原因
结构化日志里别用字符串拼接,用占位符
logger.LogInformation("User {UserId} logged in at {Time}", userId, DateTime.Now) 这样写,才能被 Serilog/NLog 正确提取字段、做筛选和聚合。
反模式:logger.LogInformation($"User {userId} logged in at {DateTime.Now}") → 日志变成纯文本,所有结构化能力失效。
占位符名不强制匹配变量名,但建议一致;大括号内不能有空格或表达式(如 {userId.ToString()} 不合法)
复杂对象可直接传入,Serilog 会自动序列化(前提是启用了相关配置),但避免传入含循环引用或敏感属性的对象










