Explorar el Código

feat: implement InterpolatedStringHandler for Logger, replace Func<string> lambda overloads

Agent-Logs-Url: https://github.com/Meloong-Git/MeloongCore/sessions/186510eb-a481-4e72-b83d-a3e77c103fc6

Co-authored-by: LTCatt <[email protected]>
copilot-swe-agent[bot] hace 5 meses
padre
commit
4e07bcf17a
Se han modificado 2 ficheros con 165 adiciones y 14 borrados
  1. 161 10
      Shared/General/Logger.cs
  2. 4 4
      Shared/Utils/FileUtils.cs

+ 161 - 10
Shared/General/Logger.cs

@@ -53,16 +53,16 @@ public static class Logger {
     public static void Error(string message, LogBehavior behavior = LogBehavior.AlertThenFeedback, [CallerFilePath] string filePath = "") 
         => Log(message, LogLevel.Error, behavior, filePath);
 
-    public static void Log(Func<string> messageGetter, LogLevel level = LogLevel.Info, LogBehavior behavior = LogBehavior.None, [CallerFilePath] string filePath = "") {
-        if (Instance.MinLevel <= level) Instance.Log(messageGetter(), level, behavior, filePath); }
-    public static void Trace(Func<string> messageGetter, LogBehavior behavior = LogBehavior.None, [CallerFilePath] string filePath = "") 
-        => Log(messageGetter, LogLevel.Trace, behavior, filePath);
-    public static void Info(Func<string> messageGetter, LogBehavior behavior = LogBehavior.None, [CallerFilePath] string filePath = "") 
-        => Log(messageGetter, LogLevel.Info, behavior, filePath);
-    public static void Warn(Func<string> messageGetter, LogBehavior behavior = LogBehavior.ToastIfDebug, [CallerFilePath] string filePath = "") 
-        => Log(messageGetter, LogLevel.Warn, behavior, filePath);
-    public static void Error(Func<string> messageGetter, LogBehavior behavior = LogBehavior.AlertThenFeedback, [CallerFilePath] string filePath = "") 
-        => Log(messageGetter, LogLevel.Error, behavior, filePath);
+    public static void Log(LogLevel level, [InterpolatedStringHandlerArgument("level")] ref LogInterpolatedStringHandler handler, LogBehavior behavior = LogBehavior.None, [CallerFilePath] string filePath = "") {
+        if (handler.IsEnabled) Instance.Log(handler.ToStringAndClear(), level, behavior, filePath); }
+    public static void Trace(ref TraceLogInterpolatedStringHandler handler, LogBehavior behavior = LogBehavior.None, [CallerFilePath] string filePath = "") {
+        if (handler.IsEnabled) Instance.Log(handler.ToStringAndClear(), LogLevel.Trace, behavior, filePath); }
+    public static void Info(ref InfoLogInterpolatedStringHandler handler, LogBehavior behavior = LogBehavior.None, [CallerFilePath] string filePath = "") {
+        if (handler.IsEnabled) Instance.Log(handler.ToStringAndClear(), LogLevel.Info, behavior, filePath); }
+    public static void Warn(ref WarnLogInterpolatedStringHandler handler, LogBehavior behavior = LogBehavior.ToastIfDebug, [CallerFilePath] string filePath = "") {
+        if (handler.IsEnabled) Instance.Log(handler.ToStringAndClear(), LogLevel.Warn, behavior, filePath); }
+    public static void Error(ref ErrorLogInterpolatedStringHandler handler, LogBehavior behavior = LogBehavior.AlertThenFeedback, [CallerFilePath] string filePath = "") {
+        if (handler.IsEnabled) Instance.Log(handler.ToStringAndClear(), LogLevel.Error, behavior, filePath); }
 
     public static void Log(Exception ex, string? message = null, LogLevel level = LogLevel.Warn, LogBehavior behavior = LogBehavior.ToastIfDebug, [CallerFilePath] string filePath = "") 
         => Instance.Log(ex, message, level, behavior, filePath);
@@ -156,6 +156,157 @@ public class BaseLogger {
 
 }
 
+/// <summary>
+/// 用于 <see cref="Logger.Log(LogLevel, ref LogInterpolatedStringHandler, LogBehavior, string)"/> 的内插字符串处理器。
+/// 仅当日志等级满足要求时才格式化字符串,避免不必要的字符串拼接开销。
+/// </summary>
+[InterpolatedStringHandler]
+public ref struct LogInterpolatedStringHandler {
+    private StringBuilder? _sb;
+
+    /// <summary>当前日志等级是否满足输出要求。</summary>
+    public readonly bool IsEnabled;
+
+    public LogInterpolatedStringHandler(int literalLength, int formattedCount, LogLevel level, out bool isEnabled) {
+        IsEnabled = isEnabled = Logger.Instance.MinLevel <= level;
+        _sb = IsEnabled ? new StringBuilder(literalLength) : null;
+    }
+
+    /// <summary>追加字面量字符串片段。</summary>
+    public void AppendLiteral(string value) => _sb?.Append(value);
+    /// <summary>追加格式化值。</summary>
+    public void AppendFormatted<T>(T value) => _sb?.Append(value?.ToString());
+    /// <summary>追加带格式字符串的格式化值。</summary>
+    public void AppendFormatted<T>(T value, string? format)
+        => _sb?.Append(value is IFormattable formattable ? formattable.ToString(format, null) : value?.ToString());
+    /// <summary>追加带对齐宽度的格式化值。</summary>
+    public void AppendFormatted<T>(T value, int alignment) {
+        if (_sb is null) return;
+        var s = value?.ToString() ?? string.Empty;
+        _sb.Append(alignment > 0 ? s.PadLeft(alignment) : alignment < 0 ? s.PadRight(-alignment) : s);
+    }
+    /// <summary>追加带对齐宽度和格式字符串的格式化值。</summary>
+    public void AppendFormatted<T>(T value, int alignment, string? format) {
+        if (_sb is null) return;
+        var s = value is IFormattable formattable ? formattable.ToString(format, null) : value?.ToString() ?? string.Empty;
+        _sb.Append(alignment > 0 ? s.PadLeft(alignment) : alignment < 0 ? s.PadRight(-alignment) : s);
+    }
+    /// <summary>追加字符串值。</summary>
+    public void AppendFormatted(string? value) => _sb?.Append(value);
+    /// <summary>追加带对齐宽度和格式字符串的对象值。</summary>
+    public void AppendFormatted(object? value, int alignment = 0, string? format = null) {
+        if (_sb is null) return;
+        var s = value is IFormattable formattable ? formattable.ToString(format, null) : value?.ToString() ?? string.Empty;
+        _sb.Append(alignment > 0 ? s.PadLeft(alignment) : alignment < 0 ? s.PadRight(-alignment) : s);
+    }
+
+    internal string ToStringAndClear() {
+        var result = _sb?.ToString() ?? string.Empty;
+        _sb = null;
+        return result;
+    }
+}
+
+/// <summary>用于 <see cref="Logger.Trace(ref TraceLogInterpolatedStringHandler, LogBehavior, string)"/> 的内插字符串处理器。</summary>
+[InterpolatedStringHandler]
+public ref struct TraceLogInterpolatedStringHandler {
+    private LogInterpolatedStringHandler _inner;
+    /// <inheritdoc cref="LogInterpolatedStringHandler.IsEnabled"/>
+    public bool IsEnabled => _inner.IsEnabled;
+    public TraceLogInterpolatedStringHandler(int literalLength, int formattedCount, out bool isEnabled)
+        => _inner = new(literalLength, formattedCount, LogLevel.Trace, out isEnabled);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendLiteral"/>
+    public void AppendLiteral(string value) => _inner.AppendLiteral(value);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T)"/>
+    public void AppendFormatted<T>(T value) => _inner.AppendFormatted(value);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, string?)"/>
+    public void AppendFormatted<T>(T value, string? format) => _inner.AppendFormatted(value, format);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, int)"/>
+    public void AppendFormatted<T>(T value, int alignment) => _inner.AppendFormatted(value, alignment);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, int, string?)"/>
+    public void AppendFormatted<T>(T value, int alignment, string? format) => _inner.AppendFormatted(value, alignment, format);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted(string?)"/>
+    public void AppendFormatted(string? value) => _inner.AppendFormatted(value);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted(object?, int, string?)"/>
+    public void AppendFormatted(object? value, int alignment = 0, string? format = null) => _inner.AppendFormatted(value, alignment, format);
+    internal string ToStringAndClear() => _inner.ToStringAndClear();
+}
+
+/// <summary>用于 <see cref="Logger.Info(ref InfoLogInterpolatedStringHandler, LogBehavior, string)"/> 的内插字符串处理器。</summary>
+[InterpolatedStringHandler]
+public ref struct InfoLogInterpolatedStringHandler {
+    private LogInterpolatedStringHandler _inner;
+    /// <inheritdoc cref="LogInterpolatedStringHandler.IsEnabled"/>
+    public bool IsEnabled => _inner.IsEnabled;
+    public InfoLogInterpolatedStringHandler(int literalLength, int formattedCount, out bool isEnabled)
+        => _inner = new(literalLength, formattedCount, LogLevel.Info, out isEnabled);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendLiteral"/>
+    public void AppendLiteral(string value) => _inner.AppendLiteral(value);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T)"/>
+    public void AppendFormatted<T>(T value) => _inner.AppendFormatted(value);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, string?)"/>
+    public void AppendFormatted<T>(T value, string? format) => _inner.AppendFormatted(value, format);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, int)"/>
+    public void AppendFormatted<T>(T value, int alignment) => _inner.AppendFormatted(value, alignment);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, int, string?)"/>
+    public void AppendFormatted<T>(T value, int alignment, string? format) => _inner.AppendFormatted(value, alignment, format);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted(string?)"/>
+    public void AppendFormatted(string? value) => _inner.AppendFormatted(value);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted(object?, int, string?)"/>
+    public void AppendFormatted(object? value, int alignment = 0, string? format = null) => _inner.AppendFormatted(value, alignment, format);
+    internal string ToStringAndClear() => _inner.ToStringAndClear();
+}
+
+/// <summary>用于 <see cref="Logger.Warn(ref WarnLogInterpolatedStringHandler, LogBehavior, string)"/> 的内插字符串处理器。</summary>
+[InterpolatedStringHandler]
+public ref struct WarnLogInterpolatedStringHandler {
+    private LogInterpolatedStringHandler _inner;
+    /// <inheritdoc cref="LogInterpolatedStringHandler.IsEnabled"/>
+    public bool IsEnabled => _inner.IsEnabled;
+    public WarnLogInterpolatedStringHandler(int literalLength, int formattedCount, out bool isEnabled)
+        => _inner = new(literalLength, formattedCount, LogLevel.Warn, out isEnabled);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendLiteral"/>
+    public void AppendLiteral(string value) => _inner.AppendLiteral(value);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T)"/>
+    public void AppendFormatted<T>(T value) => _inner.AppendFormatted(value);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, string?)"/>
+    public void AppendFormatted<T>(T value, string? format) => _inner.AppendFormatted(value, format);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, int)"/>
+    public void AppendFormatted<T>(T value, int alignment) => _inner.AppendFormatted(value, alignment);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, int, string?)"/>
+    public void AppendFormatted<T>(T value, int alignment, string? format) => _inner.AppendFormatted(value, alignment, format);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted(string?)"/>
+    public void AppendFormatted(string? value) => _inner.AppendFormatted(value);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted(object?, int, string?)"/>
+    public void AppendFormatted(object? value, int alignment = 0, string? format = null) => _inner.AppendFormatted(value, alignment, format);
+    internal string ToStringAndClear() => _inner.ToStringAndClear();
+}
+
+/// <summary>用于 <see cref="Logger.Error(ref ErrorLogInterpolatedStringHandler, LogBehavior, string)"/> 的内插字符串处理器。</summary>
+[InterpolatedStringHandler]
+public ref struct ErrorLogInterpolatedStringHandler {
+    private LogInterpolatedStringHandler _inner;
+    /// <inheritdoc cref="LogInterpolatedStringHandler.IsEnabled"/>
+    public bool IsEnabled => _inner.IsEnabled;
+    public ErrorLogInterpolatedStringHandler(int literalLength, int formattedCount, out bool isEnabled)
+        => _inner = new(literalLength, formattedCount, LogLevel.Error, out isEnabled);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendLiteral"/>
+    public void AppendLiteral(string value) => _inner.AppendLiteral(value);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T)"/>
+    public void AppendFormatted<T>(T value) => _inner.AppendFormatted(value);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, string?)"/>
+    public void AppendFormatted<T>(T value, string? format) => _inner.AppendFormatted(value, format);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, int)"/>
+    public void AppendFormatted<T>(T value, int alignment) => _inner.AppendFormatted(value, alignment);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, int, string?)"/>
+    public void AppendFormatted<T>(T value, int alignment, string? format) => _inner.AppendFormatted(value, alignment, format);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted(string?)"/>
+    public void AppendFormatted(string? value) => _inner.AppendFormatted(value);
+    /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted(object?, int, string?)"/>
+    public void AppendFormatted(object? value, int alignment = 0, string? format = null) => _inner.AppendFormatted(value, alignment, format);
+    internal string ToStringAndClear() => _inner.ToStringAndClear();
+}
+
 /// <summary>
 /// 将日志输出到 <see cref="Debug"/> 并写入指定的文件夹,最多保留 5 个文件。
 /// 不实现 <see cref="LogBehavior"/>。

