// Copyright (c) 2025-2026 GeWuYou // SPDX-License-Identifier: Apache-2.0 using System; using System.Collections.Generic; using GFramework.Core.Abstractions.Logging; using GFramework.Core.Logging; namespace GFramework.Godot.Logging; /// /// Godot platform logger implementation. /// /// /// This logger preserves the existing entry point while delegating output to /// so Godot rendering remains compatible with the Core appender pipeline. /// public sealed class GodotLogger : AbstractLogger { private static readonly IReadOnlyDictionary EmptyProperties = new Dictionary(StringComparer.Ordinal); private readonly GodotLogAppender _appender; /// /// Initializes a logger that preserves the historical fixed-format template. /// /// The logger name. /// The minimum enabled log level. public GodotLogger(string? name = null, LogLevel minLevel = LogLevel.Info) : this( name ?? RootLoggerName, CreateFixedOptionsProvider(minLevel), () => minLevel) { } /// /// Initializes a logger with Godot-specific formatting options. /// /// The logger name. /// The logger options. public GodotLogger(string? name, GodotLoggerOptions options) : this( name ?? RootLoggerName, CreateOptionsProvider(options), CreateMinLevelProvider(options)) { } /// /// Initializes the core logger with dynamic options and level providers. /// /// The resolved logger name used in rendered output. /// /// The provider that supplies the latest rendering options for each write. /// /// The provider that supplies the latest effective minimum level. /// /// The Godot factory uses this constructor so cached logger instances can observe hot-reloaded settings without /// being recreated. The default public constructor supplies a fixed provider to avoid allocation on the log /// path. /// internal GodotLogger( string name, Func optionsProvider, Func minLevelProvider) : base(name, minLevelProvider ?? throw new ArgumentNullException(nameof(minLevelProvider))) { _appender = new GodotLogAppender( optionsProvider ?? throw new ArgumentNullException(nameof(optionsProvider))); } /// /// Writes a log entry to Godot. /// /// The log level. /// The rendered message body. /// The optional exception. protected override void Write(LogLevel level, string message, Exception? exception) { WriteEntry(level, message, exception, properties: null); } /// /// Uses Godot-aware structured rendering instead of the base string concatenation fallback. /// /// The log level. /// The message body before Godot template rendering. /// Structured properties appended through the configured Godot template. public override void Log(LogLevel level, string message, params (string Key, object? Value)[] properties) { if (!IsEnabled(level)) { return; } WriteEntry(level, message, exception: null, properties); } /// /// Uses Godot-aware structured rendering instead of the base string concatenation fallback. /// /// The log level. /// The message body before Godot template rendering. /// The optional exception written after the rendered message. /// Structured properties appended through the configured Godot template. public override void Log( LogLevel level, string message, Exception? exception, params (string Key, object? Value)[] properties) { if (!IsEnabled(level)) { return; } WriteEntry(level, message, exception, properties); } private void WriteEntry( LogLevel level, string message, Exception? exception, (string Key, object? Value)[]? properties) { var entry = new LogEntry( DateTime.UtcNow, level, Name(), message, exception, ToPropertiesDictionary(properties)); _appender.Append(entry); } private static IReadOnlyDictionary ToPropertiesDictionary( (string Key, object? Value)[]? properties) { if (properties == null || properties.Length == 0) { return EmptyProperties; } var result = new Dictionary(StringComparer.Ordinal); foreach (var property in properties) { if (string.IsNullOrWhiteSpace(property.Key)) { continue; } result[property.Key.Trim()] = property.Value; } return result.Count == 0 ? EmptyProperties : result; } private static Func CreateFixedOptionsProvider(LogLevel minLevel) { var options = GodotLoggerOptions.ForMinimumLevel(minLevel); return () => options; } private static Func CreateOptionsProvider(GodotLoggerOptions options) { ArgumentNullException.ThrowIfNull(options); return () => options; } private static Func CreateMinLevelProvider(GodotLoggerOptions options) { ArgumentNullException.ThrowIfNull(options); return () => options.GetEffectiveMinLevel(); } }