当前位置: 首页 > article >正文

C#主流日志库深度对比:NLog、log4net与Serilog如何选择?

日志作为系统的"黑匣子",是故障排查与性能分析的基石。但在.NET生态中,NLog、log4net、Serilog三大主流日志库常让开发者陷入选择困难。本文通过多维实测,带你穿透技术迷雾!


一、核心特性对比:架构设计决定能力边界

日志库核心优势典型场景
NLogXML配置支持异步日志、多目标输出(文件/DB/网络)企业级复杂日志策略定制
log4netApache生态集成、跨语言兼容性强传统.NET Framework项目维护
Serilog结构化日志、高性能JSON输出、现代化API设计微服务架构、ELK日志分析场景

架构差异解析

  • Serilog采用结构化日志模型,直接生成JSON格式日志,省去文本解析开销
  • NLog通过异步队列实现高吞吐,支持200+输出目标扩展
  • log4net采用同步写入模型,在并发场景下易成性能瓶颈

二、性能实测:吞吐量相差2918倍的秘密

1. 基准测试(单线程1万条日志)

代码实现:

    using BenchmarkDotNet.Attributes;
    using log4net;
    using log4net.Appender;
    using log4net.Config;
    using log4net.Core;
    using log4net.Filter;
    using log4net.Layout;
    using NLog;
    using Serilog;

    [MemoryDiagnoser]
    [ThreadingDiagnoser]
    public class LogBenchmarks
    {
        private static readonly Serilog.ILogger serilogLogger = Log.Logger;
        private static readonly Logger nlogLogger = NLog.LogManager.GetCurrentClassLogger();
        private static readonly ILog log4netLogger = log4net.LogManager.GetLogger(typeof(LogBenchmarks));

        [Params(10000)]
        public int LogCount { get; set; }

        [GlobalSetup]
        public void Setup()
        {
            // Serilog配置(异步文件写入)
            Log.Logger = new LoggerConfiguration()
                .WriteTo.Async(a => a.File("serilog.log"), bufferSize: 1000)
                .CreateLogger();

            // NLog配置(异步文件写入)
            var nlogConfig = new NLog.Config.LoggingConfiguration();
            var asyncFileTarget = new NLog.Targets.FileTarget("asyncFile")
            { 
                FileName = "nlog.log",
                BufferSize = 1000,
            };
            nlogConfig.AddRuleForAllLevels(asyncFileTarget);
            NLog.LogManager.Configuration = nlogConfig;

            // log4net配置(同步文件写入)
            var logRepository = log4net.LogManager.GetRepository(System.Reflection.Assembly.GetEntryAssembly());
            RollingFileAppender appender = new RollingFileAppender
            {
                Name = "root",
                File = "log\\log_",
                AppendToFile = true,
                LockingModel = new FileAppender.MinimalLock(),
                RollingStyle = RollingFileAppender.RollingMode.Date,
                DatePattern = "yyyyMMdd-HH\".log\"",
                StaticLogFileName = false,
                MaxSizeRollBackups = 10,
                Layout = new PatternLayout("[%d{HH:mm:ss.fff}] %-5p %c T%t:%n%m%n")
            };
            appender.ClearFilters();
            appender.AddFilter(new LevelMatchFilter() { LevelToMatch = Level.Debug });
            BasicConfigurator.Configure(logRepository, appender);
            appender.ActivateOptions();
        }

        [Benchmark(Description = "Serilog Async")]
        public void SerilogTest()
        {
            for (int i = 0; i < LogCount; i++)
                serilogLogger.Information("Log entry {Number}", i);
        }

        [Benchmark(Description = "NLog Async")]
        public void NLogTest()
        {
            for (int i = 0; i < LogCount; i++)
                nlogLogger.Info("Log entry {0}", i);
        }

        [Benchmark(Description = "log4net Sync")]
        public void Log4NetTest()
        {
            for (int i = 0; i < LogCount; i++)
                log4netLogger.InfoFormat("Log entry {0}", i);
        }
    }

