From eb304aa4c7aa2867cddc0849626a705ff024cff5 Mon Sep 17 00:00:00 2001 From: maj00r Date: Mon, 31 Aug 2026 00:44:36 +0200 Subject: [PATCH] log avalonia warnings and write bledy.txt as it goes --- StarterNG/Infrastructure/Diagnostics.cs | 79 +++++++++++++++-- .../Infrastructure/DiagnosticsLogSink.cs | 85 +++++++++++++++++++ StarterNG/Program.cs | 10 ++- 3 files changed, 164 insertions(+), 10 deletions(-) create mode 100644 StarterNG/Infrastructure/DiagnosticsLogSink.cs diff --git a/StarterNG/Infrastructure/Diagnostics.cs b/StarterNG/Infrastructure/Diagnostics.cs index 2630d7b..2a70e20 100644 --- a/StarterNG/Infrastructure/Diagnostics.cs +++ b/StarterNG/Infrastructure/Diagnostics.cs @@ -15,6 +15,9 @@ public static class Diagnostics private static readonly object Gate = new(); private static readonly List Lines = new(); + private static StreamWriter? _writer; + private static bool _writerFailed; + public static string LogPath => Path.Combine(Directory.GetCurrentDirectory(), "starter", "bledy.txt"); @@ -25,13 +28,29 @@ public static class Diagnostics public static void Clear() { - lock (Gate) Lines.Clear(); + lock (Gate) + { + Lines.Clear(); + CloseWriter(); + try + { + if (File.Exists(LogPath)) + File.Delete(LogPath); + } + catch + { + } + } } public static void Log(string message) { + string line = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)} {message}"; lock (Gate) - Lines.Add($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)} {message}"); + { + Lines.Add(line); + Append(line); + } } public static void Log(IEnumerable messages) @@ -45,22 +64,66 @@ public static class Diagnostics public static void Flush() { - string[] snapshot; lock (Gate) { - if (Lines.Count == 0) return; - snapshot = Lines.ToArray(); + if (Lines.Count == 0) + return; + + try + { + _writer?.Flush(); + } + catch (Exception ex) + { + _writerFailed = true; + Lines.Add($"log flush failed: {ex.GetType().Name}: {ex.Message}"); + } + + if (!_writerFailed && _writer is not null) + return; + + try + { + Directory.CreateDirectory(Path.GetDirectoryName(LogPath)!); + File.WriteAllLines(LogPath, Lines); + } + catch + { + } } + } + + private static void Append(string line) + { + if (_writerFailed) + return; try { - string dir = Path.GetDirectoryName(LogPath)!; - Directory.CreateDirectory(dir); - File.WriteAllLines(LogPath, snapshot); + if (_writer is null) + { + Directory.CreateDirectory(Path.GetDirectoryName(LogPath)!); + _writer = new StreamWriter(LogPath, append: false) { AutoFlush = true }; + } + _writer.WriteLine(line); + } + catch + { + _writerFailed = true; + CloseWriter(); + } + } + + private static void CloseWriter() + { + try + { + _writer?.Dispose(); } catch { } + _writer = null; } private static Window? Owner => diff --git a/StarterNG/Infrastructure/DiagnosticsLogSink.cs b/StarterNG/Infrastructure/DiagnosticsLogSink.cs new file mode 100644 index 0000000..3d629a5 --- /dev/null +++ b/StarterNG/Infrastructure/DiagnosticsLogSink.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Logging; + +namespace StarterNG.Infrastructure; + +public sealed class DiagnosticsLogSink : ILogSink +{ + private const int MaxDistinctMessages = 200; + + private readonly LogEventLevel _minimumLevel; + private readonly HashSet _seen = new(StringComparer.Ordinal); + private readonly object _gate = new(); + private bool _capped; + + private DiagnosticsLogSink(LogEventLevel minimumLevel) => _minimumLevel = minimumLevel; + + public static void Install(LogEventLevel minimumLevel = LogEventLevel.Warning) => + Logger.Sink = new DiagnosticsLogSink(minimumLevel); + + public bool IsEnabled(LogEventLevel level, string area) => level >= _minimumLevel; + + public void Log(LogEventLevel level, string area, object? source, string messageTemplate) => + Write(level, area, source, messageTemplate, null); + + public void Log(LogEventLevel level, string area, object? source, string messageTemplate, + params object?[] propertyValues) => + Write(level, area, source, messageTemplate, propertyValues); + + private void Write(LogEventLevel level, string area, object? source, string messageTemplate, + object?[]? propertyValues) + { + if (!IsEnabled(level, area)) + return; + + string line = $"[Avalonia/{area}/{level}] {Format(messageTemplate, propertyValues)}"; + if (source is not null) + line += $" (source: {source.GetType().Name})"; + + lock (_gate) + { + if (_capped || !_seen.Add(line)) + return; + if (_seen.Count >= MaxDistinctMessages) + { + _capped = true; + Diagnostics.Log(line); + Diagnostics.Log($"[Avalonia] {MaxDistinctMessages} distinct messages logged, muting the rest"); + return; + } + } + + Diagnostics.Log(line); + } + + private static string Format(string template, object?[]? values) + { + if (values is null || values.Length == 0 || template.IndexOf('{') < 0) + return template; + + var sb = new StringBuilder(template.Length + 32); + int next = 0; + for (int i = 0; i < template.Length; i++) + { + if (template[i] != '{') + { + sb.Append(template[i]); + continue; + } + + int close = template.IndexOf('}', i); + if (close < 0) + { + sb.Append(template, i, template.Length - i); + break; + } + + sb.Append(next < values.Length ? values[next]?.ToString() ?? "null" : template[i..(close + 1)]); + next++; + i = close; + } + return sb.ToString(); + } +} diff --git a/StarterNG/Program.cs b/StarterNG/Program.cs index 463d4e6..f7d5eb7 100644 --- a/StarterNG/Program.cs +++ b/StarterNG/Program.cs @@ -5,7 +5,7 @@ using StarterNG.Infrastructure; namespace StarterNG; -class Program +static class Program { [STAThread] @@ -39,9 +39,15 @@ class Program } } + private static AppBuilder WithDiagnosticsLogging(this AppBuilder builder) + { + DiagnosticsLogSink.Install(); + return builder; + } + public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() .WithInterFont() - .LogToTrace(); + .WithDiagnosticsLogging(); }