diff --git a/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging.meta b/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging.meta
new file mode 100644
index 0000000..b4f8a68
--- /dev/null
+++ b/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 5d025faec52e1234bacf13031de20526
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/Serilog.Sinks.Unity3D.Extensions.Logging.asmdef b/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/Serilog.Sinks.Unity3D.Extensions.Logging.asmdef
new file mode 100644
index 0000000..98a52d1
--- /dev/null
+++ b/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/Serilog.Sinks.Unity3D.Extensions.Logging.asmdef
@@ -0,0 +1,28 @@
+{
+ "name": "Serilog.Sinks.Unity3D.Extensions.Logging",
+ "rootNamespace": "",
+ "references": [
+ "GUID:ebcad7e3972724c43aa595f17cf4d103"
+ ],
+ "includePlatforms": [],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": true,
+ "precompiledReferences": [
+ "Serilog.dll",
+ "Serilog.Extensions.Logging.dll",
+ "Microsoft.Extensions.Logging.Abstractions.dll"
+ ],
+ "autoReferenced": true,
+ "defineConstraints": [
+ "HAS_SERILOG_EXTENSIONS_LOGGING"
+ ],
+ "versionDefines": [
+ {
+ "name": "org.nuget.serilog.extensions.logging",
+ "expression": "",
+ "define": "HAS_SERILOG_EXTENSIONS_LOGGING"
+ }
+ ],
+ "noEngineReferences": false
+}
\ No newline at end of file
diff --git a/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/Serilog.Sinks.Unity3D.Extensions.Logging.asmdef.meta b/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/Serilog.Sinks.Unity3D.Extensions.Logging.asmdef.meta
new file mode 100644
index 0000000..534391f
--- /dev/null
+++ b/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/Serilog.Sinks.Unity3D.Extensions.Logging.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: f94b348d662815649a7508205ced4fad
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/SerilogUnityContextScopeExtensions.cs b/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/SerilogUnityContextScopeExtensions.cs
new file mode 100644
index 0000000..f7282a4
--- /dev/null
+++ b/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/SerilogUnityContextScopeExtensions.cs
@@ -0,0 +1,56 @@
+#nullable enable
+using System;
+
+namespace Serilog.Sinks.Unity3D
+{
+ public static class SerilogUnityContextScopeExtensions
+ {
+ ///
+ ///
+ /// Enables to play nicely with .
+ /// This way you can use e.g. log.BeginScope(gameObject)
and the log entry will be clickable in Unity's console window.
+ ///
+ ///
+ /// To be used in conjunction with
+ /// when you set up your factory:
+ ///
+ /// new Microsoft.Extensions.Logging.LoggerFactory().AddSerilogWithUnityObjectScope(logger, dispose: true)
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static LoggerConfiguration EnableUnityObjectScope(this LoggerConfiguration loggerConfiguration)
+ {
+ return loggerConfiguration
+ .Destructure.With();
+ }
+
+ ///
+ ///
+ /// Add Serilog to the logging pipeline and enable to be used with
+ ///
+ ///
+ /// The logger factory to configure.
+ /// The Serilog logger; if not supplied, the static will be used.
+ /// When true, dispose when the framework disposes the provider. If the
+ /// logger is not specified but is true, the method will be
+ /// called on the static class instead.
+ /// Can optionally be used to transform s to
+ /// something else when calling with an
+ /// argument. Influences how this object gets represented in the logger's scope.
+ /// Reference to the supplied .
+ public static Microsoft.Extensions.Logging.ILoggerFactory AddSerilogWithUnityObjectScope(
+ this Microsoft.Extensions.Logging.ILoggerFactory factory,
+ Serilog.ILogger? logger = null,
+ bool dispose = false,
+ UnityObjectTransformerDelegate? unityObjectScopeTransformer = null)
+ {
+ if (factory == null) throw new ArgumentNullException(nameof(factory));
+
+ factory.AddProvider(new SerilogUnityContextScopeLoggerProvider(logger, dispose, unityObjectScopeTransformer));
+
+ return factory;
+ }
+ }
+}
diff --git a/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/SerilogUnityContextScopeExtensions.cs.meta b/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/SerilogUnityContextScopeExtensions.cs.meta
new file mode 100644
index 0000000..49d35d1
--- /dev/null
+++ b/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/SerilogUnityContextScopeExtensions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1bf169c22685fd54cb8e17ce3913cfaa
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/SerilogUnityContextScopeLogger.cs b/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/SerilogUnityContextScopeLogger.cs
new file mode 100644
index 0000000..f335c79
--- /dev/null
+++ b/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/SerilogUnityContextScopeLogger.cs
@@ -0,0 +1,176 @@
+#nullable enable
+using Serilog.Context;
+using System;
+using System.Threading;
+using UnityEngine;
+
+namespace Serilog.Sinks.Unity3D
+{
+ ///
+ /// A wrapper around .
+ /// Keeps track of s used as scope items.
+ ///
+ public class SerilogUnityContextScopeLogger : Microsoft.Extensions.Logging.ILogger
+ {
+ private static SynchronizationContext? _synchronizationContext;
+
+#if UNITY_EDITOR
+ [UnityEditor.InitializeOnLoadMethod]
+#endif
+ [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
+ private static void Initialize()
+ {
+ _synchronizationContext = SynchronizationContext.Current;
+ }
+
+ private readonly Microsoft.Extensions.Logging.ILogger _logger;
+ private readonly UnityObjectTransformerDelegate? _unityObjectTransformer;
+ private AsyncLocal? _unityContextScope;
+
+ private UnityContextScope? CurrentScope
+ {
+ get => _unityContextScope?.Value;
+ set => (_unityContextScope ??= new AsyncLocal()).Value = value;
+ }
+
+ public SerilogUnityContextScopeLogger(Microsoft.Extensions.Logging.ILogger logger, UnityObjectTransformerDelegate? unityObjectTransformer)
+ {
+ _logger = logger;
+ _unityObjectTransformer = unityObjectTransformer;
+ }
+
+ public void Log(Microsoft.Extensions.Logging.LogLevel logLevel, Microsoft.Extensions.Logging.EventId eventId, TState state, Exception? exception, Func formatter)
+ {
+ if (_unityContextScope?.Value != null)
+ {
+ var unityContext = _unityContextScope.Value.UnityContext;
+ if (ReferenceEquals(unityContext, null) == false)
+ {
+ LogWithUnityContext(logLevel, eventId, state, exception, formatter, unityContext);
+ return;
+ }
+ }
+
+ _logger.Log(logLevel, eventId, state, exception, formatter);
+ }
+
+ private void LogWithUnityContext(
+ Microsoft.Extensions.Logging.LogLevel logLevel,
+ Microsoft.Extensions.Logging.EventId eventId,
+ TState state,
+ Exception? exception,
+ Func formatter,
+ UnityEngine.Object unityContext)
+ {
+ LogContext.PushProperty(UnityObjectEnricher.UnityContextKey, unityContext, destructureObjects: true);
+ _logger.Log(logLevel, eventId, state, exception, formatter);
+ }
+
+ public bool IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel)
+ {
+ return _logger.IsEnabled(logLevel);
+ }
+
+ public IDisposable? BeginScope(TState state) where TState : notnull
+ {
+ if (state is UnityEngine.Object unityContext)
+ {
+ IDisposable? scopeDisposable;
+
+ if (_unityObjectTransformer == null)
+ {
+ if (SynchronizationContext.Current != _synchronizationContext)
+ {
+ throw new NotSupportedException("BeginScope(UnityEngine.Object) can only be used from the main thread.");
+ }
+
+ scopeDisposable = _logger.BeginScope(unityContext);
+ }
+ else
+ {
+ scopeDisposable = _logger.BeginScope(_unityObjectTransformer(unityContext));
+ }
+
+ return CurrentScope = new UnityContextScope(this, scopeDisposable, unityContext);
+ }
+
+ return _logger.BeginScope(state);
+ }
+
+ private class UnityContextScope : IDisposable
+ {
+ private readonly SerilogUnityContextScopeLogger _logger;
+ private readonly IDisposable? _chainedDisposable;
+
+ private readonly WeakReference _unityContextReference;
+ private bool _disposed;
+
+ public UnityContextScope? Parent { get; }
+
+ public UnityEngine.Object? UnityContext
+ {
+ get
+ {
+ if (_unityContextReference.TryGetTarget(out var result) == false)
+ return null;
+
+ return result;
+ }
+ }
+
+ public UnityContextScope(SerilogUnityContextScopeLogger logger, IDisposable? chainedDisposable, UnityEngine.Object unityContext)
+ {
+ _logger = logger;
+ _chainedDisposable = chainedDisposable;
+ _unityContextReference = new WeakReference(unityContext);
+
+ Parent = logger.CurrentScope;
+ }
+
+ public void Dispose()
+ {
+ if (_disposed)
+ return;
+
+ _disposed = true;
+
+ // Just like in Serilog.Extensions.Logging.SerilogLoggerScope.Dispose():
+ // In case one of the parent scopes has been disposed out-of-order, don't
+ // just blindly reinstate our own parent.
+ for (var scan = _logger.CurrentScope; scan != null; scan = scan.Parent)
+ {
+ if (ReferenceEquals(scan, this))
+ _logger.CurrentScope = Parent;
+ }
+
+ _chainedDisposable?.Dispose();
+ }
+ }
+
+ public class UnityObjectToStringWrapper
+ {
+ private UnityEngine.Object _unityContext;
+ private string? _toString = null;
+
+ public UnityObjectToStringWrapper(UnityEngine.Object unityContext)
+ {
+ _unityContext = unityContext;
+ }
+
+ public override string ToString()
+ {
+ if (_toString != null)
+ return _toString;
+
+ _synchronizationContext?.Send(GetObjectName, null);
+ return _toString!;
+ }
+
+ private void GetObjectName(object state)
+ {
+ _toString = _unityContext.ToString();
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/SerilogUnityContextScopeLogger.cs.meta b/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/SerilogUnityContextScopeLogger.cs.meta
new file mode 100644
index 0000000..5ba8399
--- /dev/null
+++ b/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/SerilogUnityContextScopeLogger.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9cc5dda9e0b90e142a028aa25bff74de
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/SerilogUnityContextScopeLoggerProvider.cs b/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/SerilogUnityContextScopeLoggerProvider.cs
new file mode 100644
index 0000000..854da06
--- /dev/null
+++ b/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/SerilogUnityContextScopeLoggerProvider.cs
@@ -0,0 +1,32 @@
+#nullable enable
+using Serilog.Extensions.Logging;
+
+namespace Serilog.Sinks.Unity3D
+{
+ public delegate object UnityObjectTransformerDelegate(UnityEngine.Object obj);
+
+ ///
+ /// A thin wrapper around .
+ ///
+ public class SerilogUnityContextScopeLoggerProvider : Microsoft.Extensions.Logging.ILoggerProvider
+ {
+ private readonly SerilogLoggerProvider _innerProvider;
+ private readonly UnityObjectTransformerDelegate? _unityObjectTransformer;
+
+ public SerilogUnityContextScopeLoggerProvider(ILogger? logger, bool dispose, UnityObjectTransformerDelegate? unityObjectTransformer = null)
+ {
+ _innerProvider = new SerilogLoggerProvider(logger, dispose);
+ _unityObjectTransformer = unityObjectTransformer;
+ }
+
+ public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName)
+ {
+ return new SerilogUnityContextScopeLogger(_innerProvider.CreateLogger(categoryName), _unityObjectTransformer);
+ }
+
+ public void Dispose()
+ {
+ _innerProvider.Dispose();
+ }
+ }
+}
diff --git a/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/SerilogUnityContextScopeLoggerProvider.cs.meta b/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/SerilogUnityContextScopeLoggerProvider.cs.meta
new file mode 100644
index 0000000..dc4f52b
--- /dev/null
+++ b/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/SerilogUnityContextScopeLoggerProvider.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 48cee4520c4f7d1488b09c00bdbffb02
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/UnityObjectDestructuringPolicy.cs b/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/UnityObjectDestructuringPolicy.cs
new file mode 100644
index 0000000..c4cd0de
--- /dev/null
+++ b/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/UnityObjectDestructuringPolicy.cs
@@ -0,0 +1,26 @@
+#nullable enable
+using Serilog.Core;
+using Serilog.Events;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Serilog.Sinks.Unity3D
+{
+ ///
+ /// Makes sure we destructure s as scalars, i.e. keeping their reference.
+ ///
+ internal class UnityObjectDestructuringPolicy : IDestructuringPolicy
+ {
+ public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, [NotNullWhen(true)] out LogEventPropertyValue? result)
+ {
+ result = null;
+
+ if (value is UnityEngine.Object unityObject)
+ {
+ result = new ScalarValue(unityObject);
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/UnityObjectDestructuringPolicy.cs.meta b/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/UnityObjectDestructuringPolicy.cs.meta
new file mode 100644
index 0000000..eae2fdf
--- /dev/null
+++ b/Serilog.Sinks.Unity3D/Assets/Serilog.Sinks.Unity3D/Serilog.Extensions.Logging/UnityObjectDestructuringPolicy.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0b7f1405e6865e544a3fcebfd870fc4e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant: