第4章:NLog实战——在ASP.NET Core中集成NLog

好,咱们今天来聊聊NLog。说实话,在.NET生态里,NLog是我个人用得最顺手的日志框架之一。它轻量、灵活,而且配置起来特别直观。我记得刚入行那会儿,项目里还在用自己写的日志类,后来一接触NLog,就再也没换过别的。

这一章,咱们就手把手把NLog集成到ASP.NET Core项目里。我会把依赖注入、配置文件、中间件这些关键点都讲透。你跟着走一遍,基本就能直接用到生产环境了。

4.1 为什么选NLog?

你可能会问:市面上日志框架那么多,Serilog、log4net、Microsoft.Extensions.Logging,为什么偏偏选NLog?

我的答案是:NLog在性能和灵活性之间找到了一个很好的平衡点。它支持结构化日志,也支持传统的文本日志。配置起来比Serilog简单,但功能一点不差。我在项目中遇到过几次高并发场景,NLog的表现都很稳,没有出现丢日志或者性能瓶颈的情况。

说白了,选NLog就是图个省心。你不需要花太多时间在日志框架本身,把精力留给业务逻辑就好。

4.2 安装NLog包

首先,你得把NLog的NuGet包装进项目。打开你的ASP.NET Core项目,在包管理器控制台里敲两行命令:

Install-Package NLog.Web.AspNetCore
Install-Package NLog.Extensions.Logging

嗯,这里要注意一下版本。我建议直接用最新稳定版,别追预览版。预览版有时候会有兼容性问题,我在一个项目里吃过这个亏,折腾了半天才发现是版本不匹配。

装完之后,你的项目文件里应该能看到这两个包的引用。如果你用的是.NET 6及以上版本,基本不会有兼容性问题。

4.3 配置NLog配置文件

接下来,咱们创建一个NLog的配置文件。在项目根目录下新建一个 nlog.config 文件。这个文件就是NLog的“大脑”,所有日志规则都在这里定义。

我给你一个我常用的模板:

<?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>
    <!-- 文件输出 -->
    <target name="file" xsi:type="File"
            fileName="${basedir}/logs/${shortdate}.log"
            layout="${longdate} | ${level:uppercase=true} | ${logger} | ${message} ${exception:format=ToString}" />

    <!-- 控制台输出 -->
    <target name="console" xsi:type="Console"
            layout="${longdate} | ${level:uppercase=true} | ${message}" />
  </targets>

  <!-- 定义日志规则 -->
  <rules>
    <!-- 所有日志都输出到文件和控制台 -->
    <logger name="*" minlevel="Info" writeTo="file,console" />
  </rules>
</nlog>

这个配置做了两件事:一是把日志写到文件里,按日期分文件;二是同时在控制台输出。你想想看,开发的时候看控制台,生产环境查文件,是不是很方便?

我个人习惯把日志文件放在 logs 目录下,这样方便统一管理。如果你有多个项目,建议在文件名里加上项目名,避免混淆。

4.4 在Program.cs中集成NLog

配置文件准备好了,接下来就是在 Program.cs 里把NLog挂到ASP.NET Core的日志管道上。这一步很关键,搞错了日志就出不来了。

看代码:

using NLog;
using NLog.Web;

var builder = WebApplication.CreateBuilder(args);

// 清除默认的日志提供程序,只使用NLog
builder.Logging.ClearProviders();
builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);

// 使用NLog作为日志提供程序
builder.Host.UseNLog();

// 其他服务注册...
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// 配置中间件管道
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();

这里有个细节:builder.Host.UseNLog() 这行代码必须在 builder.Build() 之前调用。我曾经有一次把它写到了后面,结果日志死活不工作,排查了半天才发现是顺序问题。

另外,ClearProviders() 这一步不是必须的,但我建议你加上。因为默认的Console提供程序会和NLog冲突,导致日志重复输出。你想想看,一条日志在控制台出现两次,排查问题的时候多闹心。

4.5 依赖注入:在控制器中使用ILogger

集成完成后,你就可以在控制器里通过依赖注入使用 ILogger<T> 了。ASP.NET Core的日志抽象层会自动把日志转发给NLog。

举个例子:

[ApiController]
[Route("api/[controller]")]
public class WeatherController : ControllerBase
{
    private readonly ILogger<WeatherController> _logger;

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

    [HttpGet]
    public IActionResult Get()
    {
        _logger.LogInformation("开始获取天气数据...");

        try
        {
            // 模拟业务逻辑
            var weatherData = new { City = "北京", Temperature = 25 };
            _logger.LogInformation("天气数据获取成功: {City}, {Temperature}°C", weatherData.City, weatherData.Temperature);
            return Ok(weatherData);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "获取天气数据时发生异常");
            return StatusCode(500, "服务器内部错误");
        }
    }
}

