namespace Supercell.Laser.Server { using Supercell.Laser.Titan.Debug; using System; using System.Collections.Concurrent; using System.IO; using System.Threading; public static class Logger { // Один фоновый писатель на каждый файл. Сетевые потоки только кладут строку // в неблокирующую очередь и сразу возвращаются — никакого диск-I/O в hot path, // никакой сериализации всех потоков через общий FileLock. private const long MaxFileSizeBytes = 50L * 1024 * 1024; // 50 МБ, потом ротация private const int MaxQueueLength = 100000; // защита от неограниченного роста очереди private sealed class FileWriter { private readonly string _path; private readonly BlockingCollection _queue; private readonly Thread _thread; private long _droppedCount; public FileWriter(string path) { _path = path; _queue = new BlockingCollection(new ConcurrentQueue(), MaxQueueLength); _thread = new Thread(Loop) { IsBackground = true, Name = "log-writer:" + Path.GetFileName(path) }; _thread.Start(); } public void Enqueue(string line) { // TryAdd не блокирует: если очередь переполнена (аномальный флуд логов), // строку роняем и считаем — это лучше, чем блокировать сетевые потоки. if (!_queue.TryAdd(line)) Interlocked.Increment(ref _droppedCount); } private void Loop() { StreamWriter writer = OpenWriter(); try { foreach (string line in _queue.GetConsumingEnumerable()) { writer.WriteLine(line); // Дренируем всё что накопилось, потом один Flush — батчинг записи. while (_queue.TryTake(out string next)) writer.WriteLine(next); long dropped = Interlocked.Exchange(ref _droppedCount, 0); if (dropped > 0) writer.WriteLine($"[{DateTime.Now}][LOGGER] Пропущено {dropped} строк лога (очередь переполнена)."); writer.Flush(); if (writer.BaseStream.Length >= MaxFileSizeBytes) writer = Rotate(writer); } } catch { // Писатель не должен ронять процесс из-за проблем с диском. } finally { try { writer?.Flush(); writer?.Dispose(); } catch { } } } private StreamWriter OpenWriter() { try { var fs = new FileStream(_path, FileMode.Append, FileAccess.Write, FileShare.Read); return new StreamWriter(fs) { AutoFlush = false }; } catch { return StreamWriter.Null; } } private StreamWriter Rotate(StreamWriter current) { try { current.Flush(); current.Dispose(); string rolled = _path + "." + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".old"; if (File.Exists(rolled)) File.Delete(rolled); File.Move(_path, rolled); } catch { // Не удалось ротировать — просто продолжаем писать в текущий (или новый) файл. } return OpenWriter(); } } private static readonly ConcurrentDictionary Writers = new(); private static void Write(string path, string line) { Writers.GetOrAdd(path, p => new FileWriter(p)).Enqueue(line); } // Только запись в server.log без вывода в консоль (консоль пишет вызывающий). internal static void WriteServerLog(string line) { Write(LogFile, line); } private const string LogFile = "./server.log"; public static void Init() { Console.ForegroundColor = ConsoleColor.Cyan; Debugger.SetListener(new DebuggerListener()); Write(LogFile, $"[{DateTime.Now}] Server started"); } public static void Print(string log) { string line = $"[{DateTime.Now}][DEBUG] {log}"; Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine("[DEBUG] " + log); Write(LogFile, line); } public static void Warning(string log) { string line = $"[{DateTime.Now}][WARNING] {log}"; Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("[WARNING] " + log); Write(LogFile, line); } public static void Error(string log) { string line = $"[{DateTime.Now}][ERROR] {log}"; Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("[ERROR] " + log); Write(LogFile, line); } public static void Log(string log) { string line = $"[{DateTime.Now}][WARN!!] {log}"; Write(LogFile, line); Write("./log.txt", line); } public static void BLog(string log) { Write("./battles.txt", $"[{DateTime.Now}] {log}"); } public static void CrashLogs(string log) { Write("./crash-logs.txt", $"[{DateTime.Now}] {log}"); } public static void HandleReport(string log) { Write("./chatreports.txt", $"[{DateTime.Now}] {log}"); } } public class DebuggerListener : IDebuggerListener { public void Error(string message) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("[LOGIC] Error: " + message); Logger.WriteServerLog($"[{DateTime.Now}][LOGIC] Error: {message}"); } public void Print(string message) { Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine("[LOGIC] Info: " + message); Logger.WriteServerLog($"[{DateTime.Now}][LOGIC] Info: {message}"); } public void Warning(string message) { Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("[LOGIC] Warning: " + message); Logger.WriteServerLog($"[{DateTime.Now}][LOGIC] Warning: {message}"); } } }