Merge pull request #330 from gnh1201/dev
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
Deploy Jekyll with GitHub Pages dependencies preinstalled / build (push) Has been cancelled
Deploy Jekyll with GitHub Pages dependencies preinstalled / deploy (push) Has been cancelled

Improve TraceLogger with file-based logging and fallback
This commit is contained in:
Namhyeon Go 2025-09-28 21:38:49 +09:00 committed by GitHub
commit 058edb28eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,34 +1,67 @@
// TraceLogger.cs (WelsonJS.Launcher) // TraceLogger.cs
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Namhyeon Go <gnh1201@catswords.re.kr>, Catswords OSS and WelsonJS Contributors // SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors
// https://github.com/gnh1201/welsonjs // 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;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using System.Security.Cryptography;
namespace WelsonJS.Launcher namespace WelsonJS.Launcher
{ {
/// <summary>
/// File-based trace logger.
/// Writes to %APPDATA%\WelsonJS\Logs\<Namespace>.<random-6>.<PID>.log
/// Falls back to current directory if %APPDATA%\WelsonJS\Logs cannot be created.
/// </summary>
public class TraceLogger : ICompatibleLogger public class TraceLogger : ICompatibleLogger
{ {
private static readonly string _logFileName; private static readonly string _logFilePath;
static TraceLogger() static TraceLogger()
{ {
try try
{ {
_logFileName = (typeof(TraceLogger).Namespace ?? "WelsonJS.Launcher") + ".log"; string ns = typeof(TraceLogger).Namespace ?? "WelsonJS.Launcher";
Trace.Listeners.Add(new TextWriterTraceListener(_logFileName)); 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}.log");
if (!Trace.Listeners.OfType<TextWriterTraceListener>().Any())
{
var fs = new FileStream(_logFilePath, FileMode.Append, FileAccess.Write, FileShare.Read);
var writer = new StreamWriter(fs) { AutoFlush = true };
Trace.Listeners.Add(new TextWriterTraceListener(writer)
{
Name = "FileTraceListener",
TraceOutputOptions = TraceOptions.DateTime
});
}
} }
catch (Exception ex) catch (Exception ex)
{ {
// Fallback when the process cannot write to the working directory
Trace.Listeners.Add(new ConsoleTraceListener()); 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; Trace.AutoFlush = true;
} }
@ -38,21 +71,35 @@ namespace WelsonJS.Launcher
private static string Format(object[] args) private static string Format(object[] args)
{ {
if (args == null || args.Length == 0) return string.Empty; if (args == null || args.Length == 0)
return string.Empty;
if (args.Length == 1) if (args.Length == 1)
return args[0]?.ToString() ?? string.Empty; return args[0]?.ToString() ?? string.Empty;
string format = args[0]?.ToString() ?? string.Empty; string fmt = args[0]?.ToString() ?? string.Empty;
try try {
{ return string.Format(fmt, args.Skip(1).ToArray());
return string.Format(format, args.Skip(1).ToArray());
} }
catch catch {
{ return string.Join(" ", args.Select(a => a?.ToString() ?? ""));
// In case of mismatched format placeholders
return string.Join(" ", args);
} }
} }
private static string GenerateRandomSuffix(int length)
{
char[] buf = new char[length];
byte[] rnd = new byte[length];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(rnd);
}
for (int i = 0; i < length; i++)
buf[i] = (char)('a' + (rnd[i] % 26));
return new string(buf);
}
} }
} }