【ASP.NET CORE】4.Serilog日志框架搭建

介绍

Serilog 是 .NET 平台中非常流行且强大的结构化日志库,其最大特点是“结构化日志记录(Structured Logging)”,支持通过键值对记录丰富的上下文信息,并且拥有强大的 Sink 插件系统,支持写入控制台、文件、数据库、Elasticsearch、Seq 等。

实现

环境EnvironmentType输出目标写入方式保留策略
开发0Console + File同步即时写入7 天
生产1File Only异步缓冲(10K 队列)30 天

使用 AppContext.BaseDirectory 锁定 DLL 所在目录,解决 dotnet run 与直接运行 EXE 时路径不一致的问题。

Common 类库封装(SerilogExtensions.cs)

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Events;
using Serilog.Exceptions;
using Serilog.Formatting.Compact;

namespace HQ.Common.Logging;

public static class SerilogExtensions
{
    /// <summary>
    /// 环境类型:0=开发,1=生产
    /// </summary>
    public static int EnvironmentType { get; private set; } = 0;

    public static IHostBuilder UseCommonSerilog(this IHostBuilder hostBuilder)
    {
        // Bootstrap Logger(启动错误捕获)
        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Warning()
            .WriteTo.Console(new CompactJsonFormatter())
            .CreateBootstrapLogger();

        return hostBuilder.UseSerilog((context, services, config) =>
        {
            var configuration = context.Configuration;
            
            // 读取环境类型:0=开发,1=生产
            EnvironmentType = configuration.GetValue<int>("EnvironmentType");
            var isProduction = EnvironmentType == 1;
            
            // 强制使用 DLL 所在目录(bin/Debug/net9.0/ 或 bin/Release/net9.0/)
            var baseDir = AppContext.BaseDirectory;
            var logDir = Path.Combine(baseDir, "logs");
            
            // 确保日志目录存在
            if (!Directory.Exists(logDir))
            {
                Directory.CreateDirectory(logDir);
            }
            
            // 完整日志路径
            var logPath = Path.Combine(logDir, "log-.json");
            
            // 记录日志路径(调试用,首次启动时可见)
            Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 日志输出路径: {logPath}");
            
            // 配置最小级别
            if (isProduction)
            {
                config.MinimumLevel.Information();
                config.MinimumLevel.Override("Microsoft", LogEventLevel.Warning);
                config.MinimumLevel.Override("System", LogEventLevel.Warning);
                config.MinimumLevel.Override("Microsoft.AspNetCore.Hosting.Diagnostics", LogEventLevel.Error);
                config.MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning);
            }
            else
            {
                config.MinimumLevel.Debug();
                config.MinimumLevel.Override("Microsoft", LogEventLevel.Information);
                config.MinimumLevel.Override("System", LogEventLevel.Information);
            }

            // 配置输出目标
            if (isProduction)
            {
                // 生产环境
                config.WriteTo.Async(a => 
                    a.File(
                        new CompactJsonFormatter(),
                        logPath,
                        rollingInterval: RollingInterval.Day,
                        retainedFileCountLimit: 30,        // 保留30天
                        fileSizeLimitBytes: 104857600,     // 100MB
                        buffered: true,                    // 启用缓冲
                        flushToDiskInterval: TimeSpan.FromSeconds(5) // 5秒刷盘
                    ),
                    bufferSize: 10000,      // 异步队列大小
                    blockWhenFull: false    // 队列满时丢弃,不阻塞
                );
            }
            else
            {
                // 开发环境:控制台 + 文件
                config.WriteTo.Console(new CompactJsonFormatter());
                config.WriteTo.File(
                    new CompactJsonFormatter(),
                    logPath,
                    rollingInterval: RollingInterval.Day,
                    retainedFileCountLimit: 7,         // 保留7天
                    fileSizeLimitBytes: 104857600      // 100MB
                );
            }
            
            // 增强信息
            config.Enrich.FromLogContext();
            config.Enrich.WithMachineName();
            config.Enrich.WithThreadId();
            config.Enrich.WithExceptionDetails();
            config.Enrich.WithProperty("EnvironmentType", isProduction ? "Production(1)" : "Development(0)");
            
        }, preserveStaticLogger: true);
    }

    public static IApplicationBuilder UseCommonSerilogRequestLogging(this IApplicationBuilder app) =>
        app.UseSerilogRequestLogging(options =>
        {
            options.MessageTemplate = 
                "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms";
            options.GetLevel = (ctx, elapsed, ex) =>
                ex != null || ctx.Response.StatusCode > 499
                    ? LogEventLevel.Error
                    : LogEventLevel.Information;
        });
    
    public static bool IsProductionEnvironment() => EnvironmentType == 1;
    public static bool IsDevelopmentEnvironment() => EnvironmentType == 0;
}

