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);
}
}
}