多租户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
相关产品推荐
相关产品推荐

