如何在基类中创建含自定义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
相关产品推荐
相关产品推荐

