C# Serilog使用:现代结构化日志记录完全指南


Serilog是.NET生态中最受欢迎的结构化日志记录框架之一,以其强大的日志处理能力和丰富的输出目标(Sinks)支持而著称。本文将全面介绍如何在C#项目中使用Serilog实现高效的日志记录。

一、Serilog核心概念

1.1 结构化日志 vs 传统日志

传统日志:

2023-08-15 14:30:45 [INFO] 用户12345登录成功

结构化日志(Serilog):

{
  "Timestamp": "2023-08-15T14:30:45.123456Z",
  "Level": "Information",
  "Message": "用户 {UserId} 登录成功",
  "Properties": {
    "UserId": "12345",
    "SourceContext": "AuthService",
    "IPAddress": "192.168.1.100"
  }
}

1.2 核心组件

组件作用
Logger日志记录器,用于创建和写入日志事件
Sink日志输出目标(控制台、文件、数据库等)
Enricher日志丰富器,用于添加额外属性(线程ID、机器名等)
Filter日志过滤器,控制哪些日志会被记录
Formatting控制日志输出的格式

二、基础安装与配置

2.1 安装NuGet包

# 基础包
dotnet add package Serilog
dotnet add package Serilog.Sinks.Console
dotnet add package Serilog.Sinks.File

# ASP.NET Core集成
dotnet add package Serilog.AspNetCore

2.2 最小化配置示例

using Serilog;

// 创建Logger配置
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .WriteTo.Console()
    .WriteTo.File("logs/myapp.txt", rollingInterval: RollingInterval.Day)
    .CreateLogger();

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

    // 记录结构化日志
    var user = new { Id = 123, Name = "张三" };
    Log.Information("用户 {@User} 登录成功", user);
}
catch (Exception ex)
{
    Log.Fatal(ex, "应用程序启动失败");
}
finally
{
    Log.CloseAndFlush(); // 确保所有日志都被写入
}

三、ASP.NET Core集成

3.1 完整集成示例

// Program.cs
using Serilog;

// 初始化Serilog配置
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
    .Enrich.FromLogContext()
    .Enrich.WithMachineName()
    .Enrich.WithProcessId()
    .Enrich.WithThreadId()
    .WriteTo.Console(outputTemplate: 
        "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties}{NewLine}{Exception}")
    .WriteTo.File("logs/webapp.log", rollingInterval: RollingInterval.Day)
    .CreateLogger();

try
{
    var builder = WebApplication.CreateBuilder(args);

    // 使用Serilog替代默认日志系统
    builder.Host.UseSerilog();

    var app = builder.Build();

    // 示例中间件,演示日志上下文
    app.Use(async (context, next) =>
    {
        using (LogContext.PushProperty("RequestId", context.TraceIdentifier))
        {
            await next();
        }
    });

    app.MapGet("/", () => 
    {
        Log.Information("首页被访问");
        return "Hello World!";
    });

    app.Run();
}
catch (Exception ex)
{
    Log.Fatal(ex, "应用程序终止");
}
finally
{
    Log.CloseAndFlush();
}

3.2 控制器中使用示例

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

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

    [HttpGet]
    public IActionResult Get()
    {
        // 结构化日志记录
        _logger.LogInformation("获取天气数据,城市: {City}", "北京");

        return Ok(new { Temperature = 25, Summary = "Sunny" });
    }
}

四、常用Sinks配置

4.1 控制台输出

.WriteTo.Console(
    outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message}{NewLine}{Exception}{NewLine}")

4.2 文件输出

.WriteTo.File(
    path: "logs/log-.txt",
    rollingInterval: RollingInterval.Day,
    retainedFileCountLimit: 7,
    shared: true,
    outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")

4.3 Seq日志服务器

.WriteTo.Seq("http://localhost:5341",
    apiKey: "your-api-key",
    controlLevelSwitch: new LoggingLevelSwitch(LogEventLevel.Verbose))

4.4 Elasticsearch

.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://localhost:9200"))
{
    IndexFormat = "myapp-logs-{0:yyyy.MM.dd}",
    AutoRegisterTemplate = true,
    NumberOfShards = 2,
    NumberOfReplicas = 1
})

4.5 SQL数据库

