Log4Net 在 .NET Framework 项目中依然常见,但一个关键信息需要明确:它已于 2023 年正式进入维护模式,不再开发新功能,并且不支持 .NET 6+ 的原生 Microsoft.Extensions.Logging 抽象。如果你正在启动新项目,直接使用 Microsoft.Extensions.Logging 搭配 Console 或 File 提供程序,是更稳妥也更现代的选择。不过,如果你必须对接遗留系统,或者手头已有成熟的 Log4Net 配置,那么掌握它的正确用法就至关重要了——尤其是那些容易踩坑的细节。
如何正确初始化 Log4Net(避免“Logger is null”)
Log4Net 不会自动扫描配置文件,你必须显式调用初始化方法,而且这一步只能执行一次。多次调用虽然不会报错,但会导致配置丢失或行为异常,得不偿失。
XmlConfigurator.Configure()只加载 App.config 或 Web.config 中的节点,前提是配置节已在configSections中注册。- 更推荐的做法是使用
XmlConfigurator.Configure(Assembly.GetCallingAssembly(), new FileInfo("log4net.config"))加载外部 XML 文件,这样配置更独立,也更容易维护和替换。 - 务必在调用
LogManager.GetLogger()之前完成初始化。典型的位置是Program.cs(.NET Framework)或Global.asax Application_Start。 - 在 ASP.NET Core 中无法直接使用这种方式,因为
LogManager依赖AppDomain,而 Core 采用的是HostBuilder模型。
为什么 GetLogger(typeof(T)) 和 GetLogger("name") 行为不同
Log4Net 的 Logger 实例本质上是按名称查找的。GetLogger(typeof(T)) 等价于 GetLogger(typeof(T).FullName),而 GetLogger("MyApp.Service") 则创建了一个独立命名空间下的 Logger。两者在日志级别和 Appender 绑定上是完全独立的——即使你在配置中写了 ,也不会匹配 typeof(MyApp.Service.UserService) 生成的 Logger 名称。
层级匹配依赖于点号(.)分隔。例如,MyApp.Service 是 MyApp 的子 Logger,可以继承父级的 Level 和 Appender,但需要通过 additivity="false" 来控制是否叠加。调试时,可以使用 LogManager.GetCurrentLoggers() 来查看当前所有已创建的 Logger 名称,避免拼写错误或层级误解。
FileAppender 写入失败的常见原因
日志文件打不开、没内容、权限报错——这些问题 90% 都出在 FileAppender 的配置环节上。
File路径如果写的是相对路径(比如"logs/app.log"),实际路径会基于AppDomain.CurrentDomain.BaseDirectory,而不是 exe 所在目录。尤其是在 IIS 部署时,BaseDirectory 是 w3wp.exe 的路径,很容易搞错。- 如果没有设置
AppendToFile="true",每次启动都会覆盖日志。另外,若未设置LockingModel type="log4net.Appender.FileAppender+MinimalLock",多进程或多 AppDomain 下写入会直接抛出IOException。 - 在 Windows 环境下,如果路径包含中文或空格,记得用双引号包裹 XML 中的路径,同时确保应用有对应目录的写入权限(IIS 应用池标识用户需要授权)。
- 建议改用
RollingFileAppender,配合MaximumFileSize和MaxSizeRollBackups,避免单个文件过大或磁盘被占满。
与 ASP.NET Core 共存的现实路径
不能直接把 Log4Net 当作 .NET Core 的日志提供程序注入,但可以通过桥接方式保留旧的日志逻辑。
- 使用
Microsoft.Extensions.Logging.Log4NetProviderNuGet 包(非官方,比如Microsoft.Extensions.Logging.Log4Net.AspNetCore),它会把ILogger的调用转译为 Log4Net 的ILog调用。 - 这个 Provider 只负责转发日志事件,并不接管配置——你仍然需要手动调用
XmlConfigurator.Configure()来初始化 Log4Net。 - 注意:Log4Net 的
PatternLayout格式符(如%date %level %logger - %message)不会自动解析Microsoft.Extensions.Logging的结构化日志参数。比如logger.LogInformation("User {Id} logged in", userId)中的{Id}会被原样输出为字符串,而不是被替换为实际值。 - 如果你需要结构化日志,建议放弃 Log4Net,改用
Serilog或NLog,它们原生支持Microsoft.Extensions.Logging的 message template 解析。
Log4Net 的核心陷阱不在语法,而在于生命周期管理——初始化遗漏、多线程争抢配置、路径权限误判,以及对 .NET Core 运行模型的误用。一旦在生产环境出现日志静默,优先检查 LogManager.GetCurrentLoggers() 返回是否为空,再确认 XmlConfigurator 是否真的被执行过,而不是翻来覆去地排查配置文件格式。