From ce84aab861a2298793abce3582dbb6322457aaac Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Sun, 28 Sep 2025 19:59:45 +0900 Subject: [PATCH] Improve TraceLogger with file-based logging and fallback TraceLogger now writes logs to a file in %APPDATA%\WelsonJS\Logs with a randomized suffix and process ID. If the log directory cannot be created, it falls back to the current directory. The logger also improves formatting and random suffix generation for log file names. --- .../WelsonJS.Launcher/TraceLogger.cs | 80 ++++++++++++++----- 1 file changed, 60 insertions(+), 20 deletions(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/TraceLogger.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/TraceLogger.cs index 6efbaf6..bf94f28 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/TraceLogger.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/TraceLogger.cs @@ -1,34 +1,67 @@ -// TraceLogger.cs (WelsonJS.Launcher) +// TraceLogger.cs // SPDX-License-Identifier: GPL-3.0-or-later -// SPDX-FileCopyrightText: 2025 Namhyeon Go , Catswords OSS and WelsonJS Contributors +// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors // https://github.com/gnh1201/welsonjs // -// We use the ICompatibleLogger interface to maintain a BCL-first style. -// This allows for later replacement with logging libraries such as ILogger or Log4Net. -// using System; using System.Diagnostics; +using System.IO; using System.Linq; +using System.Security.Cryptography; namespace WelsonJS.Launcher { + /// + /// File-based trace logger. + /// Writes to %APPDATA%\WelsonJS\Logs\..pid<####>.log + /// Falls back to current directory if %APPDATA%\WelsonJS\Logs cannot be created. + /// public class TraceLogger : ICompatibleLogger { - private static readonly string _logFileName; + private static readonly string _logFilePath; static TraceLogger() { try { - _logFileName = (typeof(TraceLogger).Namespace ?? "WelsonJS.Launcher") + ".log"; - Trace.Listeners.Add(new TextWriterTraceListener(_logFileName)); + string ns = typeof(TraceLogger).Namespace ?? "WelsonJS.Launcher"; + string suffix = GenerateRandomSuffix(6); + int pid = Process.GetCurrentProcess().Id; + + // Try %APPDATA%\WelsonJS\Logs + string baseDir; + try + { + string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + baseDir = Path.Combine(appData, "WelsonJS", "Logs"); + Directory.CreateDirectory(baseDir); + } + catch + { + // Fallback: current directory + baseDir = AppDomain.CurrentDomain.BaseDirectory; + } + + _logFilePath = Path.Combine(baseDir, $"{ns}.{suffix}.pid{pid}.log"); + + var fs = new FileStream(_logFilePath, FileMode.Append, FileAccess.Write, FileShare.Read); + var writer = new StreamWriter(fs) { AutoFlush = true }; + + if (!Trace.Listeners.OfType().Any()) + { + Trace.Listeners.Add(new TextWriterTraceListener(writer) + { + Name = "FileTraceListener", + TraceOutputOptions = TraceOptions.DateTime + }); + } } catch (Exception ex) { - // Fallback when the process cannot write to the working directory Trace.Listeners.Add(new ConsoleTraceListener()); - Trace.TraceWarning($"TraceLogger: failed to initialize file listener '{_logFileName}'. Falling back to ConsoleTraceListener. Error: {ex.Message}"); + Trace.TraceWarning($"TraceLogger: failed to open log file. Using console. Error: {ex.Message}"); } + Trace.AutoFlush = true; } @@ -39,20 +72,27 @@ namespace WelsonJS.Launcher private static string Format(object[] args) { if (args == null || args.Length == 0) return string.Empty; + if (args.Length == 1) return args[0]?.ToString() ?? string.Empty; - if (args.Length == 1) - return args[0]?.ToString() ?? string.Empty; + string fmt = args[0]?.ToString() ?? string.Empty; + try { return string.Format(fmt, args.Skip(1).ToArray()); } + catch { return string.Join(" ", args.Select(a => a?.ToString() ?? "")); } + } - string format = args[0]?.ToString() ?? string.Empty; - try + private static string GenerateRandomSuffix(int length) + { + char[] buf = new char[length]; + byte[] rnd = new byte[length]; + + using (var rng = RandomNumberGenerator.Create()) { - return string.Format(format, args.Skip(1).ToArray()); - } - catch - { - // In case of mismatched format placeholders - return string.Join(" ", args); + rng.GetBytes(rnd); } + + for (int i = 0; i < length; i++) + buf[i] = (char)('a' + (rnd[i] % 26)); + + return new string(buf); } } }