.WriteTo.MSSqlServer(
    connectionString: "Server=.;Database=Logs;Integrated Security=SSPI;",
    tableName: "LogEvents",
    autoCreateSqlTable: true)

五、日志丰富(Enrichers)

5.1 常用Enrichers

.Enrich.WithProperty("Application", "MyApp")
.Enrich.WithMachineName()
.Enrich.WithProcessId()
.Enrich.WithThreadId()
.Enrich.WithEnvironmentUserName()
.Enrich.WithAssemblyName()
.Enrich.WithAssemblyVersion()
.Enrich.WithMemoryUsage()

5.2 自定义Enricher

public class UserEnricher : ILogEventEnricher
{
    public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
    {
        var user = HttpContext.Current?.User?.Identity?.Name ?? "anonymous";
        var property = propertyFactory.CreateProperty("CurrentUser", user);
        logEvent.AddPropertyIfAbsent(property);
    }
}

// 使用
.Enrich.With<UserEnricher>()

六、日志过滤

6.1 基本过滤

.Filter.ByIncludingOnly(Matching.FromSource("MyApp.Controllers"))
.Filter.ByExcluding(Matching.WithProperty<int>("StatusCode", code => code >= 200 && code < 300))

6.2 条件过滤

.WriteTo.Logger(lc => lc
    .Filter.ByIncludingOnly(e => e.Level == LogEventLevel.Error)
    .WriteTo.File("logs/errors.txt"))

七、高级配置技巧

7.1 动态日志级别

var levelSwitch = new LoggingLevelSwitch(LogEventLevel.Information);

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.ControlledBy(levelSwitch)
    .WriteTo.Console()
    .CreateLogger();

// 运行时修改日志级别
levelSwitch.MinimumLevel = LogEventLevel.Warning;

7.2 子日志记录器

var diagnosticLogger = Log.ForContext("LoggerType", "Diagnostic");
diagnosticLogger.Information("系统诊断信息 {@Metrics}", metrics);

7.3 日志上下文

using (LogContext.PushProperty("TransactionId", Guid.NewGuid()))
using (LogContext.PushProperty("UserId", user.Id))
{
    Log.Information("处理用户请求");
}

八、性能优化

8.1 异步日志

.WriteTo.Async(a => a.Console())
.WriteTo.Async(a => a.File("logs/async.txt"))

8.2 批量写入

.WriteTo.Sink(new PeriodicBatchingSink(
    new FileSink("logs/batched.txt", new JsonFormatter(), null),
    new PeriodicBatchingSinkOptions
    {
        BatchSizeLimit = 100,
        Period = TimeSpan.FromSeconds(5)
    }))

8.3 性能敏感场景

// 使用LoggerMessage模式
public static class LogEvents
{
    public static readonly Action<ILogger, int, Exception?> ProcessingOrder = 
        LoggerMessage.Define<int>(
            LogLevel.Information,
            new EventId(1001, "ProcessingOrder"),
            "处理订单 {OrderId}");
}

// 使用
LogEvents.ProcessingOrder(_logger, order.Id, null);

九、最佳实践

  1. 结构化日志优先:始终使用{Property}格式而非字符串拼接
  2. 合理选择Sinks:生产环境避免仅使用控制台输出
  3. 日志级别控制:生产环境通常设为Information级别
  4. 敏感信息保护:避免记录密码、密钥等
  5. 上下文丰富:添加请求ID、用户信息等
  6. 日志轮转策略:配置合理的日志保留策略

十、常见问题解决

10.1 日志丢失

  • 确保程序退出时调用Log.CloseAndFlush()
  • 检查Sink配置是否有权限问题
  • 使用同步Sink或增加缓冲时间

10.2 性能问题

  • 启用异步写入
  • 减少不必要的Enricher
  • 提高最小日志级别

10.3 Seq连接问题

// 添加重试策略
.WriteTo.Seq("http://localhost:5341",
    period: TimeSpan.FromSeconds(5),
    batchPostingLimit: 100,
    bufferBaseFilename: "logs/seq-buffer")

十一、扩展阅读

通过本指南,您应该已经掌握了Serilog的核心用法和高级配置技巧。Serilog的强大之处在于其灵活性和可扩展性,建议根据项目需求选择合适的Sinks和Enrichers组合,构建最适合您应用的日志系统。


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注