Azure Functions v2中添加CorrelationId至日志上下文的问题
你碰到的这个问题其实是Azure Functions运行时的日志时机导致的——函数的启动日志(比如Executing 'func1' (Reason='New ServiceBus message detected', Id=xxxx))和结束日志(比如Executed 'func1' (Succeeded, Id=xxxx))是由Functions运行时在你的业务代码执行前、后自动生成的,这时候你在函数体内设置的CorrelationContext还没生效,自然不会出现在这些系统级日志里。
下面是几种针对性的解决办法,按推荐程度排序:
1. 使用自定义Functions中间件(最优解)
从Azure Functions v4开始,隔离进程模式支持自定义中间件,你可以通过中间件在运行时记录日志之前提前提取并设置CorrelationId,这样所有日志(包括系统启动/结束日志)都会带上这个标识。
实现步骤:
首先创建一个中间件类,实现IFunctionsWorkerMiddleware接口:
public class CorrelationIdMiddleware : IFunctionsWorkerMiddleware { private readonly ICorrelationContextAccessor _correlationContextAccessor; private readonly ICorrelationContextFactory _correlationContextFactory; public CorrelationIdMiddleware(ICorrelationContextAccessor correlationContextAccessor, ICorrelationContextFactory correlationContextFactory) { _correlationContextAccessor = correlationContextAccessor; _correlationContextFactory = correlationContextFactory; } public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) { // 从ServiceBus消息中提取CorrelationId,没有则生成一个默认值 if (context.BindingContext.BindingData.TryGetValue("Message", out var messageObj) && messageObj is Message message) { var correlationId = message.CorrelationId ?? Guid.NewGuid().ToString(); // 设置CorrelationContext _correlationContextAccessor.CorrelationContext = _correlationContextFactory.Create(correlationId, "X-Correlation-ID"); // 将CorrelationId存入上下文,方便后续日志直接引用 context.Items["CorrelationId"] = correlationId; } // 继续执行后续中间件和函数逻辑 await next(context); } }
然后在Program.cs中注册这个中间件:
var host = new HostBuilder() .ConfigureFunctionsWorkerDefaults(builder => { // 添加自定义CorrelationId中间件 builder.UseMiddleware<CorrelationIdMiddleware>(); }) .ConfigureServices(services => { // 注册你的Correlation相关依赖 services.AddScoped<ICorrelationContextAccessor, CorrelationContextAccessor>(); services.AddScoped<ICorrelationContextFactory, CorrelationContextFactory>(); }) .Build(); host.Run();
这样中间件会在运行时记录启动日志前执行,提前完成CorrelationId的设置,后续所有日志(包括系统级的启动/结束日志)都会自动带上这个标识。
2. 自定义日志格式化器(备选方案)
如果不想用中间件,也可以通过自定义日志 enricher(比如用Serilog)来自动为所有日志条目添加CorrelationId,但要注意:这种方式对运行时在你设置CorrelationId之前生成的日志,可能还是拿不到值,所以可靠性不如中间件。
示例代码(Serilog enricher):
public class CorrelationIdEnricher : ILogEventEnricher { private readonly ICorrelationContextAccessor _correlationContextAccessor; public CorrelationIdEnricher(ICorrelationContextAccessor correlationContextAccessor) { _correlationContextAccessor = correlationContextAccessor; } public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) { var correlationId = _correlationContextAccessor.CorrelationContext?.CorrelationId ?? "N/A"; logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("CorrelationId", correlationId)); } }
之后在Serilog配置中添加这个enricher即可,但需要确保你的日志框架能正确关联到CorrelationContext。
3. 手动补充启动/结束日志(兜底方案)
如果上面两种方法都不适用,你可以在函数体内手动记录带CorrelationId的启动/结束日志,同时通过配置过滤掉系统默认的启动/结束日志。
示例代码:
[FunctionName("func1")] public async Task Run([ServiceBusTrigger("mytopic", "MySubscription", Connection = "ServiceBusConnectionString")]Message message) { var correlationId = message.CorrelationId ?? Guid.NewGuid().ToString(); _correlationContextAccessor.CorrelationContext = _correlationContextFactory.Create(correlationId, "X-Correlation-ID"); // 手动记录启动日志 _logger.LogInformation("Starting execution of 'func1' | CorrelationId: {CorrelationId}, MessageId: {MessageId}", correlationId, message.MessageId); try { // 你的业务逻辑 _logger.LogInformation($"Processed message: {message.MessageId}, Content: {Encoding.UTF8.GetString(message.Body)}"); } finally { // 手动记录结束日志 _logger.LogInformation("Finished execution of 'func1' | CorrelationId: {CorrelationId}", correlationId); } }
然后在host.json中过滤掉系统默认的启动/结束日志:
{ "logging": { "logLevel": { "Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor": "None" } } }
这种方式虽然能满足需求,但需要额外维护手动日志,不如中间件方案优雅。
内容的提问来源于stack exchange,提问作者Ashkan S

