// TraceLogger.cs
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors
// https://github.com/gnh1201/welsonjs
//
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 _logFilePath;
static TraceLogger()
{
try
{
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)
{
Trace.Listeners.Add(new ConsoleTraceListener());
Trace.TraceWarning($"TraceLogger: failed to open log file. Using console. Error: {ex.Message}");
}
Trace.AutoFlush = true;
}
public void Info(params object[] args) => Trace.TraceInformation(Format(args));
public void Warn(params object[] args) => Trace.TraceWarning(Format(args));
public void Error(params object[] args) => Trace.TraceError(Format(args));
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;
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() ?? "")); }
}
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);
}
}
}