Serilog.Sinks.Unity3D
Serilog.Sinks.Unity3D copied to clipboard
Add support for Serilog.Extensions.Logging
Based on the discussion in #9, here's support for using UnityEngine.Object
s with BeginScope()
and using the latest pushed scope as context when sending following log entries to Unity's console window.
This way, log entries are clickable and e.g. GameObject
s get pinged in the editor.
On key design choice from a user's perspective was to avoid additonal APIs or dependencies. User code only needs to reference Microsoft.Extensions.Logging.ILogger
and work with the abstractions. Everything else is done upfront via configuration.
Example
Config:
Serilog.Core.Logger logger = new Serilog.LoggerConfiguration()
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.Unity3D()
.EnableUnityObjectScope() // Important to add!
.CreateLogger();
var loggerFactory = new Microsoft.Extensions.Logging.LoggerFactory()
.AddSerilogWithUnityObjectScope(logger, dispose: true); // Also important! Use this instead of .AddSerilog(...)
Usage:
// Grab a logger manually for the example's sake. Usually done via DI.
var log = loggerFactory.CreateLogger("SomeCategory");
using (var scope = log.BeginScope(gameObject))
{
log.LogInformation($"You can click me to ping {gameObject.name}"); // Produces a clickable log entry inside this scope
}
log.LogInformation($"Now you can _not_ click me to ping {gameObject.name}");
Async/await support
It also works with async/await code as long as it is managed by Unity's main thread. UniTask should work fine, too.
async Task LogFromTask(UnityEngine.Object unityContext, string msg)
{
using var scope = log.BeginScope(unityContext);
for (int i = 0; i < 5; i++)
{
await Task.Delay(TimeSpan.FromMilliseconds(300));
log.LogInformation(msg);
}
}
var t1 = LogFromTask(gameObject, "T1");
var t2 = LogFromTask(Camera.main, "T2");
await Task.WhenAll(t1, t2); // Both task log in parallel with separate scopes
No thread safety for UnityEngine.Objects
This stems from the known Unity limitation: Access to all/most members of its managed-lifecycle objects is limited to the main thread.
So when calling e.g. BeginScope(gameObject)
on other threads the library throws an NotSupportedException
to keep it from failing somewhere else down the line (like in enrichers or sinks).
Everything else works fine from other threads.
Optional integration
The new functionality lives in a separate assembly (Serilog.Sinks.Unity3D.Extensions.Logging
). The assembly definition makes sure it only compiles when the dependencies are in place, namely the package org.nuget.serilog.extensions.logging
(which in turn depends on Serilog and Microsoft's abstractions).
If the dependencies are not found, the base sink (Serilog.Sinks.Unity3D) still works just like before.
Installation via manifest.json
{
"scopedRegistries": [
{
"name": "Unity NuGet",
"url": "https://unitynuget-registry.azurewebsites.net",
"scopes": [
"org.nuget"
]
}
],
"dependencies": {
"org.nuget.serilog.extensions.logging": "8.0.0",
"com.serilog.sinks.unity3d": "https://github.com/krisrok/Serilog.Sinks.Unity3D.git?path=/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D#3.0.0-ext",
...
I'm not a maintainer, but as a listener on #9, this looks pretty cool! A few thoughts:
- I still don't totally understand Serilog's internals; seems like if we can get Unity contexts into the sink via
BeginScope
then we should also be able to get them in via an enricher, and thus not force people to create log scopes everywhere they log. Maybe I'm mistaken - Is there really no way to use the existing Serilog setup and avoid adding a separate
ILoggerFactory.Add*
extension method? Coming from the WPF and ASP.NET worlds, I've never seen a helper library like this that couldn't work withAddSerilog
... - This could all be made thread-safe if the logger delegated back to the Unity main thread. This library used to behave this way (using the
MainThreadDispatcher
package), so I'm not sure why that changed. Even if theMainThreadDispatcher
dependency is not added back, its logic could be replicated in theUnity3DLogEventSink
(it's literally only like 100 lines).
Pinging @KuraiAndras for visibility on this PR.
The earliest when I can take a look is around sunday