如何为Azure Function日志自动添加Correlation ID(避免IHttpContextAccessor)
为Azure Functions自动在日志中添加Correlation ID
场景描述
所有Azure Function均为HttpTrigger类型,调用方会在请求中携带correlation-id请求头。不想在每个自定义服务类中注入IHttpContextAccessor手动获取并拼接该ID到日志,希望直接调用日志方法时自动带上Correlation ID:
原写法(需手动拼接):
logger.LogInformation($"{correlationId}: Saving some entity to DB"); // 输出:7eec51f0-4046-4398-b044-45ecf905cd81: Saving some entity to DB期望写法(自动注入ID):
customLogger.LogInformation("Saving some entity to DB"); // 输出:7eec51f0-4046-4398-b044-45ecf905cd81: Saving some entity to DB
之前尝试自定义日志提供程序时遇到与默认.NET ILogger<>兼容的问题,以下是可行的解决方案:
解决方案:包装默认ILogger实现
无需从零实现完整的日志提供程序,只需包装默认的ILogger<T>,在日志输出前自动注入Correlation ID。
1. 实现日志包装器类
根据Azure Functions的运行模式(In-Process/隔离模式)选择对应实现:
针对In-Process模式
public class CorrelationIdLogger<T> : ILogger<T> { private readonly ILogger<T> _innerLogger; private readonly IHttpContextAccessor _httpContextAccessor; public CorrelationIdLogger(ILogger<T> innerLogger, IHttpContextAccessor httpContextAccessor) { _innerLogger = innerLogger; _httpContextAccessor = httpContextAccessor; } public IDisposable BeginScope<TState>(TState state) => _innerLogger.BeginScope(state); public bool IsEnabled(LogLevel logLevel) => _innerLogger.IsEnabled(logLevel); public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { // 从请求头获取Correlation ID,无值时用默认标识 var correlationId = _httpContextAccessor.HttpContext?.Request.Headers["correlation-id"].FirstOrDefault() ?? "no-correlation-id"; // 拼接日志消息 var formattedMessage = $"{correlationId}: {formatter(state, exception)}"; // 委托给默认日志器输出 _innerLogger.Log(logLevel, eventId, state, exception, (s, e) => formattedMessage); } }
针对隔离(Isolated Worker)模式
隔离模式下无法直接使用IHttpContextAccessor,需改用IFunctionContextAccessor获取请求信息:
public class CorrelationIdLogger<T> : ILogger<T> { private readonly ILogger<T> _innerLogger; private readonly IFunctionContextAccessor _functionContextAccessor; public CorrelationIdLogger(ILogger<T> innerLogger, IFunctionContextAccessor functionContextAccessor) { _innerLogger = innerLogger; _functionContextAccessor = functionContextAccessor; } public IDisposable BeginScope<TState>(TState state) => _innerLogger.BeginScope(state); public bool IsEnabled(LogLevel logLevel) => _innerLogger.IsEnabled(logLevel); public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { var correlationId = "no-correlation-id"; var functionContext = _functionContextAccessor.FunctionContext; if (functionContext?.BindingContext.BindingData.TryGetValue("HttpRequest", out var requestObj) == true) { if (requestObj is HttpRequestData request) { correlationId = request.Headers["correlation-id"].FirstOrDefault() ?? correlationId; } } var formattedMessage = $"{correlationId}: {formatter(state, exception)}"; _innerLogger.Log(logLevel, eventId, state, exception, (s, e) => formattedMessage); } }
2. 注册服务
在服务配置中替换默认的ILogger<T>注册,让依赖注入返回包装后的实例:
In-Process模式(Startup.cs)
public class Startup : FunctionsStartup { public override void Configure(IFunctionsHostBuilder builder) { // 注册HttpContextAccessor builder.Services.AddHttpContextAccessor(); // 替换默认ILogger<T>为自定义包装器 builder.Services.AddTransient(typeof(ILogger<>), typeof(CorrelationIdLogger<>)); } }
隔离模式(Program.cs)
var host = new HostBuilder() .ConfigureFunctionsWorkerDefaults() .ConfigureServices(services => { // 注册FunctionContextAccessor services.AddSingleton<IFunctionContextAccessor, FunctionContextAccessor>(); // 替换默认ILogger<T>为自定义包装器 services.AddTransient(typeof(ILogger<>), typeof(CorrelationIdLogger<>)); }) .Build(); host.Run();
使用方式
在自定义服务中直接注入ILogger<T>即可,无需任何额外修改,日志会自动带上Correlation ID:
public class MyService { private readonly ILogger<MyService> _logger; public MyService(ILogger<MyService> logger) { _logger = logger; } public void SaveEntity() { // 自动添加Correlation ID到日志 _logger.LogInformation("Saving some entity to DB"); } }
内容的提问来源于stack exchange,提问作者osotorrio
相关产品推荐
相关产品推荐

