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: