From 2ce78b6c6599b3c99484a60999bc26a6a4be7135 Mon Sep 17 00:00:00 2001 From: Syed Mustafa Ahmed Naqvi Date: Mon, 1 Jun 2026 15:07:19 +0500 Subject: [PATCH] Add daily file logger target selection Adds backend/client daily log writing with separate log prefixes and target-aware log directories. --- Services/Logging/FileLogTarget.cs | 7 +++ Services/Logging/FileLogger.cs | 99 +++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 Services/Logging/FileLogTarget.cs create mode 100644 Services/Logging/FileLogger.cs diff --git a/Services/Logging/FileLogTarget.cs b/Services/Logging/FileLogTarget.cs new file mode 100644 index 0000000..f0de1fd --- /dev/null +++ b/Services/Logging/FileLogTarget.cs @@ -0,0 +1,7 @@ +namespace UtopiaCanteenSystem.Services.Logging; + +public enum FileLogTarget +{ + Backend, + Client +} diff --git a/Services/Logging/FileLogger.cs b/Services/Logging/FileLogger.cs new file mode 100644 index 0000000..673a963 --- /dev/null +++ b/Services/Logging/FileLogger.cs @@ -0,0 +1,99 @@ +using System.IO; +using System.Text; + +namespace UtopiaCanteenSystem.Services.Logging; + +/// +/// Thread-safe daily file logger. Never throws to callers. +/// +public static class FileLogger +{ + private static readonly object Lock = new(); + private static FileLogTarget? _target; + + public static bool IsConfigured => _target.HasValue; + + public static void ConfigureBackend() => Configure(FileLogTarget.Backend); + + public static void ConfigureClient() => Configure(FileLogTarget.Client); + + public static void Configure(FileLogTarget target) + { + _target = target; + var dir = GetLogsDirectory(); + try + { + Directory.CreateDirectory(dir); + } + catch + { + // Ignore + } + + Info("FileLogger", $"Logging initialized. Directory={dir}"); + } + + public static string GetLogsDirectory() => + _target == FileLogTarget.Client + ? LogPaths.ClientLogsDirectory + : LogPaths.BackendLogsDirectory; + + public static void Debug(string component, string message) => Write("DEBUG", component, message, null); + + public static void Info(string component, string message) => Write("INFO", component, message, null); + + public static void Warn(string component, string message, Exception? ex = null) => + Write("WARN", component, message, ex); + + public static void Error(string component, string message, Exception? ex = null) => + Write("ERROR", component, message, ex); + + private static void Write(string level, string component, string message, Exception? ex) + { + if (!_target.HasValue) + return; + + try + { + var dir = GetLogsDirectory(); + var prefix = _target == FileLogTarget.Client ? "client" : "backend"; + var file = Path.Combine(dir, $"{prefix}-{DateTime.Now:yyyy-MM-dd}.log"); + var line = FormatLine(level, component, message, ex); + + lock (Lock) + { + Directory.CreateDirectory(dir); + File.AppendAllText(file, line, Encoding.UTF8); + } + } + catch + { + // Never crash the app because logging failed. + } + } + + private static string FormatLine(string level, string component, string message, Exception? ex) + { + var sb = new StringBuilder(); + sb.Append($"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} [{level}] [{component}] {message}"); + if (ex != null) + { + sb.AppendLine(); + sb.Append($"Exception: {ex.GetType().Name}: {ex.Message}"); + if (!string.IsNullOrWhiteSpace(ex.StackTrace)) + { + sb.AppendLine(); + sb.Append(ex.StackTrace); + } + + if (ex.InnerException != null) + { + sb.AppendLine(); + sb.Append($"InnerException: {ex.InnerException.GetType().Name}: {ex.InnerException.Message}"); + } + } + + sb.AppendLine(); + return sb.ToString(); + } +}