首页 > 解决方案 > Serilog 访问传递给日志记录语句的 LogEvent 中的原始对象

问题描述

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

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 日志记录语句,这样我就可以在我的接收器中访问这个游戏对象。(Debug.LogFormat当单击日志消息时,使用 GameObject 调用将在 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 的解决方案?

标签: c#unity3dserilog

解决方案


尼克在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);
}

这是在上下文中,在 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");

推荐阅读