Skip to main content

日志组件 - Serilog

为什么选择 Serilog?

Serilog 优势

Serilog 是 .NET 中最流行的结构化日志库,支持丰富的输出目标(Sink)和强大的结构化日志功能。

📊 结构化日志 - 支持结构化数据,便于查询和分析

🔌 丰富的 Sink - 支持文件、数据库、Elasticsearch等多种输出

⚡ 高性能 - 异步写入,不阻塞应用程序

快速开始

1. 安装 NuGet 包

# 核心包
dotnet add package Serilog
dotnet add package Serilog.AspNetCore

# 常用 Sinks
dotnet add package Serilog.Sinks.Console
dotnet add package Serilog.Sinks.File
dotnet add package Serilog.Sinks.Seq

# 扩展
dotnet add package Serilog.Enrichers.Environment
dotnet add package Serilog.Enrichers.Thread

2. 基础配置

Program.cs (.NET 6+)
using Serilog;

// 创建日志配置
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.Console()
.WriteTo.File("logs/app-.log", rollingInterval: RollingInterval.Day)
.CreateLogger();

try
{
Log.Information("应用程序启动中...");

var builder = WebApplication.CreateBuilder(args);

// 使用 Serilog
builder.Host.UseSerilog();

builder.Services.AddControllers();

var app = builder.Build();

app.UseSerilogRequestLogging(); // 记录 HTTP 请求

app.MapControllers();

app.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "应用程序启动失败");
}
finally
{
Log.CloseAndFlush();
}

日志级别

级别使用场景示例
Verbose详细的调试信息跟踪程序执行流程
Debug调试信息变量值、状态信息
Information常规信息应用启动、关键操作完成
Warning警告信息可恢复的错误、性能问题
Error错误信息异常、失败的操作
Fatal致命错误导致应用崩溃的错误
Log.Verbose("详细信息:{Details}", details);
Log.Debug("调试信息:变量值 = {Value}", value);
Log.Information("用户 {UserId} 登录成功", userId);
Log.Warning("磁盘空间不足:{FreeSpace}GB", freeSpace);
Log.Error(ex, "处理订单 {OrderId} 时发生错误", orderId);
Log.Fatal(ex, "应用程序崩溃");

结构化日志

结构化日志的威力

使用 {} 占位符而非字符串插值,Serilog 会自动捕获结构化数据。

// 结构化日志:使用占位符
var user = new { Id = 123, Name = "张三", Email = "zhangsan@example.com" };
Log.Information("用户 {UserId} {UserName} 登录成功", user.Id, user.Name);

// 使用 @ 解构对象
Log.Information("创建订单:{@Order}", order);

// 使用 $ 转换为字符串
Log.Information("处理文件:{FilePath}", filePath);

输出(JSON格式):

{
"Timestamp": "2025-01-15T10:30:00",
"Level": "Information",
"MessageTemplate": "用户 {UserId} {UserName} 登录成功",
"Properties": {
"UserId": 123,
"UserName": "张三"
}
}

常用 Sinks

1. 文件 Sink

Log.Logger = new LoggerConfiguration()
.WriteTo.File(
path: "logs/app-.log",
rollingInterval: RollingInterval.Day, // 按天滚动
retainedFileCountLimit: 30, // 保留30天
fileSizeLimitBytes: 10_000_000, // 10MB
rollOnFileSizeLimit: true, // 超过大小时滚动
shared: true, // 多进程共享
flushToDiskInterval: TimeSpan.FromSeconds(1) // 刷新间隔
)
.CreateLogger();

2. Seq Sink (推荐用于开发和生产)

// 安装: dotnet add package Serilog.Sinks.Seq

Log.Logger = new LoggerConfiguration()
.WriteTo.Seq("http://localhost:5341")
.CreateLogger();
为什么用 Seq?
  • 🔍 强大的查询和过滤功能
  • 📊 实时日志查看和图表
  • 🎯 支持结构化日志
  • 🆓 免费版足够小团队使用

3. Elasticsearch Sink

// 安装: dotnet add package Serilog.Sinks.Elasticsearch

Log.Logger = new LoggerConfiguration()
.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://localhost:9200"))
{
AutoRegisterTemplate = true,
IndexFormat = "applogs-{0:yyyy.MM.dd}"
})
.CreateLogger();

4. 数据库 Sink

// 安装: dotnet add package Serilog.Sinks.MSSqlServer

Log.Logger = new LoggerConfiguration()
.WriteTo.MSSqlServer(
connectionString: "Server=.;Database=Logs;...",
sinkOptions: new MSSqlServerSinkOptions
{
TableName = "Logs",
AutoCreateSqlTable = true
})
.CreateLogger();