配置文件

{
  "EnvironmentType": 0,
  
  "ConnectionStrings": {
    "Default": "Server=localhost;Database=HQDev;User ID=sa;Password=;Encrypt=True;TrustServerCertificate=True;MultipleActiveResultSets=True;Max Pool Size=1000"
  },
  
  "AllowedHosts": "*"
}
using HQ.Common.DI;
using HQ.Common.Logging;
using HQ.Common.ORM.SQLSugar;
using HQ.Service.Base;
using HQ.Service.Interface;
using Serilog;
using SqlSugar;

var builder = WebApplication.CreateBuilder(args);

// 初始化 Serilog
builder.Host.UseCommonSerilog();

// 注册 SQLSugar 客户端(单例)
builder.Services.AddSingleton<ISqlSugarClient>(sp =>
{
    var connectionString = builder.Configuration.GetConnectionString("Default");
    return new SqlSugarClient(new ConnectionConfig
    {
        ConnectionString = connectionString,
        DbType = DbType.SqlServer,
        IsAutoCloseConnection = true,
        InitKeyType = InitKeyType.Attribute,
        MoreSettings = new ConnMoreSettings
        {
            SqlServerCodeFirstNvarchar = true
        }
    });
});

// 注册仓储和服务
builder.Services.AddScoped(typeof(IBaseRepository<,>), typeof(BaseRepository<,>));
builder.Services.AddHQServices(typeof(IServiceMarker).Assembly);

// 添加控制器
builder.Services.AddControllers();

// 添加 Swagger
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// 数据库预热(使用独立 scope)
try
{
    using (var scope = app.Services.CreateScope())
    {
        var db = scope.ServiceProvider.GetRequiredService<ISqlSugarClient>();
        var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();
        
        int retryCount = 0;
        const int maxRetries = 3;
        
        while (retryCount < maxRetries)
        {
            try
            {
                await db.Ado.GetScalarAsync("SELECT 1");
                logger.LogInformation("数据库连接预热成功");
                break;
            }
            catch (Exception ex)
            {
                retryCount++;
                if (retryCount >= maxRetries)
                {
                    logger.LogError(ex, "数据库连接预热失败,已达到最大重试次数");
                    throw;
                }
                
                logger.LogWarning(ex, "数据库连接预热失败,{RetryCount}秒后第{RetryNumber}次重试...", retryCount, retryCount + 1);
                await Task.Delay(TimeSpan.FromSeconds(retryCount));
            }
        }
    }
}
catch (Exception ex)
{
    Log.Fatal(ex, "数据库连接初始化失败,应用程序无法启动");
    throw;
}

// 请求日志中间件(放在管道最前面以捕获所有请求)
app.UseCommonSerilogRequestLogging();

// 根据 EnvironmentType(0=开发,1=生产)配置中间件
if (SerilogExtensions.IsDevelopmentEnvironment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
    Log.Information("当前运行环境: 开发环境 (EnvironmentType=0)");
}
else if (SerilogExtensions.IsProductionEnvironment())
{
    app.UseExceptionHandler("/error");
    app.UseHsts();
    Log.Information("当前运行环境: 生产环境 (EnvironmentType=1)");
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

// 应用程序生命周期管理
try
{
    Log.Information("应用程序启动成功,环境类型: {EnvironmentType},访问地址: {Urls}", 
        SerilogExtensions.EnvironmentType, 
        string.Join(", ", builder.WebHost.GetSetting("urls") ?? "默认"));
    
    await app.RunAsync();
}
catch (Exception ex)
{
    Log.Fatal(ex, "应用程序异常终止");
}
finally
{
    Log.CloseAndFlush();
}

使用

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

    public async Task CreateOrder(CreateOrderRequest request)
    {
        // 结构化日志(属性可被检索)
        _logger.LogInformation("创建订单 {@Request}", request);
        
        try
        {
            // 业务逻辑...
            _logger.LogInformation("订单创建成功 {OrderId} {Amount}", order.Id, order.Amount);
        }
        catch (Exception ex)
        {
            // 错误日志必须包含异常对象
            _logger.LogError(ex, "创建订单失败 {@Request}", request);
            throw;
        }
    }
}
© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容