百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

C#.NET NLog 详解(c#nuget)

zhezhongyun 2025-07-23 19:24 42 浏览

简介

NLog.NET 平台上最流行的开源日志框架之一,特色是 灵活的配置、丰富的输出目标(Target),以及 高性能 的异步写入能力。

适用场景:从控制台、文件、数据库、网络 到 ElasticSearch、Seq、Azure Table Storage 等各种日志收集后端。

支持文件、数据库(SQL/NoSQL)、控制台、邮件、Elasticsearch 等 50+ 内置目标,并可通过插件扩展

原生兼容 JSON 格式,可输出带上下文信息的结构化日志,便于 ELK 等系统分析

性能优势

  • 异步日志:默认异步写入,不影响主线程
  • 缓冲机制:批量写入减少 I/O 操作
  • 低开销:轻量级设计,最小化性能影响

安装

安装核心包

dotnet add package NLog

安装扩展包以集成 Microsoft.Extensions.Logging

dotnet add package NLog.Extensions.Logging
dotnet add package NLog.Web.AspNetCore

NLog.config 文件

在项目根目录添加一个 NLog.config(或 nlog.configXML 文件,IDE 会自动识别并在运行时加载

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <!-- 全局设置 -->
  <extensions>
    <!-- 扩展目标或布局,如:Seq、GELF 等 -->
  </extensions>

  <!-- 输出目标(Target) -->
  <targets>
    <!-- 控制台 -->
    <target xsi:type="Console" name="console" layout="${longdate}|${level:uppercase=true}|${logger}|${message}${onexception:inner=${newline}${exception:format=toString}}" />

    <!-- 文件 -->
    <target xsi:type="File"    name="file"
            fileName="logs/app.${shortdate}.log"
            layout="${longdate}|${level}|${logger}|${message}${onexception:${newline}${exception:format=toString}}" 
            archiveEvery="Day"
            maxArchiveFiles="7"
            concurrentWrites="true"
            keepFileOpen="false" />
  </targets>

  <!-- 日志规则(Rule):决定哪些 Logger 名称、哪些级别,写入哪些目标 -->
  <rules>
    <!-- 所有来自 “*” 的日志 ≥ Info  写到控制台 -->
    <logger name="*" minlevel="Info" writeTo="console" />
    <!-- 所有日志 ≥ Debug 写到文件 -->
    <logger name="*" minlevel="Debug" writeTo="file" />
  </rules>
</nlog>

appsettings.json 配置方案

{
  "NLog": {
    "targets": {
      "logfile": {
        "type": "File",
        "fileName": "logs/${shortdate}.log"
      }
    },
    "rules": [
      {
        "logger": "*",
        "minLevel": "Debug",
        "writeTo": "logfile"
      }
    ]
  }
}

C# 代码配置

var config = new LoggingConfiguration();

// 文件目标(按类名分目录)
var fileTarget = new FileTarget("timeLevelClassFile") {
    FileName = "NLog/${shortdate}/${logger}.log",
    Layout = "${longdate} ${level:uppercase=true} ${logger} ${message}",
    ArchiveEvery = FileArchivePeriod.Day,
    MaxArchiveDays = 30
};

// 异步包装提升性能
var asyncFileTarget = new AsyncTargetWrapper(fileTarget);

config.AddTarget(asyncFileTarget);
config.AddRule(LogLevel.Info, LogLevel.Fatal, asyncFileTarget);

// 应用配置
LogManager.Configuration = config;

核心组件

组件

作用

Logger

记录日志的入口,通常按类或命名空间获取:var log =
LogManager.GetCurrentClassLogger();

Target

日志输出目标,如 Console, File, Database, Mail, Network, Custom

Layout

输出格式模板,可包含 ${longdate},${level}, ${message}, ${exception} 等内置渲染器

Rule

日志规则,匹配 Logger 名称与级别后,将日志路由到一个或多个 Target

Filter

规则的高级过滤器,精细控制哪些消息被忽略或处理

在代码中使用

using NLog;

public class MyService
{
    private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();

    public void DoWork()
    {
        Logger.Trace("Trace message");
        Logger.Debug("Debug message");
        Logger.Info("Application started");
        
        try
        {
            // ...
            throw new InvalidOperationException("Oops");
        }
        catch (Exception ex)
        {
            Logger.Error(ex, "Unhandled exception in DoWork");
        }
    }
}
  • Log Levels

Trace < Debug < Info < Warn < Error < Fatal

  • 参数化日志
Logger.Info("Processing {OrderId} for {Customer}", order.Id, customer.Name);

常用布局渲染器

  • ${longdate}:完整日期时间
  • ${level}:日志级别
  • ${logger}:记录器名称
  • ${message}:日志消息
  • ${exception}:异常信息
  • ${callsite}:调用位置(类名+方法名)
  • ${stacktrace}:堆栈跟踪
  • ${machinename}:机器名
  • ${processid}:进程 ID
  • ${threadid}:线程 ID
  • ${aspnet-request-url}:ASP.NET 请求 URL

高级用法

结构化日志

Logger.Info("订单 {OrderId} 创建,用户 {UserId}", orderId, userId);

Mapped Diagnostics Context (MDC) / Mapped Diagnostics Logical Context (MDLC)

用于给日志记录中添加每个执行上下文的额外属性,比如请求 ID、用户 ID 等。

using NLog.MappedDiagnosticsLogicalContext;

public void HandleRequest(HttpContext ctx)
{
    MDLC.Set("RequestId", ctx.TraceIdentifier);
    MDLC.Set("User", ctx.User?.Identity?.Name ?? "anonymous");

    Logger.Info("Handling request");
    // …
    MDLC.Remove("User");
}

在 Layout 中引用:

layout="${longdate}|${mdlc:item=RequestId}|${mdlc:item=User}|${message}"

异步日志记录

<targets async="true">
  <target name="file" xsi:type="File" fileName="logs/${shortdate}.log" />
</targets>

使用缓存

<target name="file" xsi:type="File">
  <layout cache="${longdate} ${message}" />
</target>

异步与缓存

  • AsyncWrapper:将任何目标包裹为异步写入,避免日志写入阻塞业务线程
<target xsi:type="AsyncWrapper" name="asyncFile" overflowAction="Discard"
        timeToSleepBetweenBatches="50" batchSize="100">
  <target-wrapper>
    <target xsi:type="File" fileName="logs/async.log" layout="…"/>
  </target-wrapper>
</target>
  • BufferingWrapper:按条数或时间批量写入,适合数据库、网络等慢目标。
<target name="buffered" xsi:type="BufferingWrapper" bufferSize="100">
  <target xsi:type="File" fileName="buffered.log" />
</target>

条件过滤

<rules>
  <!-- 仅记录包含特定关键字的日志 -->
  <logger name="*" writeTo="file">
    <filters>
      <when condition="contains(message, 'payment')" action="Log" />
    </filters>
  </logger>
</rules>

文件归档策略

<target name="rollingFile" xsi:type="File"
        fileName="${basedir}/logs/app.log"
        archiveFileName="${basedir}/logs/archive/app.{#}.log"
        archiveEvery="Day"
        archiveNumbering="Rolling"
        maxArchiveFiles="30"
        layout="${longdate} ${message}" />

自定义扩展

  • 创建自定义 Target
[Target("CustomTarget")]
public sealed class CustomTarget : TargetWithLayout
{
    protected override void Write(LogEventInfo logEvent)
    {
        string logMessage = RenderLogEvent(Layout, logEvent);
        // 自定义输出逻辑(如发送到第三方服务)
        SendToCustomService(logMessage);
    }
}
  • 配置自定义 Target
<targets>
  <target name="custom" xsi:type="CustomTarget" 
          layout="${message}" />
</targets>

多环境配置

<!-- 开发环境 -->
<logger name="*" minlevel="Debug" writeTo="console,debug-file" />

<!-- 生产环境 -->
<logger name="*" minlevel="Info" writeTo="file" />
<logger name="Microsoft.*" minlevel="Warning" final="true" />

配置变量

<variable name="logDirectory" value="${basedir}/logs" />
<target name="file" xsi:type="File" fileName="${logDirectory}/${shortdate}.log" />

避免日志记录开销

// 避免不必要的字符串拼接
if (Logger.IsDebugEnabled) {
    Logger.Debug("复杂计算结果: {0}", ExpensiveCalculation());
}

// 或使用结构化日志
Logger.Debug("用户 {UserName} 登录", userName);

启用内部日志调试

<nlog internalLogFile="nlog-internal.log" internalLogLevel="Debug">

按级别/命名空间过滤

<rules>
  <!-- Controllers 命名空间下只记录 Error+ -->
  <logger name="MyApp.Controllers.*" minlevel="Error" writeTo="file" />
  
  <!-- 全局记录 Info+ -->
  <logger name="*" minlevel="Info" writeTo="file" />
</rules>

数据库日志写入

<target name="database" xsi:type="Database"
        connectionString="Server=.;Database=Logs;Trusted_Connection=True;"
        commandText="INSERT INTO Logs (Time, Level, Logger, Message) VALUES (@time, @level, @logger, @message)">
  <parameter name="@time" layout="${longdate}" />
  <parameter name="@level" layout="${level}" />
  <parameter name="@logger" layout="${logger}" />
  <parameter name="@message" layout="${message}" />
</target>

结构化日志(Elasticsearch 集成)

<target name="elastic" xsi:type="ElasticSearch"
        uri="http://localhost:9200"
        index="applogs-${date:format=yyyy.MM}">
  <field name="message" layout="${message}" />
  <field name="level" layout="${level}" />
  <field name="exception" layout="${exception:format=toString}" />
</target>

故障转移:FallbackGroup 目标(主目标失败时切备用)

<target name="failover" xsi:type="FallbackGroup">
  <target xsi:type="Database" connectionString="..." />
  <target xsi:type="File" fileName="fallback.log" /> <!--  -->
</target>

不同环境下集成

配置 Program.cs

// Program.cs
using NLog.Web;

var builder = WebApplication.CreateBuilder(args);

// 配置 NLog
builder.Logging.ClearProviders();
builder.Host.UseNLog();

// 其他配置...

配置 Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddLogging(loggingBuilder => {
        loggingBuilder.ClearProviders();
        loggingBuilder.AddNLog();
    });
}