+ 4 - 4
Shared/Utils/FileUtils.cs

@@ -26,7 +26,7 @@ public static class FileUtils {
     /// </summary>
     public static void Write(string filePath, byte[] content) {
         DirectoryUtils.Create(filePath, isFilePath: true);
-        Logger.Trace(() => $"写入文件:{filePath}({content.Length} 字节)");
+        Logger.Trace($"写入文件:{filePath}({content.Length} 字节)");
         File.WriteAllBytes(PathUtils.WithLongPath(filePath), content);
     }
 
@@ -38,7 +38,7 @@ public static class FileUtils {
     public static void Write(string filePath, Stream stream) {
         using FileStream fileStream = FileUtils.CreateAsStream(filePath);
         if (stream.CanSeek && stream.Position != 0) stream.Seek(0, SeekOrigin.Begin);
-        Logger.Trace(() => $"写入文件:{filePath}({stream.GetType().Name}{(stream.CanSeek ? $" {stream.Length} 字节" : "")})");
+        Logger.Trace($"写入文件:{filePath}({stream.GetType().Name}{(stream.CanSeek ? $" {stream.Length} 字节" : "")})");
         stream.CopyTo(fileStream);
     }
 
@@ -155,7 +155,7 @@ public static class FileUtils {
     /// </summary>
     public static ZipArchive OpenZip(string zipFilePath) {
         ZipArchive TryOpen(Encoding encoding) {
-            Logger.Trace(() => $"尝试以 {encoding.EncodingName} 编码打开压缩包:{zipFilePath}");
+            Logger.Trace($"尝试以 {encoding.EncodingName} 编码打开压缩包:{zipFilePath}");
             var result = ZipFile.Open(PathUtils.WithLongPath(zipFilePath), ZipArchiveMode.Read, encoding);
             try {
                 _ = result.Entries; // 如果编码有误,会在这里抛出 DecoderFallbackException;如果文件异常,会在这里抛出 InvalidDataException
@@ -251,7 +251,7 @@ public static class FileUtils {
         DirectoryUtils.Create(outputFullPath, isFilePath: true);
         FileUtils.Delete(outputFullPath);
         using var archive = ZipFile.Open(outputFullPath, ZipArchiveMode.Create);
-        Logger.Trace(() => $"创建 zip 文件:{sources.Count} 个文件 → {outputFullPath}\n{sources.Select(p => $"- {p.Value} → {p.Key}").Join('\n')}");
+        Logger.Trace($"创建 zip 文件:{sources.Count} 个文件 → {outputFullPath}\n{sources.Select(p => $"- {p.Value} → {p.Key}").Join('\n')}");
         foreach (var pair in sources) archive.CreateEntryFromFile(PathUtils.WithLongPath(pair.Value), pair.Key.Replace('\\', '/'), CompressionLevel.Optimal);
     }