代码之家  ›  专栏  ›  技术社区  ›  MelnikovI

在进程之间传递Serilog LogContext

  •  2
  • MelnikovI  · 技术社区  · 8 年前

    是否可以在 Serilog 从中获取所有属性 LogContext ?做 日志上下文 是否支持序列化/反序列化以在进程之间传递上下文?

    1 回复  |  直到 8 年前
        1
  •  3
  •   CodeFuller    8 年前

    没有防弹的方法通过 LogContext 进程之间的状态。然而,有一种解决方案在大多数情况下都能奏效(答案底部列出了限制)。

    日志上下文 是一个静态类,公开了以下方法:

    public static class LogContext
    {
        public static ILogEventEnricher Clone();
        public static IDisposable Push(ILogEventEnricher enricher);
        public static IDisposable Push(params ILogEventEnricher[] enrichers);
        [Obsolete("Please use `LogContext.Push(properties)` instead.")]
        public static IDisposable PushProperties(params ILogEventEnricher[] properties);
        public static IDisposable PushProperty(string name, object value, bool destructureObjects = false);
    }
    

    一对 Clone() Push(ILogEventEnricher enricher) 方法看起来很有前途,但如何传递 ILogEventEnricher 进程之间?

    让我们深入研究一下 LogContext source code 。首先,我们看到 Push 变更更改私有 Enrichers 的属性 ImmutableStack<ILogEventEnricher> 通过添加的新实例键入 ILogEventEnricher公司 。最常用的方法 PushProperty(string name, object value, bool destructureObjects = false) 添加的实例 PropertyEnricher :

    public static IDisposable PushProperty(string name, object value, bool destructureObjects = false)
    {
        return Push(new PropertyEnricher(name, value, destructureObjects));
    }
    

    克隆() 只需返回包裹在 SafeAggregateEnricher :

    public static ILogEventEnricher Clone()
    {
        var stack = GetOrCreateEnricherStack();
        return new SafeAggregateEnricher(stack);
    }
    

    这样我们就可以通过 日志上下文 通过提取enricher中存储的值返回 克隆() 方法 ILogEventEnricher公司 有唯一的方法:

    public interface ILogEventEnricher
    {
        void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory);
    }
    

    Enrichers影响 LogEvent 通过添加的实例 LogEventPropertyValue 至its Properties 词典不幸的是,没有简单的方法保存事件属性对象的状态,因为 LogEventPropertyValue 是一个抽象类,其子类如下 ScalarValue ,则, DictionaryValue

    但是,我们可以使用的自定义实现 ILogEventPropertyFactory 这将收集所有创建的属性,并公开它们以便在进程之间传输。缺点是并非所有Enricher都使用 propertyFactory 。其中一些直接创建属性,例如 ThreadIdEnricher :

    public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
    {
        logEvent.AddPropertyIfAbsent(new LogEventProperty(ThreadIdPropertyName, new ScalarValue(Environment.CurrentManagedThreadId)));
    }
    

    然而 PropertyEnricher 对于我们的案例来说,最有趣的是使用工厂:

    public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
    {
        if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
        if (propertyFactory == null) throw new ArgumentNullException(nameof(propertyFactory));
        var property = propertyFactory.CreateProperty(_name, _value, _destructureObjects);
        logEvent.AddPropertyIfAbsent(property);
    }
    

    现在,计划应该明确:

    1. 创建的自定义实现 ILogEventPropertyFactory 收集所有创建的属性。
    2. 克隆 日志上下文 获取聚合浓缩器。
    3. 呼叫 Enrich 最终将调用 CreateProperty 在我们的工厂中 日志上下文
    4. 序列化接收的属性并在进程之间传递。
    5. 反序列化另一端的属性并填充上下文。

    以下是实现这些步骤的代码:

    PropertyValue类:

    public class PropertyValue
    {
        public string Name { get; set; }
    
        public object Value { get; set; }
    
        public bool DestructureObjects { get; set; }
    
        public PropertyValue(string name, object value, bool destructureObjects)
        {
            Name = name;
            Value = value;
            DestructureObjects = destructureObjects;
        }
    }
    

    LogContextDump类:

    public class LogContextDump
    {
        public ICollection<PropertyValue> Properties { get; set; }
    
        public LogContextDump(IEnumerable<PropertyValue> properties)
        {
            Properties = new Collection<PropertyValue>(properties.ToList());
        }
    
        public IDisposable PopulateLogContext()
        {
            return LogContext.Push(Properties.Select(p => new PropertyEnricher(p.Name, p.Value, p.DestructureObjects) as ILogEventEnricher).ToArray());
        }
    }
    

    CaptureLogEventPropertyFactory类:

    public class CaptureLogEventPropertyFactory : ILogEventPropertyFactory
    {
        private readonly List<PropertyValue> values = new List<PropertyValue>();
    
        public LogEventProperty CreateProperty(string name, object value, bool destructureObjects = false)
        {
            values.Add(new PropertyValue(name, value, destructureObjects));
            return new LogEventProperty(name, new ScalarValue(value));
        }
    
        public LogContextDump Dump()
        {
            return new LogContextDump((values as IEnumerable<PropertyValue>).Reverse());
        }
    }
    

    LogContextSerializer类:

    public static class LogContextSerializer
    {
        public static LogContextDump Serialize()
        {
            var logContextEnricher = LogContext.Clone();
            var captureFactory = new CaptureLogEventPropertyFactory();
            logContextEnricher.Enrich(new LogEvent(DateTimeOffset.Now, LogEventLevel.Verbose, null, MessageTemplate.Empty, Enumerable.Empty<LogEventProperty>()), captureFactory);
    
            return captureFactory.Dump();
        }
    
        public static IDisposable Deserialize(LogContextDump contextDump)
        {
            return contextDump.PopulateLogContext();
        }
    }
    

    使用示例:

    string jsonData;
    using (LogContext.PushProperty("property1", "SomeValue"))
    using (LogContext.PushProperty("property2", 123))
    {
        var dump = LogContextSerializer.Serialize();
        jsonData = JsonConvert.SerializeObject(dump);
    }
    
    //  Pass jsonData between processes
    
    var restoredDump = JsonConvert.DeserializeObject<LogContextDump>(jsonData);
    using (LogContextSerializer.Deserialize(restoredDump))
    {
        //  LogContext is the same as when Clone() was called above
    }
    

    我在这里使用了JSON的序列化,但是使用了以下基本类型 LogContextDump PropertyValue 您可以使用任何想要的序列化机制。

    正如我已经说过的,这种解决方案有其缺点:

    1. 恢复 日志上下文 与原来的不完全相同。起初的 日志上下文 可以有不同类型的丰富器,但恢复的上下文将只有 房地产经纪人 。但是,如果您使用 日志上下文 作为一个简单的袋子,具有上述样品中的特性。

    2. 如果某些上下文丰富器直接绕过 物业工厂

    3. 如果某些添加的值具有无法序列化的类型,则此解决方案将不起作用。 Value 上述中的属性 PropertyValue属性值 具有类型 object 。您可以将任何类型的属性添加到 日志上下文 但您应该有一种方法来序列化它们的数据,以便在进程之间进行传递。上面对JSON的序列化/反序列化将适用于简单类型,但如果向 日志上下文