Decade
Decade
Published on 2025-10-21 / 10 Visits
0
0

DotNet日志

1. 日志系统基础概念

1.1 什么是日志?

日志是记录应用程序运行状态的重要工具,帮助我们:

  • 调试程序问题

  • 监控系统运行状态

  • 分析用户行为

  • 故障排查

1.2 日志等级

日志分为6个等级,从详细到严重:

等级

说明

使用场景

Trace

最详细

开发调试时使用

Debug

调试信息

开发阶段问题定位

Information

常规信息

正常的业务流程记录

Warning

警告信息

异常但不影响系统运行

Error

错误信息

功能异常,需要关注

Critical

严重错误

系统级故障

2. 日志与依赖注入

2.1 在业务类中使用日志

csharp

public class UserService
{
    private readonly ILogger<UserService> _logger;
    
    // 通过构造函数注入ILogger
    public UserService(ILogger<UserService> logger)
    {
        _logger = logger;
    }
    
    public void CreateUser(string userName)
    {
        _logger.LogInformation("开始创建用户: {UserName}", userName);
        
        try
        {
            // 业务逻辑代码
            _logger.LogDebug("用户数据验证通过");
            
            // 模拟业务操作
            if (userName == "admin")
            {
                _logger.LogWarning("尝试创建管理员用户");
            }
        }
        catch (Exception ex)
        {
            // 记录异常信息和上下文
            _logger.LogError(ex, "创建用户失败: {UserName}", userName);
            throw;
        }
        
        _logger.LogInformation("用户创建成功: {UserName}", userName);
    }
}

2.2 异常处理最佳实践

csharp

// ✅ 推荐:让异常向上传播,在适当位置处理
public void ProcessOrder(Order order)
{
    _logger.LogInformation("处理订单 {OrderId}", order.Id);
    
    // 业务逻辑...
    
    // 只在真正能处理异常的地方捕获
    if (发生预期内的异常)
    {
        _logger.LogWarning("订单处理出现预期内问题,继续执行");
    }
}

// ❌ 避免:在每个方法中都捕获异常
public void BadExample()
{
    try
    {
        // 业务逻辑
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "错误");
        // 吞掉异常,调用方不知道出错了
    }
}

3. 日志配置

3.1 基础配置方式

csharp

// 程序启动时的配置
ServiceCollection services = new ServiceCollection();

services.AddLogging(builder =>
{
    builder.AddConsole();  // 输出到控制台
    builder.SetMinimumLevel(LogLevel.Debug); // 设置最低日志级别
    builder.AddDebug();    // 输出到调试窗口
});

services.AddScoped<UserService>();

3.2 配置解析

  • AddLogging():向DI容器添加日志服务

  • builder:日志构建器,用于配置输出方式和级别

  • 可以在回调中配置多个日志输出目标

4. 结构化日志与Serilog

4.1 为什么需要结构化日志?

传统日志问题:

csharp

// ❌ 难以分析和搜索
logger.LogInformation($"用户 {user.Name} 在 {DateTime.Now} 登录,IP: {ip}");

结构化日志优势:

csharp

// ✅ 易于查询和分析
logger.LogInformation("用户 {UserName} 在 {LoginTime} 登录,IP: {UserIP}", 
    user.Name, DateTime.Now, ip);

4.2 Serilog 配置

csharp

using Serilog;
using Serilog.Formatting.Json;

// 1. 配置 Serilog 日志
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()                    // 设置最低级别
    .Enrich.FromLogContext()                // 从上下文丰富日志
    .WriteTo.Console(new JsonFormatter())   // 输出JSON格式到控制台
    .WriteTo.File("logs/log-.txt", 
        rollingInterval: RollingInterval.Day) // 按天分割日志文件
    .CreateLogger();

// 2. 添加到DI容器
services.AddLogging(builder =>
{
    builder.ClearProviders();    // 清除默认提供程序
    builder.AddSerilog();        // 使用Serilog
});

4.3 结构化数据记录

csharp

// 记录简单属性
logger.LogInformation("用户 {UserName} 年龄 {Age} 登录成功", "张三", 25);

// 记录复杂对象 - 使用 @ 符号序列化整个对象
var user = new { Id = 3, Name = "zack", Email = "zack@example.com" };
logger.LogWarning("新增用户 {@Person}", user);

// 记录集合数据
var tags = new List<string> { "VIP", "NewUser" };
logger.LogDebug("用户标签: {@Tags}", tags);

4.4 输出目标配置

csharp

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .Enrich.FromLogContext()
    
    // 多种输出方式
    .WriteTo.Console()                            // 控制台
    .WriteTo.File("log.txt")                      // 文件
    .WriteTo.MongoDB("mongodb://localhost/logs")  // MongoDB
    .WriteTo.Exceptionless()                      // Exceptionless服务
    .WriteTo.SQLServer("连接字符串", "Logs表")     // 数据库
    
    .CreateLogger();

5. 完整示例程序

csharp

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;

namespace LogSystem
{
    class Program
    {
        static void Main()
        {
            // 1. 配置Serilog
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Debug()
                .Enrich.FromLogContext()
                .WriteTo.Console()
                .WriteTo.File("logs/myapp.txt", rollingInterval: RollingInterval.Day)
                .CreateLogger();

            try
            {
                // 2. 设置依赖注入
                var services = new ServiceCollection();
                services.AddLogging(builder => 
                {
                    builder.ClearProviders();
                    builder.AddSerilog();
                });
                
                services.AddScoped<UserService>();
                services.AddScoped<OrderService>();

                // 3. 运行应用程序
                using var provider = services.BuildServiceProvider();
                var userService = provider.GetRequiredService<UserService>();
                var orderService = provider.GetRequiredService<OrderService>();
                
                userService.CreateUser("Alice");
                orderService.ProcessOrder(12345);
            }
            finally
            {
                // 4. 确保日志被正确刷新
                Log.CloseAndFlush();
            }
        }
    }

    public class OrderService
    {
        private readonly ILogger<OrderService> _logger;
        
        public OrderService(ILogger<OrderService> logger)
        {
            _logger = logger;
        }
        
        public void ProcessOrder(int orderId)
        {
            // 使用结构化日志记录业务信息
            _logger.LogInformation("开始处理订单 {OrderId}", orderId);
            
            var orderInfo = new { OrderId = orderId, Status = "Processing", Time = DateTime.Now };
            _logger.LogDebug("订单详情: {@OrderInfo}", orderInfo);
            
            // 模拟业务逻辑
            _logger.LogInformation("订单 {OrderId} 处理完成", orderId);
        }
    }
}

6. 最佳实践总结

  1. 使用正确的日志级别:不要所有日志都用Information

  2. 善用结构化日志:使用占位符而不是字符串拼接

  3. 合理处理异常:在适当的位置记录异常,不要过度捕获

  4. 配置合适的输出:开发环境用Console,生产环境用文件或日志服务

  5. 包含足够上下文:日志中要包含排查问题需要的信息

  6. 注意性能:避免在日志中执行复杂操作

7. NLog vs Serilog

特性

NLog

Serilog

结构化日志

支持,配置复杂

支持,配置简单

学习曲线

较陡峭

较平缓

社区生态

成熟稳定

现代化,活跃

推荐程度

⭐⭐⭐

⭐⭐⭐⭐⭐

推荐使用 Serilog,因为它的结构化日志配置更简单直观!



Comment