控制台应用集成

// 手动加载配置
LogManager.LoadConfiguration("NLog.config");

依赖注入集成

// 在 Startup.cs 中注册
services.AddSingleton<ILogger>(LogManager.GetCurrentClassLogger());

注入 ILogger

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;

    public HomeController(ILogger<HomeController> logger)
    {
        _logger = logger;
    }

    public IActionResult Index()
    {
        _logger.LogInformation("访问首页");
        return View();
    }
}

中间件记录请求

app.Use(async (context, next) =>
{
    var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
    
    var sw = Stopwatch.StartNew();
    await next();
    sw.Stop();
    
    logger.LogInformation("请求 {Method} {Path} 耗时 {Elapsed}ms", 
        context.Request.Method, 
        context.Request.Path, 
        sw.ElapsedMilliseconds);
});

AsyncWrapper VS async="true"

特性

AsyncWrapper

async="true"(简化配置)

写法

显式使用 <target xsi:type="AsyncWrapper"> 包裹

在普通 target 上设置 async="true"

能力

更强大、更灵活(支持缓冲大小、丢弃策略、包装多个 target)

语法更简洁,适合简单异步场景

推荐使用

更推荐生产使用

快速开发或小型项目时可用

背后机制

AsyncWrapper

NLog 提供的一个强大的异步包装器,适用于任何 target。它使用独立的后台线程池写日志,并允许配置行为细节

