using System; using System.IO; using System.Linq; namespace axXez.TS3 { /// Severity level for log entries. public enum LogType { /// General informational message. Info, /// Detailed diagnostic message, only shown when verbose logging is enabled. Verbose, /// Something unexpected happened but execution continues. Warning, /// A failure or exception that requires attention. Error } internal static class GlobalLogger { public static bool EnableFileLogging { get; set; } = false; /// /// When true: log lines are shown and exceptions log their full stack trace. /// When false: verbose lines are suppressed and exceptions log only the message. /// public static bool Verbose { get; set; } = false; public static int RotationCount { get; set; } = 7; private static string _filePath = "./logs"; private static string _filePrefix = "bot"; private static string _logFilePath = ""; private static int _lastLogDay = -1; private static StreamWriter? _writer; private static readonly object _lock = new(); public static string FilePath { get => _filePath; set { lock (_lock) { _filePath = value; RebuildLogFilePath(); } } } public static string FilePrefix { get => _filePrefix; set { lock (_lock) { _filePrefix = value; RebuildLogFilePath(); } } } static GlobalLogger() => RebuildLogFilePath(); private static void RebuildLogFilePath() { DateTime now = DateTime.Now; _lastLogDay = now.DayOfYear; string fileName = $"{_filePrefix}_{now:yyyy-MM-dd}.log"; _logFilePath = Path.Combine(_filePath, fileName); _writer?.Flush(); _writer?.Dispose(); _writer = null; RotateLog(); } private static StreamWriter GetWriter() { if (_writer == null) { Directory.CreateDirectory(_filePath); _writer = new StreamWriter(_logFilePath, append: true) { AutoFlush = true }; } return _writer; } public static void RotateLog() { if (!Directory.Exists(_filePath)) return; var files = new DirectoryInfo(_filePath) .GetFiles($"{_filePrefix}_*.log") .OrderBy(f => f.Name) .ToArray(); int excess = files.Length - RotationCount; for (int i = 0; i < excess; i++) files[i].Delete(); } public static void Log(string source, string message, LogType type = LogType.Info) { if (type == LogType.Verbose && !Verbose) return; DateTime now = DateTime.Now; lock (_lock) { if (now.DayOfYear != _lastLogDay) RebuildLogFilePath(); string line = $"[{now:yyyy-MM-dd HH:mm:ss}][{source}] {message}"; Console.ForegroundColor = type switch { LogType.Verbose => ConsoleColor.Gray, LogType.Warning => ConsoleColor.Yellow, LogType.Error => ConsoleColor.Red, _ => ConsoleColor.White, }; Console.WriteLine(line); Console.ResetColor(); if (EnableFileLogging) GetWriter().WriteLine(line); } } /// /// Logs an exception as . /// Includes the full stack trace when is enabled, otherwise only the message. /// public static void LogError(string source, Exception ex) { Log(source, Verbose ? ex.ToString() : ex.Message, LogType.Error); } /// /// Logs a context message and an exception as . /// Includes the full stack trace when is enabled, otherwise only the exception message. /// public static void LogError(string source, string message, Exception ex) { Log(source, $"{message}: {(Verbose ? ex.ToString() : ex.Message)}", LogType.Error); } } }