mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
fix(godot): 修复日志 review 反馈
- 修复 DeferredLogger 格式化重载提前 string.Format 的热路径问题 - 修复 GodotLogger 默认 options 分配与结构化属性无效 key 处理 - 补充 Godot logging XML 文档、回归测试和 appsettings 接入示例 - 更新 Godot logging PR review 跟踪与验证记录
This commit is contained in:
parent
b4b3538b21
commit
c967b4df3d
@ -1,15 +1,22 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using GFramework.Core.Abstractions.Logging;
|
using GFramework.Core.Abstractions.Logging;
|
||||||
using GFramework.Godot.Logging;
|
using GFramework.Godot.Logging;
|
||||||
|
|
||||||
namespace GFramework.Godot.Tests.Logging;
|
namespace GFramework.Godot.Tests.Logging;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies Godot logging configuration discovery, parsing, normalization, and live settings propagation.
|
||||||
|
/// </summary>
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public sealed class GodotLoggerSettingsLoaderTests
|
public sealed class GodotLoggerSettingsLoaderTests
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that configuration discovery honors the environment path, executable directory, and project path order.
|
||||||
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void DiscoverConfigurationPath_Should_Prefer_EnvironmentVariable_Then_ProcessPath_Then_ProjectPath()
|
public void DiscoverConfigurationPath_Should_Prefer_EnvironmentVariable_Then_ProcessPath_Then_ProjectPath()
|
||||||
{
|
{
|
||||||
@ -53,6 +60,9 @@ public sealed class GodotLoggerSettingsLoaderTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that JSON settings bind Godot logger options and category log-level overrides.
|
||||||
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void LoadFromJsonString_Should_Read_GodotLogger_Options_And_Category_Levels()
|
public void LoadFromJsonString_Should_Read_GodotLogger_Options_And_Category_Levels()
|
||||||
{
|
{
|
||||||
@ -89,6 +99,9 @@ public sealed class GodotLoggerSettingsLoaderTests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that nullable JSON option fields are normalized before the runtime receives the settings snapshot.
|
||||||
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void LoadFromJsonString_Should_Normalize_Null_GodotLogger_Options()
|
public void LoadFromJsonString_Should_Normalize_Null_GodotLogger_Options()
|
||||||
{
|
{
|
||||||
@ -115,6 +128,9 @@ public sealed class GodotLoggerSettingsLoaderTests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that numeric JSON log levels must map to defined <see cref="LogLevel"/> values.
|
||||||
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void LoadFromJsonString_Should_Reject_Invalid_Numeric_LogLevel()
|
public void LoadFromJsonString_Should_Reject_Invalid_Numeric_LogLevel()
|
||||||
{
|
{
|
||||||
@ -133,6 +149,9 @@ public sealed class GodotLoggerSettingsLoaderTests
|
|||||||
Assert.That(error?.Message, Does.Contain("Unsupported numeric LogLevel value '999'"));
|
Assert.That(error?.Message, Does.Contain("Unsupported numeric LogLevel value '999'"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that cached provider loggers read the latest settings after the provider snapshot changes.
|
||||||
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void Provider_Should_Apply_Updated_Settings_To_Existing_Loggers()
|
public void Provider_Should_Apply_Updated_Settings_To_Existing_Loggers()
|
||||||
{
|
{
|
||||||
@ -166,4 +185,25 @@ public sealed class GodotLoggerSettingsLoaderTests
|
|||||||
Assert.That(logger.IsDebugEnabled(), Is.False);
|
Assert.That(logger.IsDebugEnabled(), Is.False);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that caller-supplied structured property keys cannot break Godot log rendering.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void StructuredProperties_Should_Skip_Blank_Keys_And_Trim_Valid_Keys()
|
||||||
|
{
|
||||||
|
var formatProperties = typeof(GodotLogger).GetMethod(
|
||||||
|
"FormatProperties",
|
||||||
|
BindingFlags.NonPublic | BindingFlags.Static);
|
||||||
|
var properties = new (string Key, object? Value)[]
|
||||||
|
{
|
||||||
|
(null!, "ignored"),
|
||||||
|
(" ", "ignored"),
|
||||||
|
(" Player ", 42)
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = formatProperties?.Invoke(null, [properties]);
|
||||||
|
|
||||||
|
Assert.That(result, Is.EqualTo(" | Player=42"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,10 +14,19 @@ namespace GFramework.Godot.Logging;
|
|||||||
/// exchange so concurrent first-use calls converge on one cached instance without relying on the non-atomic
|
/// exchange so concurrent first-use calls converge on one cached instance without relying on the non-atomic
|
||||||
/// null-coalescing assignment pattern.
|
/// null-coalescing assignment pattern.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
|
/// <param name="category">The category passed to the provider when the real logger is first needed.</param>
|
||||||
|
/// <param name="providerAccessor">The accessor that returns the current provider at first use.</param>
|
||||||
internal sealed class DeferredLogger(string category, Func<ILoggerFactoryProvider> providerAccessor) : IStructuredLogger
|
internal sealed class DeferredLogger(string category, Func<ILoggerFactoryProvider> providerAccessor) : IStructuredLogger
|
||||||
{
|
{
|
||||||
private ILogger? _inner;
|
private ILogger? _inner;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the resolved inner logger, creating and atomically publishing it on first use.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The property is intentionally the single resolution gate so all delegated members share the same thread-safe
|
||||||
|
/// lazy initialization behavior.
|
||||||
|
/// </remarks>
|
||||||
private ILogger Inner
|
private ILogger Inner
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -35,221 +44,440 @@ internal sealed class DeferredLogger(string category, Func<ILoggerFactoryProvide
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the category name reported by the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The logger category name.</returns>
|
||||||
public string Name()
|
public string Name()
|
||||||
{
|
{
|
||||||
return Inner.Name();
|
return Inner.Name();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether trace messages are enabled by the current provider settings.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true when trace messages should be emitted; otherwise false.</returns>
|
||||||
public bool IsTraceEnabled()
|
public bool IsTraceEnabled()
|
||||||
{
|
{
|
||||||
return Inner.IsTraceEnabled();
|
return Inner.IsTraceEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether debug messages are enabled by the current provider settings.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true when debug messages should be emitted; otherwise false.</returns>
|
||||||
public bool IsDebugEnabled()
|
public bool IsDebugEnabled()
|
||||||
{
|
{
|
||||||
return Inner.IsDebugEnabled();
|
return Inner.IsDebugEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether informational messages are enabled by the current provider settings.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true when informational messages should be emitted; otherwise false.</returns>
|
||||||
public bool IsInfoEnabled()
|
public bool IsInfoEnabled()
|
||||||
{
|
{
|
||||||
return Inner.IsInfoEnabled();
|
return Inner.IsInfoEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether warning messages are enabled by the current provider settings.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true when warning messages should be emitted; otherwise false.</returns>
|
||||||
public bool IsWarnEnabled()
|
public bool IsWarnEnabled()
|
||||||
{
|
{
|
||||||
return Inner.IsWarnEnabled();
|
return Inner.IsWarnEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether error messages are enabled by the current provider settings.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true when error messages should be emitted; otherwise false.</returns>
|
||||||
public bool IsErrorEnabled()
|
public bool IsErrorEnabled()
|
||||||
{
|
{
|
||||||
return Inner.IsErrorEnabled();
|
return Inner.IsErrorEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether fatal messages are enabled by the current provider settings.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true when fatal messages should be emitted; otherwise false.</returns>
|
||||||
public bool IsFatalEnabled()
|
public bool IsFatalEnabled()
|
||||||
{
|
{
|
||||||
return Inner.IsFatalEnabled();
|
return Inner.IsFatalEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether the specified log level is enabled by the current provider settings.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The level to check.</param>
|
||||||
|
/// <returns>true when the level should be emitted; otherwise false.</returns>
|
||||||
public bool IsEnabledForLevel(LogLevel level)
|
public bool IsEnabledForLevel(LogLevel level)
|
||||||
{
|
{
|
||||||
return Inner.IsEnabledForLevel(level);
|
return Inner.IsEnabledForLevel(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a trace message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg">The message to write.</param>
|
||||||
public void Trace(string msg)
|
public void Trace(string msg)
|
||||||
{
|
{
|
||||||
Inner.Trace(msg);
|
Inner.Trace(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a formatted trace message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">The format string interpreted by the resolved logger.</param>
|
||||||
|
/// <param name="arg">The first format argument.</param>
|
||||||
public void Trace(string format, object arg)
|
public void Trace(string format, object arg)
|
||||||
{
|
{
|
||||||
Inner.Trace(format, arg);
|
Inner.Trace(format, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a formatted trace message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">The format string interpreted by the resolved logger.</param>
|
||||||
|
/// <param name="arg1">The first format argument.</param>
|
||||||
|
/// <param name="arg2">The second format argument.</param>
|
||||||
public void Trace(string format, object arg1, object arg2)
|
public void Trace(string format, object arg1, object arg2)
|
||||||
{
|
{
|
||||||
Inner.Trace(format, arg1, arg2);
|
Inner.Trace(format, arg1, arg2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a formatted trace message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">The format string interpreted by the resolved logger.</param>
|
||||||
|
/// <param name="arguments">The format arguments.</param>
|
||||||
public void Trace(string format, params object[] arguments)
|
public void Trace(string format, params object[] arguments)
|
||||||
{
|
{
|
||||||
Inner.Trace(format, arguments);
|
Inner.Trace(format, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a trace message and exception through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg">The message to write.</param>
|
||||||
|
/// <param name="t">The exception to attach.</param>
|
||||||
public void Trace(string msg, Exception t)
|
public void Trace(string msg, Exception t)
|
||||||
{
|
{
|
||||||
Inner.Trace(msg, t);
|
Inner.Trace(msg, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a debug message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg">The message to write.</param>
|
||||||
public void Debug(string msg)
|
public void Debug(string msg)
|
||||||
{
|
{
|
||||||
Inner.Debug(msg);
|
Inner.Debug(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a formatted debug message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">The format string interpreted by the resolved logger.</param>
|
||||||
|
/// <param name="arg">The first format argument.</param>
|
||||||
public void Debug(string format, object arg)
|
public void Debug(string format, object arg)
|
||||||
{
|
{
|
||||||
Inner.Debug(format, arg);
|
Inner.Debug(format, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a formatted debug message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">The format string interpreted by the resolved logger.</param>
|
||||||
|
/// <param name="arg1">The first format argument.</param>
|
||||||
|
/// <param name="arg2">The second format argument.</param>
|
||||||
public void Debug(string format, object arg1, object arg2)
|
public void Debug(string format, object arg1, object arg2)
|
||||||
{
|
{
|
||||||
Inner.Debug(format, arg1, arg2);
|
Inner.Debug(format, arg1, arg2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a formatted debug message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">The format string interpreted by the resolved logger.</param>
|
||||||
|
/// <param name="arguments">The format arguments.</param>
|
||||||
public void Debug(string format, params object[] arguments)
|
public void Debug(string format, params object[] arguments)
|
||||||
{
|
{
|
||||||
Inner.Debug(format, arguments);
|
Inner.Debug(format, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a debug message and exception through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg">The message to write.</param>
|
||||||
|
/// <param name="t">The exception to attach.</param>
|
||||||
public void Debug(string msg, Exception t)
|
public void Debug(string msg, Exception t)
|
||||||
{
|
{
|
||||||
Inner.Debug(msg, t);
|
Inner.Debug(msg, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes an informational message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg">The message to write.</param>
|
||||||
public void Info(string msg)
|
public void Info(string msg)
|
||||||
{
|
{
|
||||||
Inner.Info(msg);
|
Inner.Info(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a formatted informational message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">The format string interpreted by the resolved logger.</param>
|
||||||
|
/// <param name="arg">The first format argument.</param>
|
||||||
public void Info(string format, object arg)
|
public void Info(string format, object arg)
|
||||||
{
|
{
|
||||||
Inner.Info(format, arg);
|
Inner.Info(format, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a formatted informational message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">The format string interpreted by the resolved logger.</param>
|
||||||
|
/// <param name="arg1">The first format argument.</param>
|
||||||
|
/// <param name="arg2">The second format argument.</param>
|
||||||
public void Info(string format, object arg1, object arg2)
|
public void Info(string format, object arg1, object arg2)
|
||||||
{
|
{
|
||||||
Inner.Info(format, arg1, arg2);
|
Inner.Info(format, arg1, arg2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a formatted informational message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">The format string interpreted by the resolved logger.</param>
|
||||||
|
/// <param name="arguments">The format arguments.</param>
|
||||||
public void Info(string format, params object[] arguments)
|
public void Info(string format, params object[] arguments)
|
||||||
{
|
{
|
||||||
Inner.Info(format, arguments);
|
Inner.Info(format, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes an informational message and exception through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg">The message to write.</param>
|
||||||
|
/// <param name="t">The exception to attach.</param>
|
||||||
public void Info(string msg, Exception t)
|
public void Info(string msg, Exception t)
|
||||||
{
|
{
|
||||||
Inner.Info(msg, t);
|
Inner.Info(msg, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a warning message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg">The message to write.</param>
|
||||||
public void Warn(string msg)
|
public void Warn(string msg)
|
||||||
{
|
{
|
||||||
Inner.Warn(msg);
|
Inner.Warn(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a formatted warning message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">The format string interpreted by the resolved logger.</param>
|
||||||
|
/// <param name="arg">The first format argument.</param>
|
||||||
public void Warn(string format, object arg)
|
public void Warn(string format, object arg)
|
||||||
{
|
{
|
||||||
Inner.Warn(format, arg);
|
Inner.Warn(format, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a formatted warning message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">The format string interpreted by the resolved logger.</param>
|
||||||
|
/// <param name="arg1">The first format argument.</param>
|
||||||
|
/// <param name="arg2">The second format argument.</param>
|
||||||
public void Warn(string format, object arg1, object arg2)
|
public void Warn(string format, object arg1, object arg2)
|
||||||
{
|
{
|
||||||
Inner.Warn(format, arg1, arg2);
|
Inner.Warn(format, arg1, arg2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a formatted warning message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">The format string interpreted by the resolved logger.</param>
|
||||||
|
/// <param name="arguments">The format arguments.</param>
|
||||||
public void Warn(string format, params object[] arguments)
|
public void Warn(string format, params object[] arguments)
|
||||||
{
|
{
|
||||||
Inner.Warn(format, arguments);
|
Inner.Warn(format, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a warning message and exception through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg">The message to write.</param>
|
||||||
|
/// <param name="t">The exception to attach.</param>
|
||||||
public void Warn(string msg, Exception t)
|
public void Warn(string msg, Exception t)
|
||||||
{
|
{
|
||||||
Inner.Warn(msg, t);
|
Inner.Warn(msg, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes an error message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg">The message to write.</param>
|
||||||
public void Error(string msg)
|
public void Error(string msg)
|
||||||
{
|
{
|
||||||
Inner.Error(msg);
|
Inner.Error(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a formatted error message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">The format string interpreted by the resolved logger.</param>
|
||||||
|
/// <param name="arg">The first format argument.</param>
|
||||||
public void Error(string format, object arg)
|
public void Error(string format, object arg)
|
||||||
{
|
{
|
||||||
Inner.Error(format, arg);
|
Inner.Error(format, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a formatted error message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">The format string interpreted by the resolved logger.</param>
|
||||||
|
/// <param name="arg1">The first format argument.</param>
|
||||||
|
/// <param name="arg2">The second format argument.</param>
|
||||||
public void Error(string format, object arg1, object arg2)
|
public void Error(string format, object arg1, object arg2)
|
||||||
{
|
{
|
||||||
Inner.Error(format, arg1, arg2);
|
Inner.Error(format, arg1, arg2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a formatted error message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">The format string interpreted by the resolved logger.</param>
|
||||||
|
/// <param name="arguments">The format arguments.</param>
|
||||||
public void Error(string format, params object[] arguments)
|
public void Error(string format, params object[] arguments)
|
||||||
{
|
{
|
||||||
Inner.Error(format, arguments);
|
Inner.Error(format, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes an error message and exception through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg">The message to write.</param>
|
||||||
|
/// <param name="t">The exception to attach.</param>
|
||||||
public void Error(string msg, Exception t)
|
public void Error(string msg, Exception t)
|
||||||
{
|
{
|
||||||
Inner.Error(msg, t);
|
Inner.Error(msg, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a fatal message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg">The message to write.</param>
|
||||||
public void Fatal(string msg)
|
public void Fatal(string msg)
|
||||||
{
|
{
|
||||||
Inner.Fatal(msg);
|
Inner.Fatal(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a formatted fatal message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">The format string interpreted by the resolved logger.</param>
|
||||||
|
/// <param name="arg">The first format argument.</param>
|
||||||
public void Fatal(string format, object arg)
|
public void Fatal(string format, object arg)
|
||||||
{
|
{
|
||||||
Inner.Fatal(format, arg);
|
Inner.Fatal(format, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a formatted fatal message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">The format string interpreted by the resolved logger.</param>
|
||||||
|
/// <param name="arg1">The first format argument.</param>
|
||||||
|
/// <param name="arg2">The second format argument.</param>
|
||||||
public void Fatal(string format, object arg1, object arg2)
|
public void Fatal(string format, object arg1, object arg2)
|
||||||
{
|
{
|
||||||
Inner.Fatal(format, arg1, arg2);
|
Inner.Fatal(format, arg1, arg2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a formatted fatal message through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">The format string interpreted by the resolved logger.</param>
|
||||||
|
/// <param name="arguments">The format arguments.</param>
|
||||||
public void Fatal(string format, params object[] arguments)
|
public void Fatal(string format, params object[] arguments)
|
||||||
{
|
{
|
||||||
Inner.Fatal(format, arguments);
|
Inner.Fatal(format, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a fatal message and exception through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg">The message to write.</param>
|
||||||
|
/// <param name="t">The exception to attach.</param>
|
||||||
public void Fatal(string msg, Exception t)
|
public void Fatal(string msg, Exception t)
|
||||||
{
|
{
|
||||||
Inner.Fatal(msg, t);
|
Inner.Fatal(msg, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a message at the specified level through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The level to write.</param>
|
||||||
|
/// <param name="message">The message to write.</param>
|
||||||
public void Log(LogLevel level, string message)
|
public void Log(LogLevel level, string message)
|
||||||
{
|
{
|
||||||
LogFallback(level, message, exception: null);
|
LogFallback(level, message, exception: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a formatted message at the specified level while preserving deferred formatting semantics.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The level to write.</param>
|
||||||
|
/// <param name="format">The format string interpreted by the resolved logger.</param>
|
||||||
|
/// <param name="arg">The first format argument.</param>
|
||||||
public void Log(LogLevel level, string format, object arg)
|
public void Log(LogLevel level, string format, object arg)
|
||||||
{
|
{
|
||||||
LogFallback(level, string.Format(format, arg), exception: null);
|
Inner.Log(level, format, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a formatted message at the specified level while preserving deferred formatting semantics.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The level to write.</param>
|
||||||
|
/// <param name="format">The format string interpreted by the resolved logger.</param>
|
||||||
|
/// <param name="arg1">The first format argument.</param>
|
||||||
|
/// <param name="arg2">The second format argument.</param>
|
||||||
public void Log(LogLevel level, string format, object arg1, object arg2)
|
public void Log(LogLevel level, string format, object arg1, object arg2)
|
||||||
{
|
{
|
||||||
LogFallback(level, string.Format(format, arg1, arg2), exception: null);
|
Inner.Log(level, format, arg1, arg2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a formatted message at the specified level while preserving deferred formatting semantics.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The level to write.</param>
|
||||||
|
/// <param name="format">The format string interpreted by the resolved logger.</param>
|
||||||
|
/// <param name="arguments">The format arguments.</param>
|
||||||
public void Log(LogLevel level, string format, params object[] arguments)
|
public void Log(LogLevel level, string format, params object[] arguments)
|
||||||
{
|
{
|
||||||
LogFallback(level, string.Format(format, arguments), exception: null);
|
Inner.Log(level, format, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a message and exception at the specified level through the resolved logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The level to write.</param>
|
||||||
|
/// <param name="message">The message to write.</param>
|
||||||
|
/// <param name="exception">The exception to attach.</param>
|
||||||
public void Log(LogLevel level, string message, Exception exception)
|
public void Log(LogLevel level, string message, Exception exception)
|
||||||
{
|
{
|
||||||
LogFallback(level, message, exception);
|
LogFallback(level, message, exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a structured message through the resolved logger when it supports structured properties.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The level to write.</param>
|
||||||
|
/// <param name="message">The message to write.</param>
|
||||||
|
/// <param name="properties">The structured properties to attach.</param>
|
||||||
public void Log(LogLevel level, string message, params (string Key, object? Value)[] properties)
|
public void Log(LogLevel level, string message, params (string Key, object? Value)[] properties)
|
||||||
{
|
{
|
||||||
if (Inner is IStructuredLogger structuredLogger)
|
if (Inner is IStructuredLogger structuredLogger)
|
||||||
@ -261,6 +489,13 @@ internal sealed class DeferredLogger(string category, Func<ILoggerFactoryProvide
|
|||||||
LogFallback(level, message, exception: null, properties);
|
LogFallback(level, message, exception: null, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a structured message and exception through the resolved logger when it supports structured properties.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The level to write.</param>
|
||||||
|
/// <param name="message">The message to write.</param>
|
||||||
|
/// <param name="exception">The optional exception to attach.</param>
|
||||||
|
/// <param name="properties">The structured properties to attach.</param>
|
||||||
public void Log(LogLevel level, string message, Exception? exception, params (string Key, object? Value)[] properties)
|
public void Log(LogLevel level, string message, Exception? exception, params (string Key, object? Value)[] properties)
|
||||||
{
|
{
|
||||||
if (Inner is IStructuredLogger structuredLogger)
|
if (Inner is IStructuredLogger structuredLogger)
|
||||||
@ -272,11 +507,23 @@ internal sealed class DeferredLogger(string category, Func<ILoggerFactoryProvide
|
|||||||
LogFallback(level, message, exception, properties);
|
LogFallback(level, message, exception, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves the real logger from the current provider for the deferred category.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The logger created by the current provider.</returns>
|
||||||
private ILogger ResolveLogger()
|
private ILogger ResolveLogger()
|
||||||
{
|
{
|
||||||
return providerAccessor().CreateLogger(category);
|
return providerAccessor().CreateLogger(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Routes a message through the non-structured logger surface when the resolved logger lacks structured support.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The level to write.</param>
|
||||||
|
/// <param name="message">The message to write.</param>
|
||||||
|
/// <param name="exception">The optional exception to attach.</param>
|
||||||
|
/// <param name="properties">The structured properties rendered into a suffix for fallback loggers.</param>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="level"/> is not supported.</exception>
|
||||||
private void LogFallback(
|
private void LogFallback(
|
||||||
LogLevel level,
|
LogLevel level,
|
||||||
string message,
|
string message,
|
||||||
@ -313,11 +560,25 @@ internal sealed class DeferredLogger(string category, Func<ILoggerFactoryProvide
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Routes a non-structured message through the fallback path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">The level to write.</param>
|
||||||
|
/// <param name="message">The message to write.</param>
|
||||||
|
/// <param name="exception">The optional exception to attach.</param>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="level"/> is not supported.</exception>
|
||||||
private void LogFallback(LogLevel level, string message, Exception? exception)
|
private void LogFallback(LogLevel level, string message, Exception? exception)
|
||||||
{
|
{
|
||||||
LogFallback(level, message, exception, []);
|
LogFallback(level, message, exception, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Chooses the message-only or exception-aware write delegate for fallback logging.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The rendered fallback message.</param>
|
||||||
|
/// <param name="exception">The optional exception to attach.</param>
|
||||||
|
/// <param name="writeMessage">The delegate used when no exception is present.</param>
|
||||||
|
/// <param name="writeException">The delegate used when an exception is present.</param>
|
||||||
private static void WriteFallback(
|
private static void WriteFallback(
|
||||||
string message,
|
string message,
|
||||||
Exception? exception,
|
Exception? exception,
|
||||||
|
|||||||
@ -23,7 +23,7 @@ public sealed class GodotLogger : AbstractLogger
|
|||||||
public GodotLogger(string? name = null, LogLevel minLevel = LogLevel.Info)
|
public GodotLogger(string? name = null, LogLevel minLevel = LogLevel.Info)
|
||||||
: this(
|
: this(
|
||||||
name ?? RootLoggerName,
|
name ?? RootLoggerName,
|
||||||
() => GodotLoggerOptions.ForMinimumLevel(minLevel),
|
CreateFixedOptionsProvider(minLevel),
|
||||||
() => minLevel)
|
() => minLevel)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -36,12 +36,21 @@ public sealed class GodotLogger : AbstractLogger
|
|||||||
public GodotLogger(string? name, GodotLoggerOptions options)
|
public GodotLogger(string? name, GodotLoggerOptions options)
|
||||||
: this(
|
: this(
|
||||||
name ?? RootLoggerName,
|
name ?? RootLoggerName,
|
||||||
() => options,
|
CreateOptionsProvider(options),
|
||||||
() => options.GetEffectiveMinLevel())
|
CreateMinLevelProvider(options))
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the core logger with dynamic options and level providers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The resolved logger name used in rendered output.</param>
|
||||||
|
/// <param name="optionsProvider">The provider that supplies the latest rendering options for each write.</param>
|
||||||
|
/// <param name="minLevelProvider">The provider that supplies the latest effective minimum level.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// 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.
|
||||||
|
/// </remarks>
|
||||||
internal GodotLogger(
|
internal GodotLogger(
|
||||||
string name,
|
string name,
|
||||||
Func<GodotLoggerOptions> optionsProvider,
|
Func<GodotLoggerOptions> optionsProvider,
|
||||||
@ -150,7 +159,12 @@ public sealed class GodotLogger : AbstractLogger
|
|||||||
{
|
{
|
||||||
foreach (var property in properties)
|
foreach (var property in properties)
|
||||||
{
|
{
|
||||||
merged[property.Key] = property.Value;
|
if (string.IsNullOrWhiteSpace(property.Key))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
merged[property.Key.Trim()] = property.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,6 +174,24 @@ public sealed class GodotLogger : AbstractLogger
|
|||||||
private static readonly IReadOnlyDictionary<string, object?> EmptyProperties =
|
private static readonly IReadOnlyDictionary<string, object?> EmptyProperties =
|
||||||
new Dictionary<string, object?>(StringComparer.Ordinal);
|
new Dictionary<string, object?>(StringComparer.Ordinal);
|
||||||
|
|
||||||
|
private static Func<GodotLoggerOptions> CreateFixedOptionsProvider(LogLevel minLevel)
|
||||||
|
{
|
||||||
|
var options = GodotLoggerOptions.ForMinimumLevel(minLevel);
|
||||||
|
return () => options;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Func<GodotLoggerOptions> CreateOptionsProvider(GodotLoggerOptions options)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(options);
|
||||||
|
return () => options;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Func<LogLevel> CreateMinLevelProvider(GodotLoggerOptions options)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(options);
|
||||||
|
return () => options.GetEffectiveMinLevel();
|
||||||
|
}
|
||||||
|
|
||||||
private static string FormatValue(object? value)
|
private static string FormatValue(object? value)
|
||||||
{
|
{
|
||||||
if (value == null)
|
if (value == null)
|
||||||
|
|||||||
@ -7,15 +7,15 @@ GFramework 自身日志抽象不分叉”的稳定宿主层,并为后续 Godot
|
|||||||
|
|
||||||
## 当前恢复点
|
## 当前恢复点
|
||||||
|
|
||||||
- 恢复点编号:`GODOT-LOGGING-COMPLIANCE-POLISH-RP-002`
|
- 恢复点编号:`GODOT-LOGGING-COMPLIANCE-POLISH-RP-003`
|
||||||
- 当前阶段:`PR review hardening`
|
- 当前阶段:`PR review follow-up`
|
||||||
- 当前焦点:
|
- 当前焦点:
|
||||||
- 已补齐 `GodotLog` 静态入口、延迟 logger 解析、配置自动发现与热重载
|
- 已补齐 `GodotLog` 静态入口、延迟 logger 解析、配置自动发现与热重载
|
||||||
- 已让 `GodotLoggerFactoryProvider` 对已缓存 logger 生效动态配置,而不是只在新建 logger 时读快照
|
- 已让 `GodotLoggerFactoryProvider` 对已缓存 logger 生效动态配置,而不是只在新建 logger 时读快照
|
||||||
- 已让 `GodotLogger` 支持 `{properties}` 占位符,并把 `IStructuredLogger` / `LogContext` 属性落到 Godot 输出
|
- 已让 `GodotLogger` 支持 `{properties}` 占位符,并把 `IStructuredLogger` / `LogContext` 属性落到 Godot 输出
|
||||||
- 已兼容 `GodotLogger` 风格配置值,如 `Information` / `Critical`
|
- 已兼容 `GodotLogger` 风格配置值,如 `Information` / `Critical`
|
||||||
- 已处理 PR #314 最新 AI review 中仍适用的生命周期、配置输入、缓存边界、注释和脚本健壮性问题
|
- 已处理 PR #314 最新 AI review 中仍适用的 XML docs、热路径分配、结构化属性兜底、文档示例和 tracking 精简问题
|
||||||
- 下一轮优先只复核 CI 反馈是否已收敛,避免继续扩大 Godot logging API 面
|
- 下一轮优先刷新 PR review / CI 反馈,避免继续扩大 Godot logging API 面
|
||||||
|
|
||||||
## 当前状态摘要
|
## 当前状态摘要
|
||||||
|
|
||||||
@ -48,6 +48,10 @@ GFramework 自身日志抽象不分叉”的稳定宿主层,并为后续 Godot
|
|||||||
- 配置 JSON 会先归一化模板和颜色字典,并拒绝未定义的数字 `LogLevel`
|
- 配置 JSON 会先归一化模板和颜色字典,并拒绝未定义的数字 `LogLevel`
|
||||||
- `GodotLogTemplate` 的模板缓存和分类格式缓存已改为有界并发缓存,避免热重载或动态 category 长期单向增长
|
- `GodotLogTemplate` 的模板缓存和分类格式缓存已改为有界并发缓存,避免热重载或动态 category 长期单向增长
|
||||||
- `refactor-scripts/update-namespaces.py` 已移除本机绝对路径默认值,并会把文件处理失败汇总成非零退出码
|
- `refactor-scripts/update-namespaces.py` 已移除本机绝对路径默认值,并会把文件处理失败汇总成非零退出码
|
||||||
|
- PR #314 最新 review 中,GodotLog watcher 释放、热重载回调阻塞、配置归一化、数字 `LogLevel` 校验、
|
||||||
|
模板缓存和脚本健壮性问题已在当前 head 验证为已处理;本轮只继续修仍适用的问题
|
||||||
|
- PR #314 最新 follow-up 中,`DeferredLogger` 格式化重载现在委托给 inner logger,`GodotLogger` 默认 options
|
||||||
|
provider 已改为构造时缓存,结构化属性会跳过空白 key 并使用 trimmed key
|
||||||
|
|
||||||
## 当前风险
|
## 当前风险
|
||||||
|
|
||||||
@ -69,25 +73,18 @@ GFramework 自身日志抽象不分叉”的稳定宿主层,并为后续 Godot
|
|||||||
|
|
||||||
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter FullyQualifiedName~GodotLog -nologo`
|
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter FullyQualifiedName~GodotLog -nologo`
|
||||||
- 结果:通过
|
- 结果:通过
|
||||||
- 备注:定向新增 Godot logging 配置 / 模板回归共 `11` 项通过
|
- 备注:RP-003 follow-up 后 Godot logging 定向测试共 `15` 项通过
|
||||||
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release -nologo`
|
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release -nologo`
|
||||||
- 结果:通过
|
- 结果:通过
|
||||||
- 备注:Godot 测试项目共 `69` 项通过
|
- 备注:RP-003 follow-up 后 Godot 测试项目共 `73` 项通过
|
||||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter FullyQualifiedName~Logging -nologo`
|
- `dotnet format GFramework.Godot.Tests/GFramework.Godot.Tests.csproj --verify-no-changes --no-restore --include ...`
|
||||||
- 结果:通过
|
- 结果:通过
|
||||||
- 备注:Core logging 相关测试共 `214` 项通过,覆盖 `AbstractLogger` 动态最小级别改造回归
|
- 备注:include 范围为本轮修改的 C# 文件;全项目 format 仍命中既有行尾 / 编码问题,详见 trace
|
||||||
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter FullyQualifiedName~GodotLog -nologo`
|
- 历史验证明细已保留在 [执行 trace](../traces/godot-logging-compliance-polish-trace.md) 的 `RP-001 验证` 与
|
||||||
- 结果:通过
|
`RP-002 验证` 小节,active tracking 入口只保留当前恢复点相关结果
|
||||||
- 备注:PR review hardening 后 Godot logging 定向测试共 `14` 项通过
|
|
||||||
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release -nologo`
|
|
||||||
- 结果:通过
|
|
||||||
- 备注:PR review hardening 后 Godot 测试项目共 `72` 项通过
|
|
||||||
- `python3 -B refactor-scripts/update-namespaces.py --help`
|
|
||||||
- 结果:通过
|
|
||||||
- 备注:确认脚本 CLI 参数解析可用
|
|
||||||
|
|
||||||
## 下一步
|
## 下一步
|
||||||
|
|
||||||
1. 刷新 PR review / CI 状态,确认最新 head 上 CodeRabbit 与 Greptile 线程是否关闭或变为 stale
|
1. 提交 RP-003 review follow-up 改动
|
||||||
2. 若 CI 仍报 MegaLinter `dotnet-format` restore 失败,优先复核 Actions restore 环境,而不是继续改本地格式
|
2. 刷新 PR review / CI 状态,确认最新 head 上 CodeRabbit 与 Greptile 线程是否关闭或变为 stale
|
||||||
3. 后续若继续推进能力设计,再评估 Godot 输出是否应变成 Core 可组合 sink / appender
|
3. 若 CI 仍报 MegaLinter `dotnet-format` restore 失败,优先复核 Actions restore 环境,而不是继续改本地格式
|
||||||
|
|||||||
@ -38,7 +38,7 @@
|
|||||||
- 同步更新 `docs/zh-CN/godot/logging.md`,把文档结论从“只有薄适配层”刷新成“已具备宿主便利层和热重载语义”
|
- 同步更新 `docs/zh-CN/godot/logging.md`,把文档结论从“只有薄适配层”刷新成“已具备宿主便利层和热重载语义”
|
||||||
- 已从 `ai-libs/GodotLogger` 复制 MIT 许可证到 `third-party-licenses/GodotLogger/LICENSE`
|
- 已从 `ai-libs/GodotLogger` 复制 MIT 许可证到 `third-party-licenses/GodotLogger/LICENSE`
|
||||||
|
|
||||||
### 验证
|
### RP-001 验证
|
||||||
|
|
||||||
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter FullyQualifiedName~GodotLog -nologo`
|
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter FullyQualifiedName~GodotLog -nologo`
|
||||||
- 结果:通过(11/11)
|
- 结果:通过(11/11)
|
||||||
@ -47,7 +47,7 @@
|
|||||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter FullyQualifiedName~Logging -nologo`
|
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter FullyQualifiedName~Logging -nologo`
|
||||||
- 结果:通过(214/214)
|
- 结果:通过(214/214)
|
||||||
|
|
||||||
### 下一步
|
### RP-001 下一步
|
||||||
|
|
||||||
1. 若继续推进本主题,优先评估 Godot 输出是否应变成 Core 可组合 appender / sink
|
1. 若继续推进本主题,优先评估 Godot 输出是否应变成 Core 可组合 appender / sink
|
||||||
2. 若出现后续 review 反馈,直接在本 topic 追加 RP-002,而不是重新开临时 local-plan
|
2. 若出现后续 review 反馈,直接在本 topic 追加 RP-002,而不是重新开临时 local-plan
|
||||||
@ -68,17 +68,52 @@
|
|||||||
- 同步补充 Godot logging 内部类型和关键方法 XML 文档,说明热重载、快照发布、分类匹配和模板缓存语义
|
- 同步补充 Godot logging 内部类型和关键方法 XML 文档,说明热重载、快照发布、分类匹配和模板缓存语义
|
||||||
- 同步更新 `docs/zh-CN/godot/logging.md`,记录 `ConfigurationPath` 的诊断语义和 `Shutdown()` teardown 用法
|
- 同步更新 `docs/zh-CN/godot/logging.md`,记录 `ConfigurationPath` 的诊断语义和 `Shutdown()` teardown 用法
|
||||||
|
|
||||||
### 验证
|
### RP-002 验证
|
||||||
|
|
||||||
|
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter FullyQualifiedName~GodotLog -nologo`
|
||||||
|
- 结果:通过(15/15)
|
||||||
|
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release -nologo`
|
||||||
|
- 结果:通过(73/73)
|
||||||
|
- `python3 -B refactor-scripts/update-namespaces.py --help`
|
||||||
|
- 结果:通过
|
||||||
|
|
||||||
|
### RP-002 下一步
|
||||||
|
|
||||||
|
1. 提交 RP-002 review hardening 改动
|
||||||
|
2. 刷新 PR review / CI,确认最新 head 是否关闭已处理线程
|
||||||
|
3. 若 CI 仍只有 MegaLinter `dotnet-format` restore 失败,优先定位 Actions restore 环境
|
||||||
|
|
||||||
|
## 2026-05-03
|
||||||
|
|
||||||
|
### 阶段:PR review follow-up(RP-003)
|
||||||
|
|
||||||
|
- 再次使用 `$gframework-pr-review` 抓取 PR #314 最新 review payload,确认当前 head 上仍有 CodeRabbit 与
|
||||||
|
Greptile 未解决线程
|
||||||
|
- 本轮验证后接受并处理仍适用的 review 结论:
|
||||||
|
- `GodotLoggerSettingsLoaderTests` 公开测试类型与公开测试方法需要 XML 文档
|
||||||
|
- `DeferredLogger` 的公开接口成员需要 XML 文档,并且格式化 `Log(...)` 重载不应提前执行 `string.Format`
|
||||||
|
- `GodotLogger` 默认构造器不应在每条日志上重新创建 options,结构化属性 key 需要跳过空白并做 trim
|
||||||
|
- Godot logging 文档需要给出最小 `appsettings.json` 示例、放置约定和热重载覆盖说明
|
||||||
|
- active tracking 不应同时保留 RP-001 与 RP-002 的详细验证计数,trace 重复标题需要消除
|
||||||
|
- 本轮验证后确认以下旧 review 结论在当前 head 已处理,无需重复改动:
|
||||||
|
- `GodotLog.Shutdown()` 已可释放 materialized configuration source 的 watcher
|
||||||
|
- hot-reload callback 已走无 `Thread.Sleep` 的 `LoadSettings()`,`Thread.Sleep` 只保留在 startup strict load retry
|
||||||
|
- JSON options 归一化、数字 `LogLevel` 校验、GodotLogTemplate 缓存和 namespace 脚本健壮性已在当前 head 存在
|
||||||
|
|
||||||
|
### RP-003 验证
|
||||||
|
|
||||||
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter FullyQualifiedName~GodotLog -nologo`
|
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter FullyQualifiedName~GodotLog -nologo`
|
||||||
- 结果:通过(14/14)
|
- 结果:通过(14/14)
|
||||||
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release -nologo`
|
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release -nologo`
|
||||||
- 结果:通过(72/72)
|
- 结果:通过(72/72)
|
||||||
- `python3 -B refactor-scripts/update-namespaces.py --help`
|
- `dotnet format GFramework.Godot.Tests/GFramework.Godot.Tests.csproj --verify-no-changes --no-restore --include ...`
|
||||||
- 结果:通过
|
- 结果:通过,include 范围为本轮修改的三个 C# 文件
|
||||||
|
- `dotnet format GFramework.Godot.Tests/GFramework.Godot.Tests.csproj --verify-no-changes --no-restore`
|
||||||
|
- 结果:未通过;命中既有 `GFramework.Godot.Tests/Coroutine/GodotTimeSourceTests.cs` 行尾与
|
||||||
|
`GFramework.Godot.Tests/GlobalUsings.cs` 编码问题,本轮未把该历史格式清理并入 PR review follow-up
|
||||||
|
|
||||||
### 下一步
|
### RP-003 下一步
|
||||||
|
|
||||||
1. 提交 RP-002 review hardening 改动
|
1. 提交 RP-003 review follow-up 改动
|
||||||
2. 刷新 PR review / CI,确认最新 head 是否关闭已处理线程
|
2. 刷新 PR review,确认 CodeRabbit / Greptile 线程是否关闭或 stale
|
||||||
3. 若 CI 仍只有 MegaLinter `dotnet-format` restore 失败,优先定位 Actions restore 环境
|
3. 若 CI 仍只有 MegaLinter `dotnet-format` restore 失败,继续定位 Actions restore 环境而不是扩大本地格式清理范围
|
||||||
|
|||||||
@ -95,6 +95,47 @@ var logger = GodotLog.CreateLogger<Main>();
|
|||||||
`GodotLog.Configure(...)` 失效。长生命周期服务器或测试宿主如果需要在退出时主动释放配置文件 watcher,可以调用
|
`GodotLog.Configure(...)` 失效。长生命周期服务器或测试宿主如果需要在退出时主动释放配置文件 watcher,可以调用
|
||||||
`GodotLog.Shutdown()`;它会停止热重载监听,已创建 logger 仍然继续使用最后一次成功发布的配置快照。
|
`GodotLog.Shutdown()`;它会停止热重载监听,已创建 logger 仍然继续使用最后一次成功发布的配置快照。
|
||||||
|
|
||||||
|
最小可复制的 `appsettings.json` 可以只包含 `Logging` 根节点。`LogLevel` 使用 `Default` 和类别名控制过滤阈值,
|
||||||
|
`GodotLogger` 控制 Godot 输出模式、模板和颜色:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Info",
|
||||||
|
"Game.Services": "Debug"
|
||||||
|
},
|
||||||
|
"GodotLogger": {
|
||||||
|
"Mode": "Debug",
|
||||||
|
"DebugMinLevel": "Debug",
|
||||||
|
"ReleaseMinLevel": "Info",
|
||||||
|
"DebugOutputTemplate": "[{timestamp:HH:mm:ss.fff}] [color={color}][{level:u3}][/color] [{category:l16}] {message}{properties}",
|
||||||
|
"ReleaseOutputTemplate": "[{timestamp:HH:mm:ss.fff}] [{level:u3}] [{category:l16}] {message}{properties}",
|
||||||
|
"Colors": {
|
||||||
|
"Info": "white",
|
||||||
|
"Warning": "orange",
|
||||||
|
"Error": "red"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
配置文件发现顺序固定为:
|
||||||
|
|
||||||
|
1. `GODOT_LOGGER_CONFIG` 指向的文件
|
||||||
|
2. 导出程序或测试进程所在目录的 `appsettings.json`
|
||||||
|
3. Godot 项目资源根目录的 `res://appsettings.json`
|
||||||
|
|
||||||
|
在编辑器项目里,`res://appsettings.json` 放在项目根目录;在导出包或专用服务器里,优先把
|
||||||
|
`appsettings.json` 放到可执行文件同目录,便于运维脚本替换。运行中修改已发现的配置文件会热重载
|
||||||
|
`Logging:LogLevel` 与 `Logging:GodotLogger` 下的模式、最小级别、模板和颜色;已创建 logger 不会重新实例化,
|
||||||
|
但下一次级别判定和写入会读取最新成功发布的配置快照。热重载解析失败或文件被短暂锁定时会保留上一份可用配置。
|
||||||
|
|
||||||
|
`GodotLog.Configure(...)` 适合在没有配置文件或需要代码覆盖默认值时使用,并且必须在首次创建 provider 或配置源前调用。
|
||||||
|
`GodotLog.ConfigurationPath` 适合启动诊断和测试断言;`GodotLog.Shutdown()` 适合测试 teardown 或长生命周期服务器退出时释放
|
||||||
|
文件 watcher,不会清空已经发布给 logger 的最后一份配置。
|
||||||
|
|
||||||
## 最小接入路径
|
## 最小接入路径
|
||||||
|
|
||||||
### 1. 在 `ArchitectureConfiguration` 中挂上 Godot provider
|
### 1. 在 `ArchitectureConfiguration` 中挂上 Godot provider
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user