1. 日志系统基础概念
1.1 什么是日志?
日志是记录应用程序运行状态的重要工具,帮助我们:
调试程序问题
监控系统运行状态
分析用户行为
故障排查
1.2 日志等级
日志分为6个等级,从详细到严重:
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. 最佳实践总结
使用正确的日志级别:不要所有日志都用Information
善用结构化日志:使用占位符而不是字符串拼接
合理处理异常:在适当的位置记录异常,不要过度捕获
配置合适的输出:开发环境用Console,生产环境用文件或日志服务
包含足够上下文:日志中要包含排查问题需要的信息
注意性能:避免在日志中执行复杂操作
7. NLog vs Serilog
推荐使用 Serilog,因为它的结构化日志配置更简单直观!