You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 04:57:02