Log4Net、NLog、Serilog 三种日志对比及WPF中使用
Log4Net 老而稳、XML配置、弱结构化;
NLog 全能中庸、配置灵活、强扩展;
Serialog 现代原生结构化、代码优先、适合云原生/ELK。
一、基本信息
1、Log4Net:Java log4j的.Net移植,经典老牌、稳定、生态大,.Net Framework时代事实标准。
2、NLog:.Net 原生开发,功能全面、配置灵活、高性能,兼顾传统与现代,企业级常用。
3、Serilog:现代结构化日志代表,原生JSON,代码优先配置,与 .NET Core/DI/ELK 生态无缝集成。
二、核心差异
1、结构化日志
(1)Log4Net:原生不支持,只能用字符串拼接,需手动扩展才能勉强结构化;日志是纯文本。
(2)NLog:支持结构化,需配置开启,输出可含字段;比Log4Net强,但不如Serilog。
(3)Serilog:原生强结构化,模板参数自动提取为独立字段,直接输出Json,机器可直接查询。
2、配置方式
(1)Log4Net:XML为主,配置集中但繁琐、易储出错、无编译检查;支持运行时重载。
(2)NLog:XML/JSON/代码 三选一,灵活;规则引擎强,支持条件路由、环境变量、动态重载。
(3)Serilog:代码优先,简洁强类型、编译检查;也支持JSON配置,但不常用。
3、性能(高并发场景)
(1)Log4Net:同步写入为主,全局锁,高并发易瓶颈;异步需第三方包装,性能一般。
(2)NLog:内置异步队列、细粒度锁,吞吐高;正确配置下性能接近Serilog。
(3)Serilog:无共享状态、异步批量写入,结构化场景性能最优;但JSON序列化有开销。
4、输出目标
(1)Log4Net:Appender(日志追加器)多,但偏传统(文件、控制台、数据库),现代生态支持弱。
(2)NLog:200+ Targets(目标),几乎覆盖所有(文件、数据库、队列、云等),企业级最全面。
(3)Serilog:Sinks(下沉点)丰富,主打现代栈(Elasticsearch、Seq、File),云原生友好。
5、.Net生态集成
(1)Log4Net:.Net Framework 友好;.Net Core 适配。
(2)NLog:全平台兼容,DI(依赖注入)支持好,容器化友好。
(3)Serilog:.Net Core/DI 原生集成,云原生首选。
三、优缺点速览
1、Log4Net
优点:极度稳定、成熟、社区大、问题易查;XML集中配置;传统项目维护友好。
缺点:无原生结构化;同步性能差;扩展能力弱。
适用:存量工控项目维持不动;工厂现场纯本地文本日志、不需要日志收集、不需要检索分析。
2、NLog
优点:全能中庸、配置灵活、高性能、扩展极强;支持动态重载、多目标、结构化;全平台兼容。
缺点:结构化不如Serilog;XML配置复杂。
适用:新建上位机项目;政企内网企业项目;既要本地日志稳定,又要偶尔简单结构化、多环境日志隔离。
3、Serilog
优点:原生结构化、代码配置简洁、现代生态完美集成、云原生首选。
缺点:JSON序列化有开销;传统XMl配置支持弱;不如NLog开箱即用。
适用:企业级、微服务、云服务、需要日志集中收集分析项目;需要日志检索、链路追踪、异常数据分析的项目。
四、WPF项目中使用配置
1、Log4Net
(1)NuGet 安装包:log4net
(2)新增配置文件:log4net.config
<log4net>
<!--根配置-->
<root>
<!--日志级别:可选值: ERROR > WARN > INFO > DEBUG -->
<level value="ERROR"/>
<level value="WARN"/>
<level value="INFO"/>
<level value="DEBUG"/>
<appender-ref ref="ErrorLog" />
<appender-ref ref="WarnLog" />
<appender-ref ref="InfoLog" />
<appender-ref ref="DebugLog" />
</root>
<!-- 错误 Error.log-->
<appender name="ErrorLog" type="log4net.Appender.RollingFileAppender">
<!--目录路径,可以是相对路径:log 或绝对路径: C:\WindowsFormsPro_log -->
<param name="File" value="Logs"/>
<!--文件名,按日期生成文件夹 "/yyyy-MM-dd/"Error.log"" -->
<param name="DatePattern" value="/yyyy-MM-dd/"Error.log""/>
<!--追加到文件-->
<appendToFile value="true"/>
<!--防止多线程时不能写Log,官方说线程非安全-->
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<!--创建日志文件的方式,可选值:Date[日期],文件大小[Size],混合[Composite]-->
<rollingStyle value="Composite"/>
<!--写到一个文件-->
<staticLogFileName value="false"/>
<!--单个文件大小。单位:KB|MB|GB-->
<maximumFileSize value="10MB"/>
<!--最多保留的文件数,设为"-1"则不限-->
<maxSizeRollBackups value="3"/>
<!--日志格式-->
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message"/>
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="ERROR" />
<param name="LevelMax" value="ERROR" />
</filter>
</appender>
<!-- 警告 Warn.log-->
<appender name="WarnLog" type="log4net.Appender.RollingFileAppender">
<!--目录路径,可以是相对路径或绝对路径-->
<param name="File" value="Logs"/>
<!--文件名,按日期生成文件夹-->
<param name="DatePattern" value="/yyyy-MM-dd/"Warn.log""/>
<!--追加到文件-->
<appendToFile value="true"/>
<!--防止多线程时不能写Log,官方说线程非安全-->
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<!--创建日志文件的方式,可选值:Date[日期],文件大小[Size],混合[Composite]-->
<rollingStyle value="Composite"/>
<!--写到一个文件-->
<staticLogFileName value="false"/>
<!--单个文件大小。单位:KB|MB|GB-->
<maximumFileSize value="10MB"/>
<!--最多保留的文件数,设为"-1"则不限-->
<maxSizeRollBackups value="3"/>
<!--日志格式-->
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message"/>
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="WARN" />
<param name="LevelMax" value="WARN" />
</filter>
</appender>
<!-- 信息 Info.log-->
<appender name="InfoLog" type="log4net.Appender.RollingFileAppender">
<!--目录路径,可以是相对路径或绝对路径-->
<param name="File" value="Logs"/>
<!--文件名,按日期生成文件夹-->
<param name="DatePattern" value="/yyyy-MM-dd/"Info.log""/>
<!--追加到文件-->
<appendToFile value="true"/>
<!--防止多线程时不能写Log,官方说线程非安全-->
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<!--创建日志文件的方式,可选值:Date[日期],文件大小[Size],混合[Composite]-->
<rollingStyle value="Composite"/>
<!--写到一个文件-->
<staticLogFileName value="false"/>
<!--单个文件大小。单位:KB|MB|GB-->
<maximumFileSize value="10MB"/>
<!--最多保留的文件数,设为"-1"则不限-->
<maxSizeRollBackups value="3"/>
<!--日志格式-->
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message"/>
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="INFO" />
<param name="LevelMax" value="INFO" />
</filter>
</appender>
<!-- 调试 Debug.log-->
<appender name="DebugLog" type="log4net.Appender.RollingFileAppender">
<!--目录路径,可以是相对路径或绝对路径-->
<param name="File" value="Logs"/>
<!--文件名,按日期生成文件夹-->
<param name="DatePattern" value="/yyyy-MM-dd/"Debug.log""/>
<!--追加到文件-->
<appendToFile value="true"/>
<!--防止多线程时不能写Log,官方说线程非安全-->
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<!--创建日志文件的方式,可选值:Date[日期],文件大小[Size],混合[Composite]-->
<rollingStyle value="Composite"/>
<!--写到一个文件-->
<staticLogFileName value="false"/>
<!--单个文件大小。单位:KB|MB|GB-->
<maximumFileSize value="10MB"/>
<!--最多保留的文件数,设为"-1"则不限-->
<maxSizeRollBackups value="3"/>
<!--日志格式-->
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message"/>
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="DEBUG" />
<param name="LevelMax" value="DEBUG" />
</filter>
</appender>
</log4net>
(3)全局加载配置文件
// 全局加载日志配置文件 Log4
XmlConfigurator.Configure(new FileInfo(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "XmlConfig\\log4net.config")));
(4)使用
public class MainWindowViewModel : BindableBase
{
private readonly ILogger _logger;
// 依赖注入
public MainWindowViewModel(ILogger logger)
{
_logger = logger;
// 日志记录
_logger.Debug("Debug日志信息");
_logger.Warn("Warn日志信息");
_logger.Info("Info日志信息");
_logger.Error("Error日志信息");
}
}
2、NLog
(1)NuGet 安装包:NLog
(2)新增配置文件:NLog.config
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd"
autoReload="true"
throwExceptions="false"
internalLogLevel="Off"
internalLogFile="nlog-internal.log">
<!-- optional, add some variables https://github.com/nlog/NLog/wiki/Configuration-file#variables -->
<variable name="myvar" value="myvalue"/>
<!--
See https://github.com/nlog/nlog/wiki/Configuration-file
for information on customizing logging rules and outputs.
-->
<targets async="true">
<!--
add your targets here
See https://github.com/nlog/NLog/wiki/Targets for possible targets.
See https://github.com/nlog/NLog/wiki/Layout-Renderers for the possible layout renderers.
-->
<!--写入数据库-->
<!--<target name="AllDatabase" xsi:type="Database"
dbProvider="System.Data.SqlClient.SqlConnection, System.Data.SqlClient"
connectionString="Data Source=DESKTOP-T2D6ILD;Initial Catalog=LogManager;Persist Security Info=True;User ID=sa;Password=sa123"
commandText="insert into dbo.NLog (Application, Logged, Level, Message,Logger, CallSite, Exception) values (@Application, @Logged, @Level, @Message,@Logger, @Callsite, @Exception);">
<parameter name="@application" layout="AspNetCoreNlog" />
<parameter name="@logged" layout="${date}" />
<parameter name="@level" layout="${level}" />
<parameter name="@message" layout="${message}" />
<parameter name="@logger" layout="${logger}" />
<parameter name="@callSite" layout="${callsite:filename=true}" />
<parameter name="@exception" layout="${exception:tostring}" />
</target>-->
<!--写入文件-->
<!--<target xsi:type="File" name="allfile"
fileName="NLog\nlog-all-${shortdate}.log"
layout="${longdate}|${logger}|${uppercase:${level}}|${message} ${exception}" />-->
<!--同样是将文件写入日志中,写入的内容有所差别,差别在layout属性中体现。
写入日志的数量有差别,差别在路由逻辑中体现-->
<!--<target xsi:type="File" name="ownFile-web"
fileName="NLog\nlog-my-${shortdate}.log"
layout="${longdate}|${logger}|${uppercase:${level}}|${message} ${exception}" />-->
<!--滚动日志文件上限数,滚动日志文件数达到上限新的文件内容会覆盖旧文件内容 -->
<!--archiveAboveSize每个日志文件大小的最大值(单位:字节),主日志文件超过大小超过该值时会将文件内容写入滚动日志并清空主日志文件内容-->
<!--${basedir}表示当前应用程序域所在的根目录-->
<!--keepFileOpen="false" 的作用
文件处理方式:
false:每次写入日志时打开文件,写入完成后立即关闭
true:在应用程序启动时就打开文件并保持打开状态,直到程序结束-->
<!--<target name="allfile" xsi:type="File"
fileName="${basedir}/adminlogs/all.log"
archiveFileName="${basedir}/adminlogs/all.{###}.txt"
archiveAboveSize="20000000"
maxArchiveFiles="30"
keepFileOpen="false"
layout="${longdate} | ${event-properties:item=EventId_Id} | ${uppercase:${level}} | ${logger} | ${aspnet-request-iP} | ${event-properties:item=user} | ${aspnet-request-url} | ${message} | ${event-properties:item=requestParam} | ${event-properties:item=jsonResult} | ${onexception:${exception:format=tostring}"/>-->
<!-- 补充:按级别分文件输出 -->
<!--${newline} 换行-->
<!--layout="${longdate}${newline}级别: ${level:uppercase=true}${newline}类名: ${logger}${newline}方法: ${callsite:className=true:methodName=true}${newline}消息: ${message}${newline}异常: ${exception:format=tostring}${newline}====================${newline}"/>-->
<target name="debugFile" xsi:type="File"
fileName="${basedir}/logs/debug-${shortdate}.log"
archiveAboveSize="20000000"
maxArchiveFiles="4"
keepFileOpen="false"
layout="${longdate} | ${level} | ${logger} | ${callsite:className=true:methodName=true} | ${message} ${exception}"/>
<target name="infoFile" xsi:type="File"
fileName="${basedir}/logs/info-${shortdate}.log"
archiveAboveSize="20000000"
maxArchiveFiles="4"
keepFileOpen="false"
layout="${longdate} | ${level} | ${logger} | ${callsite:className=true:methodName=true} | ${message} ${exception}"/>
<target name="warnFile" xsi:type="File"
fileName="${basedir}/logs/warn-${shortdate}.log"
archiveAboveSize="20000000"
maxArchiveFiles="4"
keepFileOpen="false"
layout="${longdate} | ${level} | ${logger} | ${callsite:className=true:methodName=true} | ${message} ${exception}"/>
<target name="errorFile" xsi:type="File"
fileName="${basedir}/logs/error-${shortdate}.log"
archiveAboveSize="20000000"
maxArchiveFiles="4"
keepFileOpen="false"
layout="${longdate} | ${level} | ${logger} | ${callsite:className=true:methodName=true} | ${message} ${exception}"/>
<!--写入控制台-->
<target name="console" xsi:type="ColoredConsole"
layout="${date:format=MM-dd HH\:mm\:ss} | ${uppercase:${level}} | ${logger} | ${aspnet-request-iP} | ${aspnet-request-url} | ${message}"/>
<!--写入黑洞-->
<target xsi:type="Null" name="blackhole" />
<!--
Write events to a file with the date in the filename.
<target xsi:type="File" name="f" fileName="${basedir}/logs/${shortdate}.log"
layout="${longdate} ${uppercase:${level}} ${message}" />
-->
</targets>
<rules>
<logger name="*" minlevel="Debug" maxlevel="Debug" writeTo="debugFile"/>
<logger name="*" minlevel="Info" maxlevel="Info" writeTo="infoFile"/>
<logger name="*" minlevel="Warn" maxlevel="Warn" writeTo="warnFile"/>
<logger name="*" minlevel="Error" writeTo="errorFile"/>
<!--<logger name="*" minlevel="Trace" writeTo="AllDatabase" />-->
<!-- add your logging rules here -->
<!--路由顺序会对日志打印产生影响。路由匹配逻辑为顺序匹配。-->
<!--All logs, including from Microsoft-->
<!--<logger name="*" minlevel="Trace" writeTo="allfile" />-->
<!--Skip Microsoft logs and so log only own logs-->
<!--以Microsoft打头的日志将进入此路由,由于此路由没有writeTo属性,所有会被忽略-->
<!--且此路由设置了final,所以当此路由被匹配到时。不会再匹配此路由下面的路由。未匹配到此路由时才会继续匹配下一个路由-->
<!--<logger name="Microsoft.*" minlevel="Trace" final="true" />-->
<!--上方已经过滤了所有Microsoft.*的日志,所以此处的日志只会打印除Microsoft.*外的日志-->
<!--<logger name="*" minlevel="Trace" writeTo="ownFile-web" />-->
<!--
Write all events with minimal level of Debug (So Debug, Info, Warn, Error and Fatal, but not Trace) to "f"
<logger name="*" minlevel="Debug" writeTo="f" />
-->
</rules>
</nlog>
(3)全局加载配置文件
// 配置NLog
LogManager.Setup().LoadConfigurationFromFile("XmlConfig\\NLog.config");
(4)使用
public class MainWindowViewModel : BindableBase
{
private readonly ILogger _logger;
// 依赖注入
public MainWindowViewModel(ILogger logger)
{
_logger = logger;
// 日志记录
_logger.Debug("Debug日志信息");
_logger.Warn("Warn日志信息");
_logger.Info("Info日志信息");
_logger.Error("Error日志信息");
}
}
3、Serilog
(1)NuGet 安装包:Serilog;Serilog.Sinks.File;Serilog.Sinks.Console。
(2)代码配置Serilog:
// 1. 配置 Serilog(全局静态)
private void ConfigureSerilog()
{
Log.Logger = new LoggerConfiguration()
// 最小日志级别 Debug < Information < Warning < Error < Fatal
.MinimumLevel.Information()
// 输出到控制台
.WriteTo.Console()
// 输出到文件,按天切割,保留30天
.WriteTo.File(
path: "Logs\\.log", // 日志目录,Logs日志文件夹
rollingInterval: RollingInterval.Day, // 按天切割
retainedFileCountLimit: 30, // 保留30天
fileSizeLimitBytes: 1024 * 1024 * 10 // 单个文件最大10M
)
.CreateLogger();
}
protected override void OnStartup(StartupEventArgs e)
{
ConfigureSerilog();
base.OnStartup(e);
}
(3)全局注册
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
// 全局注册 ILogger 单例
var logger = Log.Logger;
containerRegistry.RegisterInstance<ILogger>(logger);
}
(4)使用
public class MainWindowViewModel : BindableBase
{
private readonly ILogger _logger;
public MainWindowViewModel(ILogger logger)
{
_logger = logger;
_logger.Information("ViewModel 初始化");
_logger.Warning("ViewModel 初始化");
_logger.Error("ViewModel 初始化");
_logger.Debug("ViewModel 初始化");
}
}
欢迎关注微信公众号,第一时间获取更新:智造梦江湖行
本文对比了三款主流.NET日志组件:Log4Net、NLog和Serilog的核心特性。Log4Net作为经典老牌工具,XML配置稳定但缺乏结构化支持;NLog功能全面,配置灵活,适合企业级应用;Serilog则以原生结构化日志和代码优先配置见长,是云原生场景的首选。三者在结构化支持、配置方式、性能、输出目标和.NET生态集成等方面各有优劣:Log4Net适合传统本地日志需求,NLog适用于需要灵活性的企业项目,Serilog则专为现代云服务和日志分析场景设计。
浙公网安备 33010602011771号