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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 22:36:38