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

如何在基类中创建含自定义Enricher的Logger?需访问子类实例成员

可行的替代方案

好问题!这种既要访问子类实例成员,又得保证Logger只初始化一次的场景确实有点绕。除了你想到的加锁方案,还有几个更优雅的思路可以试试:


方案1:双重检查锁定+标记位(优化版锁方案)

其实你最初的锁思路可以优化得更高效,用双重检查锁定配合标记位,避免每次实例化子类都进入锁逻辑。核心是先判断Logger是否已配置,未配置再进入锁,进入后二次校验(防止多线程竞争),确保仅执行一次初始化:

public abstract class ApplicationBase
{
    // 标记位:记录Logger是否已完成配置
    private static bool _loggerConfigured;
    // 锁对象:确保静态且唯一
    private static readonly object _loggerLock = new object();

    protected ApplicationBase()
    {
        if (!_loggerConfigured)
        {
            lock (_loggerLock)
            {
                // 二次检查:避免多线程同时进入锁时重复初始化
                if (!_loggerConfigured)
                {
                    Log.Logger = new LoggerConfiguration()
                        .Enrich.FromLogContext()
                        .Enrich.WithMachineName()
                        .Enrich.WithThreadId()
                        .Enrich.With(new CustomEnricher(this)) // 传入当前子类实例
                        .MinimumLevel.Debug()
                        .WriteTo.Async(a => a.RollingFile(
                            new RenderedCompactJsonFormatter(),
                            @"c:\logs\log-{Date}.txt",
                            fileSizeLimitBytes: 4194304))
                        .CreateLogger();
                    _loggerConfigured = true;
                }
            }
        }
    }

    // 子类必须实现的方法
    public abstract object GetStoreData();
}

public class CustomEnricher : ILogEventEnricher
{
    private readonly ApplicationBase _appInstance;

    public CustomEnricher(ApplicationBase appInstance)
    {
        _appInstance = appInstance;
    }

    public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
    {
        var storeData = _appInstance.GetStoreData();
        logEvent.AddPropertyIfAbsent(
            propertyFactory.CreateProperty("StoreData", storeData)
        );
    }
}

这个方案逻辑直观,可读性强,通过双重检查大幅减少了锁的竞争开销。


方案2:原子操作+延迟加载Enricher

如果不想用显式锁,可以利用Interlocked.CompareExchange原子操作确保只有第一个子类实例被用于Enricher,同时Logger仍在静态构造函数中初始化,Enricher延迟到第一次日志写入时获取实例数据:

public abstract class ApplicationBase
{
    // 静态字段:存储第一个创建的子类实例
    private static ApplicationBase _firstAppInstance;

    static ApplicationBase()
    {
        Log.Logger = new LoggerConfiguration()
            .Enrich.FromLogContext()
            .Enrich.WithMachineName()
            .Enrich.WithThreadId()
            // 传入获取实例的委托,延迟到日志写入时再获取
            .Enrich.With(new LazyCustomEnricher(() => _firstAppInstance))
            .MinimumLevel.Debug()
            .WriteTo.Async(a => a.RollingFile(
                new RenderedCompactJsonFormatter(),
                @"c:\logs\log-{Date}.txt",
                fileSizeLimitBytes: 4194304))
            .CreateLogger();
    }

    protected ApplicationBase()
    {
        // 原子操作:只有当_firstAppInstance为null时,才将当前实例赋值给它
        // 确保只有第一个子类实例会被记录
        Interlocked.CompareExchange(ref _firstAppInstance, this, null);
    }

    public abstract object GetStoreData();
}

public class LazyCustomEnricher : ILogEventEnricher
{
    private readonly Func<ApplicationBase> _getAppInstance;

    public LazyCustomEnricher(Func<ApplicationBase> getAppInstance)
    {
        _getAppInstance = getAppInstance;
    }

    public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
    {
        var appInstance = _getAppInstance();
        if (appInstance == null) return;

        var storeData = appInstance.GetStoreData();
        logEvent.AddPropertyIfAbsent(
            propertyFactory.CreateProperty("StoreData", storeData)
        );
    }
}

这个方案完全避免了显式锁,利用原子操作保证实例唯一性,同时Logger初始化符合Serilog最佳实践,延迟加载的Enricher完美解决了静态构造函数无法访问实例成员的问题。


方案对比

  • 如果追求逻辑简单、易维护,方案1更合适;
  • 如果想避免显式锁、追求更高性能,方案2是更好的选择。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 06:47:37