// Copyright (c) 2025-2026 GeWuYou // SPDX-License-Identifier: Apache-2.0 using System; using System.Linq; using System.Threading; using GFramework.Core.Abstractions.Logging; namespace GFramework.Godot.Logging; /// /// Defers resolving the real Godot logger until the first logging operation needs it. /// /// /// This wrapper allows static logger fields to be created before or /// runs. The resolved inner logger is published with an atomic compare /// exchange so concurrent first-use calls converge on one cached instance without relying on the non-atomic /// null-coalescing assignment pattern. /// /// The category passed to the provider when the real logger is first needed. /// The accessor that returns the current provider at first use. internal sealed class DeferredLogger(string category, Func providerAccessor) : IStructuredLogger { private ILogger? _inner; /// /// Gets the resolved inner logger, creating and atomically publishing it on first use. /// /// /// The property is intentionally the single resolution gate so all delegated members share the same thread-safe /// lazy initialization behavior. /// private ILogger Inner { get { var current = Volatile.Read(ref _inner); if (current != null) { return current; } var createdLogger = ResolveLogger(); // Multiple callers can resolve concurrently; only one publishes the cached reference. return Interlocked.CompareExchange(ref _inner, createdLogger, null) ?? createdLogger; } } /// /// Gets the category name reported by the resolved logger. /// /// The logger category name. public string Name() { return Inner.Name(); } /// /// Returns whether trace messages are enabled by the current provider settings. /// /// true when trace messages should be emitted; otherwise false. public bool IsTraceEnabled() { return Inner.IsTraceEnabled(); } /// /// Returns whether debug messages are enabled by the current provider settings. /// /// true when debug messages should be emitted; otherwise false. public bool IsDebugEnabled() { return Inner.IsDebugEnabled(); } /// /// Returns whether informational messages are enabled by the current provider settings. /// /// true when informational messages should be emitted; otherwise false. public bool IsInfoEnabled() { return Inner.IsInfoEnabled(); } /// /// Returns whether warning messages are enabled by the current provider settings. /// /// true when warning messages should be emitted; otherwise false. public bool IsWarnEnabled() { return Inner.IsWarnEnabled(); } /// /// Returns whether error messages are enabled by the current provider settings. /// /// true when error messages should be emitted; otherwise false. public bool IsErrorEnabled() { return Inner.IsErrorEnabled(); } /// /// Returns whether fatal messages are enabled by the current provider settings. /// /// true when fatal messages should be emitted; otherwise false. public bool IsFatalEnabled() { return Inner.IsFatalEnabled(); } /// /// Returns whether the specified log level is enabled by the current provider settings. /// /// The level to check. /// true when the level should be emitted; otherwise false. public bool IsEnabledForLevel(LogLevel level) { return Inner.IsEnabledForLevel(level); } /// /// Writes a trace message through the resolved logger. /// /// The message to write. public void Trace(string msg) { Inner.Trace(msg); } /// /// Writes a formatted trace message through the resolved logger. /// /// The format string interpreted by the resolved logger. /// The first format argument. public void Trace(string format, object arg) { Inner.Trace(format, arg); } /// /// Writes a formatted trace message through the resolved logger. /// /// The format string interpreted by the resolved logger. /// The first format argument. /// The second format argument. public void Trace(string format, object arg1, object arg2) { Inner.Trace(format, arg1, arg2); } /// /// Writes a formatted trace message through the resolved logger. /// /// The format string interpreted by the resolved logger. /// The format arguments. public void Trace(string format, params object[] arguments) { Inner.Trace(format, arguments); } /// /// Writes a trace message and exception through the resolved logger. /// /// The message to write. /// The exception to attach. public void Trace(string msg, Exception t) { Inner.Trace(msg, t); } /// /// Writes a debug message through the resolved logger. /// /// The message to write. public void Debug(string msg) { Inner.Debug(msg); } /// /// Writes a formatted debug message through the resolved logger. /// /// The format string interpreted by the resolved logger. /// The first format argument. public void Debug(string format, object arg) { Inner.Debug(format, arg); } /// /// Writes a formatted debug message through the resolved logger. /// /// The format string interpreted by the resolved logger. /// The first format argument. /// The second format argument. public void Debug(string format, object arg1, object arg2) { Inner.Debug(format, arg1, arg2); } /// /// Writes a formatted debug message through the resolved logger. /// /// The format string interpreted by the resolved logger. /// The format arguments. public void Debug(string format, params object[] arguments) { Inner.Debug(format, arguments); } /// /// Writes a debug message and exception through the resolved logger. /// /// The message to write. /// The exception to attach. public void Debug(string msg, Exception t) { Inner.Debug(msg, t); } /// /// Writes an informational message through the resolved logger. /// /// The message to write. public void Info(string msg) { Inner.Info(msg); } /// /// Writes a formatted informational message through the resolved logger. /// /// The format string interpreted by the resolved logger. /// The first format argument. public void Info(string format, object arg) { Inner.Info(format, arg); } /// /// Writes a formatted informational message through the resolved logger. /// /// The format string interpreted by the resolved logger. /// The first format argument. /// The second format argument. public void Info(string format, object arg1, object arg2) { Inner.Info(format, arg1, arg2); } /// /// Writes a formatted informational message through the resolved logger. /// /// The format string interpreted by the resolved logger. /// The format arguments. public void Info(string format, params object[] arguments) { Inner.Info(format, arguments); } /// /// Writes an informational message and exception through the resolved logger. /// /// The message to write. /// The exception to attach. public void Info(string msg, Exception t) { Inner.Info(msg, t); } /// /// Writes a warning message through the resolved logger. /// /// The message to write. public void Warn(string msg) { Inner.Warn(msg); } /// /// Writes a formatted warning message through the resolved logger. /// /// The format string interpreted by the resolved logger. /// The first format argument. public void Warn(string format, object arg) { Inner.Warn(format, arg); } /// /// Writes a formatted warning message through the resolved logger. /// /// The format string interpreted by the resolved logger. /// The first format argument. /// The second format argument. public void Warn(string format, object arg1, object arg2) { Inner.Warn(format, arg1, arg2); } /// /// Writes a formatted warning message through the resolved logger. /// /// The format string interpreted by the resolved logger. /// The format arguments. public void Warn(string format, params object[] arguments) { Inner.Warn(format, arguments); } /// /// Writes a warning message and exception through the resolved logger. /// /// The message to write. /// The exception to attach. public void Warn(string msg, Exception t) { Inner.Warn(msg, t); } /// /// Writes an error message through the resolved logger. /// /// The message to write. public void Error(string msg) { Inner.Error(msg); } /// /// Writes a formatted error message through the resolved logger. /// /// The format string interpreted by the resolved logger. /// The first format argument. public void Error(string format, object arg) { Inner.Error(format, arg); } /// /// Writes a formatted error message through the resolved logger. /// /// The format string interpreted by the resolved logger. /// The first format argument. /// The second format argument. public void Error(string format, object arg1, object arg2) { Inner.Error(format, arg1, arg2); } /// /// Writes a formatted error message through the resolved logger. /// /// The format string interpreted by the resolved logger. /// The format arguments. public void Error(string format, params object[] arguments) { Inner.Error(format, arguments); } /// /// Writes an error message and exception through the resolved logger. /// /// The message to write. /// The exception to attach. public void Error(string msg, Exception t) { Inner.Error(msg, t); } /// /// Writes a fatal message through the resolved logger. /// /// The message to write. public void Fatal(string msg) { Inner.Fatal(msg); } /// /// Writes a formatted fatal message through the resolved logger. /// /// The format string interpreted by the resolved logger. /// The first format argument. public void Fatal(string format, object arg) { Inner.Fatal(format, arg); } /// /// Writes a formatted fatal message through the resolved logger. /// /// The format string interpreted by the resolved logger. /// The first format argument. /// The second format argument. public void Fatal(string format, object arg1, object arg2) { Inner.Fatal(format, arg1, arg2); } /// /// Writes a formatted fatal message through the resolved logger. /// /// The format string interpreted by the resolved logger. /// The format arguments. public void Fatal(string format, params object[] arguments) { Inner.Fatal(format, arguments); } /// /// Writes a fatal message and exception through the resolved logger. /// /// The message to write. /// The exception to attach. public void Fatal(string msg, Exception t) { Inner.Fatal(msg, t); } /// /// Writes a message at the specified level through the resolved logger. /// /// The level to write. /// The message to write. public void Log(LogLevel level, string message) { LogFallback(level, message, exception: null); } /// /// Writes a formatted message at the specified level while preserving deferred formatting semantics. /// /// The level to write. /// The format string interpreted by the resolved logger. /// The first format argument. public void Log(LogLevel level, string format, object arg) { Inner.Log(level, format, arg); } /// /// Writes a formatted message at the specified level while preserving deferred formatting semantics. /// /// The level to write. /// The format string interpreted by the resolved logger. /// The first format argument. /// The second format argument. public void Log(LogLevel level, string format, object arg1, object arg2) { Inner.Log(level, format, arg1, arg2); } /// /// Writes a formatted message at the specified level while preserving deferred formatting semantics. /// /// The level to write. /// The format string interpreted by the resolved logger. /// The format arguments. public void Log(LogLevel level, string format, params object[] arguments) { Inner.Log(level, format, arguments); } /// /// Writes a message and exception at the specified level through the resolved logger. /// /// The level to write. /// The message to write. /// The exception to attach. public void Log(LogLevel level, string message, Exception exception) { LogFallback(level, message, exception); } /// /// Writes a structured message through the resolved logger when it supports structured properties. /// /// The level to write. /// The message to write. /// The structured properties to attach. public void Log(LogLevel level, string message, params (string Key, object? Value)[] properties) { if (Inner is IStructuredLogger structuredLogger) { structuredLogger.Log(level, message, properties); return; } LogFallback(level, message, exception: null, properties); } /// /// Writes a structured message and exception through the resolved logger when it supports structured properties. /// /// The level to write. /// The message to write. /// The optional exception to attach. /// The structured properties to attach. public void Log(LogLevel level, string message, Exception? exception, params (string Key, object? Value)[] properties) { if (Inner is IStructuredLogger structuredLogger) { structuredLogger.Log(level, message, exception, properties); return; } LogFallback(level, message, exception, properties); } /// /// Resolves the real logger from the current provider for the deferred category. /// /// The logger created by the current provider. private ILogger ResolveLogger() { return providerAccessor().CreateLogger(category); } /// /// Routes a message through the non-structured logger surface when the resolved logger lacks structured support. /// /// The level to write. /// The message to write. /// The optional exception to attach. /// The structured properties rendered into a suffix for fallback loggers. /// Thrown when is not supported. private void LogFallback( LogLevel level, string message, Exception? exception, params (string Key, object? Value)[] properties) { var suffix = properties.Length == 0 ? string.Empty : " | " + string.Join(", ", properties.Select(static property => $"{property.Key}={property.Value}")); var rendered = message + suffix; switch (level) { case LogLevel.Trace: WriteFallback(rendered, exception, Inner.Trace, Inner.Trace); break; case LogLevel.Debug: WriteFallback(rendered, exception, Inner.Debug, Inner.Debug); break; case LogLevel.Info: WriteFallback(rendered, exception, Inner.Info, Inner.Info); break; case LogLevel.Warning: WriteFallback(rendered, exception, Inner.Warn, Inner.Warn); break; case LogLevel.Error: WriteFallback(rendered, exception, Inner.Error, Inner.Error); break; case LogLevel.Fatal: WriteFallback(rendered, exception, Inner.Fatal, Inner.Fatal); break; default: throw new ArgumentOutOfRangeException(nameof(level), level, "Unsupported log level."); } } /// /// Routes a non-structured message through the fallback path. /// /// The level to write. /// The message to write. /// The optional exception to attach. /// Thrown when is not supported. private void LogFallback(LogLevel level, string message, Exception? exception) { LogFallback(level, message, exception, []); } /// /// Chooses the message-only or exception-aware write delegate for fallback logging. /// /// The rendered fallback message. /// The optional exception to attach. /// The delegate used when no exception is present. /// The delegate used when an exception is present. private static void WriteFallback( string message, Exception? exception, Action writeMessage, Action writeException) { if (exception == null) { writeMessage(message); } else { writeException(message, exception); } } }