Serilog 访问传递给日志语句的 LogEvent 中的原始对象

Serilog access original object in LogEvent that was passed to logging statement

我在 Unity3D 中使用 Serilog。 我有一个简单的接收器,它将 Serilog 日志记录语句写入 Unity 的 Debug.LogFormat

public class UnityLogEventSink : ILogEventSink
{
    public void Emit(LogEvent logEvent)
    {
        // QUESTION: How to pass a UnityEngine.Object to Serilog logging statement such that it is available here?
        UnityEngine.Object contextObject = null;
            
        using (StringWriter stringBuffer = new StringWriter())
        {
            GetTextFormatter().Format(logEvent, stringBuffer);
            LogType logType = GetUnityLogType(logEvent);
            string logString = stringBuffer.ToString().Trim();
            Debug.LogFormat(logType, LogOption.NoStacktrace, contextObject, logString);
        }
    }
    
    // GetTextFormatter, GetUnityLogType etc. are defined here ...
}

现在我想将一个游戏对象传递给 Serilog 日志记录语句,这样我就可以在我的接收器中访问这个游戏对象。 (使用 GameObject 调用 Debug.LogFormat 将在单击日志消息时在 Unity 编辑器中突出显示该对象。我想要那个。)

// Example what I have in mind (not working):
logger.ForContext("unityObject", gameObject).Information("This is an info with context");

我尝试将 GameObject 包装在 ScalarValue 和自定义 LogEventPropertyValue 中,但 GameObject 仍被转换为字符串(发生在 Serilog 的 PropertyValueConverter.cs 中)。

我需要 Debug.LogFormat 的原始 GameObject 实例。 有没有办法保留 GameObject 引用以便我可以在我的接收器中使用它?

作为解决方法,我可以将引用存储在静态地图中,并使用地图的键记录一个字符串 属性。这样我就可以稍后在接收器中从该地图中获取实例。但这是围绕 Serilog 工作的。 有没有更好的利用 Serilog 的解决方案?

Nick 在 https://github.com/serilog/serilog/issues/1124

中为我回答了这个问题
public class ScalarValueEnricher : ILogEventEnricher
{
    protected readonly LogEventProperty _prop;

    public ScalarValueEnricher(string name, object value)
    {
        _prop = new LogEventProperty(name, new ScalarValue(value));
    }

    public void Enrich(LogEvent evt, ILogEventPropertyFactory _) =>
         evt.AddPropertyIfAbsent(_prop);
}

(Here it is in context, in F#)

也可以创建一个 Unity 特定的子类:

public class UnityObjectEnricher : ScalarValueEnricher
{
    public static readonly string unityObjectPropertyName = "unityObject";
    public UnityObjectEnricher(UnityEngine.Object value)
        : base(unityObjectPropertyName, value)
    {
    }
}

然后可以在接收器中访问此 属性:

private UnityEngine.Object GetUnityEngineContextObject(LogEvent logEvent)
{
    if (logEvent.Properties.TryGetValue(UnityObjectEnricher.unityObjectPropertyName, out LogEventPropertyValue logEventPropertyValue))
    {
        if (logEventPropertyValue is ScalarValue scalarValue)
            return scalarValue.Value as UnityEngine.Object;
    }
    return null;
}

像这样使用它:

// Note: using LogContext requires Serilog configuration ".Enrich.FromLogContext()"
using (LogContext.Push(new UnityObjectEnricher(gameObject)))
{
    logger.Information("This is an info with context");
}

或者:

logger.ForContext(new UnityObjectEnricher(gameObject)).Information("This is another info with context");