.NET Core 5多租户应用如何配置Serilog实现日志写入对应租户库
实现方案
完全可以实现,核心思路是从请求上下文提取租户标识,动态路由日志到对应租户的数据库sink,以下是两种成熟的落地方式:
方案1:使用Serilog.Sinks.Map实现动态路由(推荐)
Serilog官方生态提供的Serilog.Sinks.Map包专门用于按日志属性动态分发日志到不同的sink,实现成本最低。
- 第一步:安装依赖包
除了基础的Serilog.AspNetCore、对应数据库的sink包(比如SQL Server用Serilog.Sinks.MSSqlServer)外,额外安装Serilog.Sinks.Map - 第二步:配置Serilog动态路由规则
Log.Logger = new LoggerConfiguration() // 开启日志上下文属性支持 .Enrich.FromLogContext() // 按TenantKey属性分发日志,未知租户默认走Unknown分支 .WriteTo.Map("TenantKey", "Unknown", (tenantKey, writeTo) => { // 从通用配置库查询当前租户的连接串,建议加内存缓存避免频繁查库 var connStr = GetTenantDbConnStrFromConfigDb(tenantKey); // 写入对应租户的数据库,参数按自己的需求调整 writeTo.MSSqlServer( connectionString: connStr, tableName: "Logs", autoCreateSqlTable: true ); }) .CreateLogger();
- 第三步:添加中间件注入租户属性
在认证流程之后插入中间件,从JWT中提取租户Key注入日志上下文:
public class TenantLogMiddleware { private readonly RequestDelegate _next; public TenantLogMiddleware(RequestDelegate next) => _next = next; public async Task InvokeAsync(HttpContext context) { if (context.User.Identity.IsAuthenticated) { // 替换为你JWT中实际存储租户Key的Claim类型 var tenantKey = context.User.Claims.FirstOrDefault(c => c.Type == "TenantKey")?.Value; if (!string.IsNullOrEmpty(tenantKey)) { using (LogContext.PushProperty("TenantKey", tenantKey)) { await _next(context); } return; } } // 无租户信息的请求走默认逻辑 await _next(context); } } // Startup/Program中注册中间件,放在UseAuthentication之后、UseAuthorization之前 app.UseAuthentication(); app.UseMiddleware<TenantLogMiddleware>(); app.UseAuthorization();
方案2:自定义Serilog Sink
如果有特殊的日志写入逻辑(比如自定义字段、批量写入),可以自己实现ISink接口:
public class TenantLogSink : ILogEventSink { private readonly IHttpContextAccessor _httpContextAccessor; public TenantLogSink(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public void Emit(LogEvent logEvent) { var context = _httpContextAccessor.HttpContext; if (context?.User.Identity.IsAuthenticated == true) { var tenantKey = context.User.Claims.FirstOrDefault(c => c.Type == "TenantKey")?.Value; if (!string.IsNullOrEmpty(tenantKey)) { var connStr = GetTenantConnStr(tenantKey); // 自行实现写入数据库逻辑,比如用Dapper、原生SqlClient操作 WriteLogToTenantDb(connStr, logEvent); return; } } // 无租户信息的日志统一写入公共日志库 WriteLogToPublicDb(logEvent); } }
注意事项
- 租户连接字符串建议加内存缓存,过期时间可设为1~24小时,避免每次写日志都查询通用配置库影响性能
- 如果租户量级过万,可自定义sink的过期清理逻辑,避免Serilog.Sinks.Map缓存过多sink实例导致内存占用过高
- 非请求上下文的日志(比如后台任务、启动日志)没有租户信息,可统一写入公共日志库,或手动指定TenantKey属性写入对应租户库
内容的提问来源于stack exchange,提问作者Den
相关产品推荐
相关产品推荐

