第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会把参数单独提取出来,方便后续做日志分析。我在项目中遇到过很多次,用字符串拼接的日志,排查问题时根本没法做关键字搜索。
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做全链路追踪,把请求的整个调用链串起来。到时候你会发现,日志不只是记录错误,更是排查问题的利器。