Azure Functions中Serilog对接App Insights日志关联问题排查
Azure Functions + Serilog + Application Insights 日志关联分组失败排查
已成功配置Azure Functions通过Serilog将日志输出至Application Insights,但日志无法实现正确的关联分组。尝试自定义OperationIdEnricher、OperationTelemetryConverter并完成如下配置后问题仍未解决,请问遗漏了什么?
自定义Enricher代码
public class OperationIdEnrichment : ILogEventEnricher { public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) { logEvent.AddOrUpdateProperty(new LogEventProperty("OperationId", new ScalarValue(Guid.NewGuid()))); logEvent.AddOrUpdateProperty(new LogEventProperty("ParentId", new ScalarValue(Guid.NewGuid()))); } }
自定义TelemetryConverter代码
public class OperationTelemetryConverter : TraceTelemetryConverter { private const string OperationId = "OperationId"; private const string ParentId = "ParentId"; public override IEnumerable<ITelemetry> Convert(LogEvent logEvent, IFormatProvider formatProvider) { foreach (var telemetry in base.Convert(logEvent, formatProvider)) { if (TryGetScalarProperty(logEvent, OperationId, out var operationId)) { telemetry.Context.Operation.Id = operationId.ToString(); } if (TryGetScalarProperty(logEvent, ParentId, out var parentId)) { telemetry.Context.Operation.ParentId = parentId.ToString(); } yield return telemetry; } } private bool TryGetScalarProperty(LogEvent logEvent, string propertyName, out object value) { var hasScalarValue = logEvent.Properties.TryGetValue(propertyName, out var someValue) && (someValue is ScalarValue); value = hasScalarValue ? ((ScalarValue)someValue).Value : default; return hasScalarValue; } }
配置代码
var config = TelemetryConfiguration.Active; Log.Logger = new LoggerConfiguration() .MinimumLevel.Information() .Enrich.FromLogContext() .Enrich.With<OperationIdEnrichment>() .Enrich.WithExceptionDetails() .WriteTo.ApplicationInsights(config, new OperationTelemetryConverter(), LogEventLevel.Information) .CreateLogger(); var host = new HostBuilder() .UseSerilog() .ConfigureFunctionsWorkerDefaults() .ConfigureServices((hostContext, services) => { services.AddLogging(x => x.AddSerilog()); }) .Build(); host.Run();
问题根源与修复方案
核心问题
你的自定义OperationIdEnrichment每次生成全新的Guid作为OperationId和ParentId,完全脱离了Azure Functions请求上下文自带的调用链ID,导致每条日志的关联标识都不唯一,自然无法实现请求级别的日志分组。
1. 修复Enricher:复用请求上下文的OperationId
修改Enricher,从Azure Functions的请求上下文中获取原生的InvocationId(即请求级别的OperationId),而不是生成新Guid:
public class OperationIdEnrichment : ILogEventEnricher { private readonly IHttpContextAccessor _httpContextAccessor; // 通过依赖注入获取HttpContextAccessor,用于访问Function上下文 public OperationIdEnrichment(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) { if (_httpContextAccessor.HttpContext?.Items.TryGetValue("FunctionContext", out var functionContextObj) == true && functionContextObj is FunctionContext functionContext) { // 复用Azure Functions原生的请求ID作为OperationId var operationId = functionContext.InvocationId.ToString(); // 复用HTTP请求的TraceIdentifier作为ParentId(符合调用链规范) var parentId = _httpContextAccessor.HttpContext.TraceIdentifier; logEvent.AddOrUpdateProperty(propertyFactory.CreateProperty("OperationId", operationId)); logEvent.AddOrUpdateProperty(propertyFactory.CreateProperty("ParentId", parentId)); } else { // 非请求场景的降级处理 logEvent.AddOrUpdateProperty(propertyFactory.CreateProperty("OperationId", Guid.NewGuid())); logEvent.AddOrUpdateProperty(propertyFactory.CreateProperty("ParentId", Guid.NewGuid())); } } }
2. 完善依赖注入配置
Isolated模型的Azure Functions默认不会注册IHttpContextAccessor,同时自定义Enricher现在有构造函数依赖,需要在服务容器中注册:
var host = new HostBuilder() .UseSerilog() .ConfigureFunctionsWorkerDefaults() .ConfigureServices((hostContext, services) => { services.AddHttpContextAccessor(); // 注册HttpContextAccessor services.AddSingleton<OperationIdEnrichment>(); // 注册自定义Enricher services.AddLogging(x => x.AddSerilog()); }) .Build();
3. 调整Serilog初始化方式
原来的.Enrich.With<OperationIdEnrichment>()会通过无参构造函数创建实例,现在需要改为从服务容器获取已注入的实例:
var host = new HostBuilder() .ConfigureServices(services => { services.AddHttpContextAccessor(); services.AddSingleton<OperationIdEnrichment>(); }) .UseSerilog((context, services, config) => config .MinimumLevel.Information() .Enrich.FromLogContext() .Enrich.With(services.GetRequiredService<OperationIdEnrichment>()) // 从容器获取Enricher .Enrich.WithExceptionDetails() .WriteTo.ApplicationInsights(TelemetryConfiguration.Active, new OperationTelemetryConverter(), LogEventLevel.Information)) .ConfigureFunctionsWorkerDefaults() .Build();
额外注意事项
- 若使用In-Process模型,需通过
ExecutionContext获取InvocationId,并放入LogContext:public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req, ExecutionContext context, ILogger log) { using (LogContext.PushProperty("OperationId", context.InvocationId)) { log.LogInformation("测试请求日志"); // 业务逻辑 } } - 确保
APPLICATIONINSIGHTS_CONNECTION_STRING已正确配置在local.settings.json或Azure门户的应用设置中。 - 保持Serilog的Application Insights Sink与Azure Functions SDK版本兼容,建议使用最新稳定版。
内容的提问来源于stack exchange,提问作者Dr Schizo
相关产品推荐
相关产品推荐