你看,这里用了结构化日志的写法。用占位符 {City}{Temperature} 代替字符串拼接,这样NLog会把参数单独提取出来,方便后续做日志分析。我在项目中遇到过很多次,用字符串拼接的日志,排查问题时根本没法做关键字搜索。

小提示: 结构化日志的占位符名称最好和参数名一致,这样日志分析工具(比如Elasticsearch)能自动识别字段。我一般会保持命名风格统一,比如都用驼峰命名。

4.6 请求日志中间件:记录每一次HTTP请求

好了,基础集成搞定了。接下来咱们做一个实战中非常实用的功能:请求日志中间件。这个中间件可以记录每一次HTTP请求的详细信息,包括请求路径、方法、状态码、耗时等。

为什么要做这个?你想想看,线上出了问题,用户说“我访问那个页面报错了”,你总得知道是哪个请求、什么时候、花了多长时间吧?有了请求日志,这些问题一目了然。

咱们先写一个中间件类:

public class RequestLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestLoggingMiddleware> _logger;

    public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var stopwatch = Stopwatch.StartNew();

        // 记录请求开始
        _logger.LogInformation("HTTP {Method} {Path} 开始处理", 
            context.Request.Method, context.Request.Path);

        try
        {
            await _next(context);
            stopwatch.Stop();

            // 记录请求结束
            _logger.LogInformation("HTTP {Method} {Path} 处理完成,状态码: {StatusCode},耗时: {ElapsedMilliseconds}ms",
                context.Request.Method,
                context.Request.Path,
                context.Response.StatusCode,
                stopwatch.ElapsedMilliseconds);
        }
        catch (Exception ex)
        {
            stopwatch.Stop();
            _logger.LogError(ex, "HTTP {Method} {Path} 处理异常,耗时: {ElapsedMilliseconds}ms",
                context.Request.Method,
                context.Request.Path,
                stopwatch.ElapsedMilliseconds);
            throw; // 重新抛出异常,让全局异常处理中间件处理
        }
    }
}

然后在 Program.cs 里注册这个中间件:

// 在 app.Build() 之后,UseRouting 之前注册
app.UseMiddleware<RequestLoggingMiddleware>();
注意: 中间件的注册顺序很重要。请求日志中间件应该放在管道的最前面,这样才能记录到所有请求。如果放在后面,有些请求可能已经被前面的中间件拦截了,日志就不完整了。我曾经在一个项目里把中间件放在了认证中间件后面,结果未认证的请求全都没记录到日志里,排查问题时漏掉了关键信息。

4.7 配置日志级别和过滤

生产环境里,日志量可能会非常大。你肯定不想把Debug级别的日志都写到文件里,那样磁盘很快就满了。所以,合理配置日志级别很重要。

nlog.config 里,你可以针对不同的命名空间设置不同的日志级别:

<rules>
  <!-- 第三方库的日志只记录Warning及以上 -->
  <logger name="Microsoft.*" minlevel="Warning" writeTo="file" />
  <logger name="System.*" minlevel="Warning" writeTo="file" />

  <!-- 自己项目的日志记录Info及以上 -->
  <logger name="YourNamespace.*" minlevel="Info" writeTo="file,console" />

  <!-- 所有其他日志记录Error及以上 -->
  <logger name="*" minlevel="Error" writeTo="file" />
</rules>

这样配置的好处是:你只关注自己项目的日志,第三方库的日志只在出问题时才看。磁盘空间也能省不少。

4.8 实战中的避坑指南

最后,我分享几个实战中踩过的坑,你遇到了可以少走弯路:

  • 日志文件权限问题: 在Linux服务器上,如果应用程序没有 logs 目录的写入权限,日志会静默丢失。我曾经在Docker容器里部署时忘了挂载日志目录,结果排查问题时发现日志文件根本没生成。建议在启动时检查目录权限,或者用 ${basedir} 相对路径。
  • 日志文件滚动策略: 如果不配置文件滚动,日志文件会无限增长,最终撑爆磁盘。我建议配置按天滚动,并保留最近30天的日志。在 nlog.config 里加上 archiveEvery="Day"maxArchiveFiles="30"
  • 敏感信息脱敏: 日志里不要记录密码、身份证号等敏感信息。我在一个项目里看到有人把用户密码直接打到了日志里,这要是被泄露出去,后果不堪设想。建议在记录日志前对敏感字段做脱敏处理。

好了,这一章的内容就到这里。你跟着走一遍,应该能在自己的项目里把NLog用起来了。下一章咱们聊聊如何用NLog做全链路追踪,把请求的整个调用链串起来。到时候你会发现,日志不只是记录错误,更是排查问题的利器。