日志增强 (Enrichers)

Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext() // 从上下文添加属性
.Enrich.WithMachineName() // 机器名
.Enrich.WithThreadId() // 线程ID
.Enrich.WithEnvironmentName() // 环境名(开发/生产)
.Enrich.WithProperty("Application", "MyApp") // 自定义属性
.CreateLogger();

// 使用 LogContext 添加上下文属性
using (LogContext.PushProperty("UserId", userId))
{
Log.Information("执行操作"); // 自动包含 UserId
}

在代码中使用

1. 控制器中使用

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

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

[HttpGet("{id}")]
public async Task<IActionResult> GetUser(int id)
{
_logger.LogInformation("获取用户信息:{UserId}", id);

try
{
var user = await _userService.GetByIdAsync(id);

if (user == null)
{
_logger.LogWarning("用户不存在:{UserId}", id);
return NotFound();
}

_logger.LogInformation("成功获取用户:{@User}", user);
return Ok(user);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取用户失败:{UserId}", id);
return StatusCode(500, "内部服务器错误");
}
}
}

2. 服务中使用

public class OrderService
{
private readonly ILogger<OrderService> _logger;

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

public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
{
using (_logger.BeginScope("OrderId:{OrderId}", Guid.NewGuid()))
{
_logger.LogInformation("开始创建订单:{@Request}", request);

try
{
// 创建订单逻辑
var order = await CreateAsync(request);

_logger.LogInformation("订单创建成功:{OrderId}, 金额:{Amount}",
order.Id, order.TotalAmount);

return order;
}
catch (Exception ex)
{
_logger.LogError(ex, "创建订单失败");
throw;
}
}
}
}

最佳实践

日志最佳实践

1. 使用结构化日志

// ✅ 好
Log.Information("用户 {UserId} 下单 {OrderId},金额 {Amount}", userId, orderId, amount);

// ❌ 差
Log.Information($"用户 {userId} 下单 {orderId},金额 {amount}");

2. 合理选择日志级别

// Information: 重要业务事件
Log.Information("订单 {OrderId} 创建成功", orderId);

// Warning: 可恢复的异常情况
Log.Warning("缓存未命中,使用数据库查询:{CacheKey}", cacheKey);

// Error: 需要关注的错误
Log.Error(ex, "支付失败:{OrderId}", orderId);

3. 记录关键业务指标

_logger.LogInformation("订单处理耗时:{ElapsedMs}ms, 订单ID:{OrderId}",
elapsed.TotalMilliseconds, orderId);

4. 避免记录敏感信息

// ❌ 不要记录密码、信用卡号等敏感信息
Log.Information("用户登录:{Username}, 密码:{Password}", username, password);

// ✅ 只记录必要信息
Log.Information("用户登录:{Username}", username);

5. 使用作用域关联日志

using (_logger.BeginScope(new Dictionary<string, object>
{
["UserId"] = userId,
["CorrelationId"] = correlationId
}))
{
// 此作用域内的所有日志自动包含 UserId 和 CorrelationId
_logger.LogInformation("执行操作A");
_logger.LogInformation("执行操作B");
}

性能优化

1. 异步日志

// 使用 Async Sink 避免阻塞
Log.Logger = new LoggerConfiguration()
.WriteTo.Async(a => a.File("logs/app-.log"))
.CreateLogger();

2. 条件日志

// 避免不必要的字符串操作
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug("复杂计算结果:{Result}", ExpensiveOperation());
}

3. 批量写入

Log.Logger = new LoggerConfiguration()
.WriteTo.File(
"logs/app-.log",
buffered: true, // 启用缓冲
flushToDiskInterval: TimeSpan.FromSeconds(2) // 定期刷新
)
.CreateLogger();

HTTP 请求日志

var app = builder.Build();

// 记录 HTTP 请求
app.UseSerilogRequestLogging(options =>
{
options.MessageTemplate = "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000}ms";

// 自定义属性
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
diagnosticContext.Set("UserAgent", httpContext.Request.Headers["User-Agent"]);
diagnosticContext.Set("ClientIP", httpContext.Connection.RemoteIpAddress);
};
});

总结

关键要点
  • Serilog 提供强大的结构化日志功能
  • 使用 {} 占位符保持日志结构化
  • 选择合适的 Sink(文件、Seq、Elasticsearch)
  • 合理使用日志级别
  • 避免记录敏感信息
  • 使用异步日志提高性能

相关资源