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

如何从Singleton日志器访问.NET 6的Scoped对象?

解决方案:利用Serilog LogContext传递Scoped上下文数据

你的核心问题在于Serilog格式化器是Singleton生命周期,无法直接访问Scoped服务。解决思路是把Scoped上下文数据提前注入到日志事件的属性中,而非在格式化器里尝试获取Scoped服务。具体步骤如下:

1. 利用LogContext推送上下文数据

在处理队列任务的作用域内,将ICustomContext中的数据推送到Serilog的LogContext(基于AsyncLocal,能跟踪异步上下文):

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        var workItem = await _queue.DequeueAsync(stoppingToken);
        
        // 创建当前任务的作用域
        using var scope = _serviceProvider.CreateScope();
        var customContext = scope.ServiceProvider.GetRequiredService<ICustomContext>();
        
        // 从队列消息或其他来源设置上下文数据
        customContext.CorrelationId = workItem.CorrelationId;
        customContext.TraceId = Guid.NewGuid().ToString();
        
        // 将上下文数据推送到LogContext,自动关联当前异步上下文
        using (LogContext.PushProperty("CorrelationId", customContext.CorrelationId))
        using (LogContext.PushProperty("TraceId", customContext.TraceId))
        {
            try
            {
                await workItem.ProcessAsync(scope.ServiceProvider, stoppingToken);
                _logger.LogInformation("任务处理完成");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "任务处理失败");
            }
        }
    }
}

2. 启用Serilog的LogContext增强

在Serilog配置中开启Enrich.FromLogContext(),让日志事件自动包含LogContext中的属性:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseSerilog((context, services, config) => config
            .ReadFrom.Configuration(context.Configuration)
            .ReadFrom.Services(services)
            .Enrich.FromLogContext() // 启用LogContext属性增强
            .WriteTo.Console(new CustomFormatter())
        )
        .ConfigureServices(services =>
        {
            services.AddScoped<ICustomContext, CustomContext>();
            services.AddHostedService<Worker>();
            // 其他服务注册
        });

3. 修改自定义格式化器,直接读取日志属性

不再依赖IServiceProvider,从LogEvent的Properties中提取上下文数据:

internal class CustomFormatter : ITextFormatter
{
    private readonly ILogProvider _provider;
    static readonly JsonSerializerSettings _jsonOptions = new()
    {
        NullValueHandling = NullValueHandling.Ignore,
        ContractResolver = new CamelCasePropertyNamesContractResolver()
    };

    public CustomFormatter(ILogProvider provider)
    {
        _provider = provider;
    }

    public void Format(LogEvent logEvent, TextWriter output)
    {
        var logObj = _provider.Create(logEvent);
        
        // 从日志事件属性中提取CorrelationId
        if (logEvent.Properties.TryGetValue("CorrelationId", out var correlationId))
        {
            logObj.CorrelationId = correlationId.ToString().Trim('"');
        }
        
        // 同理提取TraceId等其他上下文数据
        if (logEvent.Properties.TryGetValue("TraceId", out var traceId))
        {
            logObj.TraceId = traceId.ToString().Trim('"');
        }

        output.WriteLine(JsonConvert.SerializeObject(logObj, _jsonOptions));
    }
}

优化:自动推送上下文的Scoped服务

如果不想手动写PushProperty,可以封装一个Scoped服务,自动完成上下文推送和清理:

public class CustomContextScope : IDisposable
{
    private readonly IDisposable _logScope;

    public CustomContextScope(ICustomContext customContext)
    {
        // 批量推送上下文属性
        _logScope = LogContext.PushProperties(new Dictionary<string, object>
        {
            ["CorrelationId"] = customContext.CorrelationId,
            ["TraceId"] = customContext.TraceId
        });
    }

    public void Dispose()
    {
        _logScope.Dispose();
    }
}

// 注册为Scoped服务
services.AddScoped<CustomContextScope>();

使用时只需在作用域内获取该服务即可:

using var scope = _serviceProvider.CreateScope();
var customContext = scope.ServiceProvider.GetRequiredService<ICustomContext>();
customContext.CorrelationId = workItem.CorrelationId;
customContext.TraceId = Guid.NewGuid().ToString();

// 自动推送上下文到LogContext
using var _ = scope.ServiceProvider.GetRequiredService<CustomContextScope>();

// 处理任务
await workItem.ProcessAsync(scope.ServiceProvider, stoppingToken);

内容的提问来源于stack exchange,提问作者Kyore

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 14:45:33