日志库/模式LogCount平均耗时(ms)内存分配(MB)标准差(ms)特殊现象
Serilog Asynchronous10,0002.5654.690.1720双峰分布警告
NLog Async10,00048.3381.683.85103个离群值移除
Log4net Sync10,0007,486.25550.651,371.2566无异步支持

2. 并发测试(100线程写入)

    [MemoryDiagnoser]
    [ThreadingDiagnoser]
    public class StructBenchmark
    {
        private static readonly Serilog.ILogger serilogLogger = Log.Logger;
        private static readonly NLog.Logger nlogLogger = NLog.LogManager.GetCurrentClassLogger();
        private static readonly ILog log4netLogger = log4net.LogManager.GetLogger(typeof(LogBenchmarks));

        [GlobalSetup]
        public void Setup()
        {
            // Serilog配置(异步文件写入)
            Log.Logger = new LoggerConfiguration()
                .WriteTo.File("serilog.log")
                .CreateLogger();

            // NLog配置(异步文件写入)
            var nlogConfig = new NLog.Config.LoggingConfiguration();
            var asyncFileTarget = new NLog.Targets.FileTarget("syncFile ")
            {
                FileName = "nlog.log",
                BufferSize = 1000,
            };
            nlogConfig.AddRuleForAllLevels(asyncFileTarget);
            NLog.LogManager.Configuration = nlogConfig;

            // log4net配置(同步文件写入)
            var logRepository = log4net.LogManager.GetRepository(System.Reflection.Assembly.GetEntryAssembly());
            RollingFileAppender appender = new RollingFileAppender
            {
                Name = "root",
                File = "log\\log_",
                AppendToFile = true,
                LockingModel = new FileAppender.MinimalLock(),
                RollingStyle = RollingFileAppender.RollingMode.Date,
                DatePattern = "yyyyMMdd-HH\".log\"",
                StaticLogFileName = false,
                MaxSizeRollBackups = 10,
                Layout = new PatternLayout("[%d{HH:mm:ss.fff}] %-5p %c T%t:%n%m%n")
            };
            appender.ClearFilters();
            appender.AddFilter(new LevelMatchFilter() { LevelToMatch = Level.Debug });
            BasicConfigurator.Configure(logRepository, appender);
            appender.ActivateOptions();
        }

        [Benchmark(Description = "Serilog Structured Sync")]
        public void SerilogStructured()
        {
            var user = new { Name = "Alice", Age = 30 };
            serilogLogger.Information("User {Name} logged in", user.Name);
        }

        [Benchmark(Description = "NLog Text Sync")]
        public void NLogText()
        {
            var user = new { Name = "Alice", Age = 30 };
            nlogLogger.Info($"User {user.Name} logged in");
        }
        [Benchmark(Description = "log4net Text Sync")]
        public void Log4NetText()
        {
            var user = new { Name = "Alice", Age = 30 };
            log4netLogger.InfoFormat($"User {user.Name} logged in");
        }
    }

日志库/模式Mean (平均耗时)StdDev (标准差)
Serilog Structured Async252.8 ns11.25 ns
NLog Text Async4,978.7 ns464.88 ns
Log4net Text Sync564,903.8 ns32,919.23 ns

关键结论

  1. Serilog异步批处理机制在并发场景下表现出色
  2. log4net同步锁导致严重线程竞争
  3. NLog在吞吐量与稳定性间取得平衡

三、实战代码示例

1. Serilog结构化日志实现

// 配置支持JSON输出的日志
Log.Logger = new LoggerConfiguration()
    .WriteTo.Console(new JsonFormatter())
    .WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://es:9200")))
    .CreateLogger();

// 结构化日志记录
var user = new { Id = 1, Name = "张三" };
Log.Information("用户登录 {@User},登录IP {IP}", user, "192.168.1.1");

输出效果

{
  "Timestamp": "2023-12-20T14:30:22.123Z",
  "Level": "Information",
  "Message": "用户登录 { Id: 1, Name: 张三 }, 登录IP 192.168.1.1",
  "User": { "Id": 1, "Name": "张三" },
  "IP": "192.168.1.1"
}

