介绍
Serilog 是 .NET 平台中非常流行且强大的结构化日志库,其最大特点是“结构化日志记录(Structured Logging)”,支持通过键值对记录丰富的上下文信息,并且拥有强大的 Sink 插件系统,支持写入控制台、文件、数据库、Elasticsearch、Seq 等。
实现
| 环境 | EnvironmentType | 输出目标 | 写入方式 | 保留策略 |
|---|---|---|---|---|
| 开发 | 0 | Console + File | 同步即时写入 | 7 天 |
| 生产 | 1 | File 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









暂无评论内容