I am using Serilog in Unity3D.
I have a simple sink that writes Serilog logging statements to Debug.LogFormat
of Unity:
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 ...
}
Now I want to pass a GameObject to the Serilog logging statement, such that I can access this GameObject in my sink. (Calling Debug.LogFormat
with a GameObject will highlight the object in the Unity Editor when the log message is clicked. I want that.)
// Example what I have in mind (not working):
logger.ForContext("unityObject", gameObject).Information("This is an info with context");
I tried to wrap the GameObject in a ScalarValue, and a custom LogEventPropertyValue, but the GameObject is still converted to a string (happens in Serilog's PropertyValueConverter.cs).
I need the original GameObject instance for Debug.LogFormat. Is there a way to preserve the GameObject reference so I can use it in my sink?
As a workaround, I could store the reference in a static map and log a string property with the key for the map. This way I could fetch the instance from that map later in the sink. But this is working around Serilog. Is there a better solution that utilizes Serilog?
Nick answered this very question for me in 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#)
Could also create a Unity specific subclass:
public class UnityObjectEnricher : ScalarValueEnricher
{
public static readonly string unityObjectPropertyName = "unityObject";
public UnityObjectEnricher(UnityEngine.Object value)
: base(unityObjectPropertyName, value)
{
}
}
This property can then be accessed in the sink:
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;
}
Use it like:
// Note: using LogContext requires Serilog configuration ".Enrich.FromLogContext()"
using (LogContext.Push(new UnityObjectEnricher(gameObject)))
{
logger.Information("This is an info with context");
}
Or:
logger.ForContext(new UnityObjectEnricher(gameObject)).Information("This is another info with context");