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

多租户ASP.NET Core应用中OpenTelemetry租户标识注入方案问询

多租户Web API通过OpenTelemetry给Azure Application Insights注入租户ID

针对你的场景,核心思路是在自定义中间件拿到租户ID后,把它注入到OpenTelemetry的追踪、指标、日志上下文里,确保所有上报到Application Insights的数据都带上这个标识。下面分三类数据给出具体实现方案:

1. 追踪(Traces)注入租户ID

自定义中间件里拿到租户ID后,直接给当前的Activity(OpenTelemetry的追踪单元)添加标签,同时把租户ID存在HttpContext里方便后续复用:

public class TenantMiddleware
{
    private readonly RequestDelegate _next;

    public TenantMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // 替换成你实际获取租户ID的逻辑(比如请求头、路由参数)
        var tenantId = context.Request.Headers["X-Tenant-Id"].FirstOrDefault() ?? "unknown-tenant";

        // 给当前追踪Span添加租户ID标签
        Activity.Current?.SetTag("tenant.id", tenantId);

        // 存入HttpContext,供后续指标、日志使用
        context.Items["TenantId"] = tenantId;

        await _next(context);
    }
}

另外,也可以在OpenTelemetry的AspNetCore追踪配置里加Enrich逻辑,兜底确保所有Web请求的追踪都带上租户ID:

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation(options =>
        {
            options.Enrich = (activity, _, rawObject) =>
            {
                if (rawObject is HttpContext httpContext && 
                    httpContext.Items.TryGetValue("TenantId", out var tenantId))
                {
                    activity.SetTag("tenant.id", tenantId.ToString());
                }
            };
        })
        .AddAzureMonitorTraceExporter(options =>
        {
            options.ConnectionString = builder.Configuration["ApplicationInsights:ConnectionString"];
        }));

2. 指标(Metrics)注入租户ID

指标需要通过**维度(Dimension)**来携带租户ID,同样可以通过AspNetCore指标的Enrich逻辑自动添加,或者自定义指标时手动带上:

自动给Web请求指标加租户维度

builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics => metrics
        .AddAspNetCoreInstrumentation(options =>
        {
            options.Enrich = (_, metricTags, rawObject) =>
            {
                if (rawObject is HttpContext httpContext && 
                    httpContext.Items.TryGetValue("TenantId", out var tenantId))
                {
                    metricTags.Add("tenant.id", tenantId.ToString());
                }
            };
        })
        .AddAzureMonitorMetricExporter(options =>
        {
            options.ConnectionString = builder.Configuration["ApplicationInsights:ConnectionString"];
        }));

自定义指标手动加租户维度

如果是你自己定义的业务指标,在上报时直接带上租户ID标签:

private readonly Meter _appMeter = new Meter("MyApp.BusinessMetrics");
private readonly Counter<int> _orderCreatedCounter;
private readonly IHttpContextAccessor _httpContextAccessor;

public OrderService(IHttpContextAccessor httpContextAccessor)
{
    _orderCreatedCounter = _appMeter.CreateCounter<int>("orders.created.count");
    _httpContextAccessor = httpContextAccessor;
}

public void RecordOrderCreated()
{
    if (_httpContextAccessor.HttpContext?.Items.TryGetValue("TenantId", out var tenantId) == true)
    {
        _orderCreatedCounter.Add(1, new KeyValuePair<string, object>("tenant.id", tenantId.ToString()));
    }
}

3. 日志(Logs)注入租户ID

日志的处理分两种情况,看你用的是原生日志还是Serilog:

原生日志 + OpenTelemetry处理器

自定义一个日志处理器,从HttpContext里取租户ID并添加到日志标签:

public class TenantLogProcessor : BaseProcessor<LogRecord>
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public TenantLogProcessor(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public override void OnEnd(LogRecord logRecord)
    {
        if (_httpContextAccessor.HttpContext?.Items.TryGetValue("TenantId", out var tenantId) == true)
        {
            logRecord.AddOrUpdateTag("tenant.id", tenantId.ToString());
        }
        base.OnEnd(logRecord);
    }
}

然后注册到OpenTelemetry日志配置里:

builder.Services.AddHttpContextAccessor(); // 先注册HttpContextAccessor
builder.Services.AddOpenTelemetry()
    .WithLogging(logging => logging
        .AddAzureMonitorLogExporter(options =>
        {
            options.ConnectionString = builder.Configuration["ApplicationInsights:ConnectionString"];
        })
        .AddProcessor<TenantLogProcessor>());

Serilog 方案

如果项目用Serilog,直接通过Enrich功能给所有日志加租户ID:

Log.Logger = new LoggerConfiguration()
    .Enrich.FromLogContext()
    .Enrich.WithProperty("tenant.id", (IHttpContextAccessor accessor) => 
        accessor.HttpContext?.Items["TenantId"] ?? "unknown-tenant")
    .WriteTo.OpenTelemetry(options =>
    {
        options.Endpoint = "https://dc.services.visualstudio.com/v2/track";
        options.ResourceAttributes = new Dictionary<string, object>
        {
            { "connectionString", builder.Configuration["ApplicationInsights:ConnectionString"] }
        };
    })
    .CreateLogger();

记得注册IHttpContextAccessor:

builder.Services.AddHttpContextAccessor();

验证数据

部署后,你可以在Azure Application Insights的Logs页面用Kusto查询验证:

// 查询带租户ID的追踪
traces
| where customDimensions["tenant.id"] == "your-test-tenant"
| take 10

// 查询带租户ID的日志
union traces, logs
| where customDimensions["tenant.id"] == "your-test-tenant"
| take 10

或者在Metrics页面筛选维度tenant.id,确认指标数据是否按租户区分。

内容的提问来源于stack exchange,提问作者nen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 02:52:08