<target xsi:type="AsyncWrapper"
        name="asyncFile"
        overflowAction="Discard"
        batchSize="100"
        timeToSleepBetweenBatches="50">
  <target xsi:type="File"
          fileName="logs/app.log"
          layout="${longdate}|${level}|${message}" />
</target>
  • overflowAction:当队列满时的行为(默认 Block, 可设为 DiscardGrow
  • batchSize:每次最多写多少条
  • timeToSleepBetweenBatches:线程空闲等待时间
  • 支持多个目标包裹(多个 <target>

async="true"

NLog 针对简单异步写日志的简化语法,本质是自动为该 target 添加一个 AsyncWrapper 包裹层

<target xsi:type="File" name="file" async="true"
        fileName="logs/app.log"
        layout="${longdate}|${level}|${message}" />

相当于 NLog 内部自动做了:

<target xsi:type="AsyncWrapper" name="file">
  <target xsi:type="File" ... />
</target>

但它不支持配置 batchSize、overflowAction 等高级参数。

使用场景建议:

场景

推荐方式

原因说明

中小型项目,日志量不大

async="true"

简洁、易用,性能也足够

生产环境、日志量大(高并发写日志)

AsyncWrapper

提高性能、可配置批处理大小、溢出策略等,适合高负载

需要多个目标异步写入(如控制台+文件)

AsyncWrapper

一个 AsyncWrapper 可包裹多个目标,async="true" 无法组合多个目标

需要控制日志队列行为

AsyncWrapper

可配置缓冲区行为:Discard / Block / Grow 等

快速调试开发

async="true"

配置文件更简洁、清晰

终极文件目标配置

<?xml version="1.0" encoding="utf-8"?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <!-- 全局设置 -->
  <targets>

    <!-- 异步包装器:所有 File 写入都经过异步队列 -->
    <target xsi:type="AsyncWrapper" name="asyncFile"
            overflowAction="Discard"
            batchSize="200"
            timeToSleepBetweenBatches="50">

      <!-- 真正的文件目标 -->
      <target xsi:type="File"
              name="dailyRollingFile"
              fileName="logs/app.${shortdate}.log"
              layout="${longdate}|${level:uppercase=true}|${logger}|${message}${onexception:${newline}${exception:format=toString}}"

              <!-- 每天归档 -->
              archiveEvery="Day"
              
              <!-- 大小到 10MB 时滚动归档 -->
              archiveAboveSize="10485760"            

              <!-- 归档文件命名:app.2025-06-24.0.log, app.2025-06-24.1.log, ... -->
              archiveNumbering="Rolling"

              <!-- 归档文件名称模板(可选,默认已自动添加编号) -->
              archiveFileName="logs/archives/app.${shortdate}.{#}.log"

              <!-- 保留最近 7 个归档文件 -->
              maxArchiveFiles="7"

              <!-- 并发写入设置 -->
              concurrentWrites="true"
              keepFileOpen="false"
              encoding="utf-8"
              />

    </target>

    <!-- 也可以单独给控制台输出异步 -->
    <target xsi:type="AsyncWrapper" name="asyncConsole">
      <target xsi:type="Console" 
              layout="${longdate}|${level}|${message}" />
    </target>
    
  </targets>

  <rules>
    <!-- 所有日志 >= Info 写到控制台 -->
    <logger name="*" minlevel="Info" writeTo="asyncConsole" />

    <!-- 所有日志 >= Debug 写到文件 -->
    <logger name="*" minlevel="Debug" writeTo="asyncFile" />
  </rules>
</nlog>

配置说明:

  • fileName="logs/app.${shortdate}.log"
    每天都会以 app.YYYY-MM-DD.log 的文件名写入新日志。
  • archiveEvery="Day"
    在每个日历天的开头,都会对前一天的文件进行归档(配合 archiveNumbering 控制编号方式)。
  • archiveAboveSize="10485760"
    当当天的当前日志文件大小超过 10 MB(10×1024×1024 bytes)时,会自动把它归档并新建一个同名空文件继续写日志。
  • archiveNumbering="Rolling"
    使用滚动编号方式:第一个归档为 .0.log,第二个为 .1.log,以此类推。
  • archiveFileName="logs/archives/app.${shortdate}.{#}.log"
    将归档文件集中到 logs/archives/ 目录下,便于管理。{#} 会被替换成自增的归档号。
  • maxArchiveFiles="7"
    最多保留最近 7 个归档文件,超过则自动删除最旧的。
  • AsyncWrapper
    • overflowAction="Discard":队列满时丢弃最旧的日志,避免阻塞;
    • batchSize="200":每批写入 200 条;
    • timeToSleepBetweenBatches="50":批量写入后等待 50 ms。
  • concurrentWrites="true" & keepFileOpen="false"
    在并发写入和文件锁管理上更稳健,适合多线程/多进程场景。

相关推荐

Python入门学习记录之一:变量_python怎么用变量

写这个,主要是对自己学习python知识的一个总结,也是加深自己的印象。变量(英文:variable),也叫标识符。在python中,变量的命名规则有以下三点:>变量名只能包含字母、数字和下划线...

python变量命名规则——来自小白的总结

python是一个动态编译类编程语言,所以程序在运行前不需要如C语言的先行编译动作,因此也只有在程序运行过程中才能发现程序的问题。基于此,python的变量就有一定的命名规范。python作为当前热门...

Python入门学习教程:第 2 章 变量与数据类型

2.1什么是变量?在编程中,变量就像一个存放数据的容器,它可以存储各种信息,并且这些信息可以被读取和修改。想象一下,变量就如同我们生活中的盒子,你可以把东西放进去,也可以随时拿出来看看,甚至可以换成...

绘制学术论文中的“三线表”具体指导

在科研过程中,大家用到最多的可能就是“三线表”。“三线表”,一般主要由三条横线构成,当然在变量名栏里也可以拆分单元格,出现更多的线。更重要的是,“三线表”也是一种数据记录规范,以“三线表”形式记录的数...

Python基础语法知识--变量和数据类型

学习Python中的变量和数据类型至关重要,因为它们构成了Python编程的基石。以下是帮助您了解Python中的变量和数据类型的分步指南:1.变量:变量在Python中用于存储数据值。它们充...

一文搞懂 Python 中的所有标点符号

反引号`无任何作用。传说Python3中它被移除是因为和单引号字符'太相似。波浪号~(按位取反符号)~被称为取反或补码运算符。它放在我们想要取反的对象前面。如果放在一个整数n...

Python变量类型和运算符_python中变量的含义

别再被小名词坑哭了:Python新手常犯的那些隐蔽错误,我用同事的真实bug拆给你看我记得有一次和同事张姐一起追查一个看似随机崩溃的脚本,最后发现罪魁祸首竟然是她把变量命名成了list。说实话...

从零开始:深入剖析 Spring Boot3 中配置文件的加载顺序

在当今的互联网软件开发领域,SpringBoot无疑是最为热门和广泛应用的框架之一。它以其强大的功能、便捷的开发体验,极大地提升了开发效率,成为众多开发者构建Web应用程序的首选。而在Spr...

Python中下划线 ‘_’ 的用法,你知道几种

Python中下划线()是一个有特殊含义和用途的符号,它可以用来表示以下几种情况:1在解释器中,下划线(_)表示上一个表达式的值,可以用来进行快速计算或测试。例如:>>>2+...

解锁Shell编程:变量_shell $变量

引言:开启Shell编程大门Shell作为用户与Linux内核之间的桥梁,为我们提供了强大的命令行交互方式。它不仅能执行简单的文件操作、进程管理,还能通过编写脚本实现复杂的自动化任务。无论是...

一文学会Python的变量命名规则!_python的变量命名有哪些要求

目录1.变量的命名原则3.内置函数尽量不要做变量4.删除变量和垃圾回收机制5.结语1.变量的命名原则①由英文字母、_(下划线)、或中文开头②变量名称只能由英文字母、数字、下画线或中文字所组成。③英文字...

更可靠的Rust-语法篇-区分语句/表达式,略览if/loop/while/for

src/main.rs://函数定义fnadd(a:i32,b:i32)->i32{a+b//末尾表达式}fnmain(){leta:i3...

C++第五课:变量的命名规则_c++中变量的命名规则

变量的命名不是想怎么起就怎么起的,而是有一套固定的规则的。具体规则:1.名字要合法:变量名必须是由字母、数字或下划线组成。例如:a,a1,a_1。2.开头不能是数字。例如:可以a1,但不能起1a。3....

Rust编程-核心篇-不安全编程_rust安全性

Unsafe的必要性Rust的所有权系统和类型系统为我们提供了强大的安全保障,但在某些情况下,我们需要突破这些限制来:与C代码交互实现底层系统编程优化性能关键代码实现某些编译器无法验证的安全操作Rus...

探秘 Python 内存管理:背后的神奇机制

在编程的世界里,内存管理就如同幕后的精密操控者,确保程序的高效运行。Python作为一种广泛使用的编程语言,其内存管理机制既巧妙又复杂,为开发者们提供了便利的同时,也展现了强大的底层控制能力。一、P...