2. NLog多目标配置

<!-- NLog.config -->
<targets>
    <target name="file" xsi:type="File" 
            fileName="${basedir}/logs/${shortdate}.log"
            layout="${longdate}|${level}|${message}"/>
    
    <target name="db" xsi:type="Database"
            commandText="INSERT INTO Logs(Date,Level,Message) VALUES(@date,@level,@message)">
        <parameter name="@date" layout="${date}"/>
        <parameter name="@level" layout="${level}"/>
        <parameter name="@message" layout="${message}"/>
    </target>
</targets>

<rules>
    <logger name="*" minlevel="Info" writeTo="file,db"/>
</rules>

四、选型决策矩阵

评估维度权重SerilogNLoglog4net
性能25%958560
扩展性20%909580
易用性15%928575
综合得分100%90.586.572.5

五、终极选型建议

  1. 微服务/云原生场景

    • 首选Serilog:结构化日志与ELK生态无缝对接
    • 典型组合:Serilog + Elasticsearch + Grafana
  2. 传统企业级系统

    • 选择NLog:通过XML配置实现复杂日志路由策略
    • 推荐方案:NLog异步模式 + 数据库存储
  3. 遗留系统维护

    • 谨慎使用log4net:建议逐步替换或启用异步包装器
    • 过渡方案:log4net.Async扩展包 + 性能监控

避坑指南

  • 避免在log4net中直接使用同步日志核心组件
  • Serilog需注意对象序列化深度控制
  • NLog配置文件需启用async="true"属性

六、性能优化技巧

  1. 异步日志全攻略
// Serilog异步配置
.WriteTo.Async(a => a.File("logs/app.log"), 
    blockWhenFull: true,  // 队列满时阻塞
    bufferSize: 10000)     // 缓冲区大小

// NLog异步配置
<target name="asyncFile" xsi:type="AsyncWrapper" 
    queueLimit="5000" 
    overflowAction="Block">
    <target xsi:type="File" fileName="logs/nlog.log"/>
</target>
  1. 结构化日志优化原则
  • 避免记录大型对象(超过1MB)
  • 敏感字段使用Destructurama.Attribute标记
[NotLogged] 
public string Password { get; set; }
  1. 高性能日志实践
  • 禁用不必要的日志级别(生产环境关闭Debug)
  • 采用批处理写入(如Serilog的PeriodicBatching)
  • 文件日志启用滚动归档策略

结语

通过本文的深度对比可见,Serilog凭借现代化的设计理念在性能与功能上全面领先,而NLog在传统企业级场景仍具优势。建议新项目优先选择Serilog,老系统逐步迁移替换log4net。你的项目用哪个日志库?欢迎评论区交流实战经验!


http://www.kler.cn/a/580330.html

相关文章:

  • 在vs中无法用QtDesigner打开ui文件的解决方法
  • BGP(三)联盟、反射器
  • 区块链概述及比特币工作原理
  • DeepSeek开源Day5:3FSsmallpond技术详解
  • 最大括号深度
  • 【面试】Kafka
  • 三个一行的多选框组
  • 破局企业AI落地难题!迅易科技DeepSeek私有化部署全场景解决方案
  • Swagger笔记
  • C#中的【Obsolete】属性Attribute
  • MySQL初阶 | 库的操作
  • PostgreSQL 的登陆方式(本地和远程)
  • 《人月神话》:软件工程的成本寓言与生存法则
  • postgresql使用mysql_fdw连接mysql
  • 大白话如何使用 CSS 实现响应式布局?请列举一些常见的方法。
  • Vue3中如何实现单页应用(SPA)导航操作
  • HTML中的块元素与行内元素
  • P8700 [蓝桥杯 2019 国 B] 解谜游戏--string与cstring、memset()介绍
  • Unity Job系统详解原理和基础应用处理大量物体位置
  • 24.Harmonyos Next仿uv-ui 组件 NumberBox 步进器组件步长设置