IgdrasilLogging — Система логирования

IgdrasilLogging — простая и гибкая система логирования для отладки и мониторинга игры. Поддерживает различные уровни логирования, множественные выводы (консоль, файлы), цветное форматирование и контексты.

Использование в движке

При работе с Igdrasil Engine вам не нужно создавать LoggingContext вручную — движок автоматически инициализирует систему логирования при запуске.

Получение логгера

Используйте статический метод Igdrasil.GetLogger():

using IgdrasilEngine.Engine;

// Получаем логгер для своей системы
var logger = Igdrasil.GetLogger("MyGameSystem");

logger.Info("Система инициализирована");
logger.Warning("Что-то пошло не так");

Каждый вызов GetLogger создает новый логгер с указанным тегом, но все логгеры используют один общий контекст движка.

Настройка уровня логирования

Управляйте уровнем через свойство Igdrasil.LogLevel:

// По умолчанию: LoggingLevel.Default (Info, Warning, Error, Fatal, Wtf)
Igdrasil.LogLevel = LoggingLevel.All; // включить Debug

#if DEBUG
    Igdrasil.LogLevel = LoggingLevel.All;
#else
    Igdrasil.LogLevel = LoggingLevel.OnlyErrors;
#endif

Автоматическое логирование в файлы

При инициализации движок автоматически:

  1. Создает директорию logs/ в корне проекта
  2. Логирует в logs/latest.log (по умолчанию) — файл перезаписывается при каждом запуске
  3. Архивирует старые логи — когда накапливается 25+ файлов, создается ZIP-архив logs/archive_<дата>.zip
// Создавать новый файл при каждом запуске (вместо latest.log)
Igdrasil.InitializeWindow(options, onlyLatestLog: false);
// Создаст logs/<дата и время>.log

Все логи автоматически пишутся в консоль и файл одновременно.

Добавление своих писателей

Если нужен дополнительный вывод (например, GUI-консоль):

var customWriter = new StringWriter();
Igdrasil.ModifyLoggerContext(customWriter);

// Теперь все логи идут в Console.Out, logs/latest.log и customWriter

Внутренние логгеры движка

Движок использует логгеры для своих систем:

// Главный логгер движка
var engineLogger = Igdrasil.Logger; // тег: "Igdrasil Engine"
engineLogger.Info("Ad astra abyssosque!"); // выводится при старте

// Логгеры автоматически назначаются разным модулям
ResourceHelper.Logger = Igdrasil.Logger;
AbstractGameEvent.Logger = Igdrasil.Logger;

Автоматическая обработка исключений

При вызове Igdrasil.Start(sendExceptions: true) движок автоматически:

Igdrasil.Start(
    hideConsole: false,     // показать консоль
    sendExceptions: true    // отправлять отчеты об ошибках
);
// При падении в logs/latest.log будет полный стек + состояние движка

Быстрый старт: логирование в консоль (без движка)

using IgdrasilLogging.Engine.Logging;

// Создаём контекст (куда писать логи)
using var context = new LoggingContext(Console.Out);

// Создаём логгер с тегом
var logger = LoggerFactory.CreateLogger("Game", context);

// Логируем
logger.Info("Игра запущена");
logger.Warning("Низкий FPS: {0}", 30);
logger.Error("Не удалось загрузить текстуру");

Вывод в консоли:

25-01-2026 14:32:15.123 - (Main Thread) [Game] INFO: Игра запущена
25-01-2026 14:32:16.456 - (Main Thread) [Game] WARN: Низкий FPS: 30
25-01-2026 14:32:17.789 - (Main Thread) [Game] ERROR: Не удалось загрузить текстуру

Создание логгеров для разных систем

Используйте теги для разделения логов разных систем:

// Контекст общий
var context = new LoggingContext(Console.Out);

// Логгеры для разных подсистем
var physicsLogger = LoggerFactory.CreateLogger("Physics", context);
var audioLogger = LoggerFactory.CreateLogger("Audio", context);
var networkLogger = LoggerFactory.CreateLogger("Network", context);

physicsLogger.Info("Collision detected");
audioLogger.Warning("Sound clip missing");
networkLogger.Error("Connection timeout");

Вывод:

[Physics] INFO: Collision detected
[Audio] WARN: Sound clip missing
[Network] ERROR: Connection timeout

Логирование в файл

Используйте StreamWriter для записи логов в файл:

var fileWriter = new StreamWriter("game.log", append: true);
var context = new LoggingContext(Console.Out, fileWriter);
var logger = LoggerFactory.CreateLogger("Game", context);

logger.Info("Игра запущена");
// Логи идут и в консоль, и в файл game.log

Добавление писателя после создания контекста:

var context = new LoggingContext(Console.Out);
var logger = LoggerFactory.CreateLogger("Game", context);

// Позже добавляем файл
var fileWriter = new StreamWriter("errors.log");
context.AddWriter(fileWriter);

logger.Error("Критическая ошибка"); // запишется в консоль + файл

Уровни логирования

Фильтруйте логи по важности через LoggingLevel:

var context = new LoggingContext(Console.Out);

// По умолчанию: Info, Warning, Error, Fatal, Wtf
context.Level = LoggingLevel.Default;

// Только критические
context.Level = LoggingLevel.OnlyCritical; // Warning, Error, Fatal

// Только ошибки
context.Level = LoggingLevel.OnlyErrors; // Error, Fatal

// Всё включая Debug
context.Level = LoggingLevel.All; // Debug, Info, Warning, Error, Fatal, Wtf

// Вообще ничего
context.Level = LoggingLevel.None;

Пример настройки для релизной сборки:

var context = new LoggingContext(Console.Out);
#if DEBUG
    context.Level = LoggingLevel.All; // в отладке всё
#else
    context.Level = LoggingLevel.OnlyErrors; // в релизе только ошибки
#endif

Методы логирования

Все уровни имеют одинаковый интерфейс:

// Простое сообщение
logger.Info("Сообщение");

// С форматированием (string.Format)
logger.Warning("FPS: {0}, Objects: {1}", fps, objectCount);

// Без сообщения (пустая строка)
logger.Debug();

Уровни:

Метод Описание Цвет Когда использовать
Debug Отладочная информация Синий Детальная трассировка, проверка значений
Info Информационные сообщения Белый Нормальные события (загрузка уровня, старт игры)
Warning Предупреждения Оранжевый Неоптимальные ситуации (низкий FPS, отсутствующий ресурс)
Error Ошибки Красный Ошибки с возможностью продолжения (не удалось загрузить текстуру)
Fatal Фатальные ошибки Тёмно-красный Критические ошибки (невозможно продолжить)
Wtf "Что за чёрт?" Фиолетовый Невозможные ситуации (проверка на всякий случай)

Форматирование сообщений

Поддерживается string.Format с произвольным числом параметров:

// Переменные
logger.Info("Player {0} joined", playerName);

// Несколько параметров
logger.Debug("Position: ({0}, {1}, {2})", x, y, z);

// Объекты (будет вызван ToString())
logger.Info("Loaded asset: {0}", assetObject);

// Числа с форматированием
logger.Info("Health: {0:0.00}%", health);

Когда что использовать

LoggingContext

Используйте: Один раз при инициализации игры, создайте глобальный контекст.
Не делайте: Не создавайте новый контекст для каждого логгера — используйте один на всё приложение.

LoggerFactory.CreateLogger

Используйте: Для каждой подсистемы создайте свой логгер с уникальным тегом.
Не делайте: Не создавайте логгеры в методах, вызываемых часто — кешируйте их в полях класса.

Уровень Debug

Используйте: Для трассировки алгоритмов, проверки значений в процессе разработки.
Не делайте: Не оставляйте Debug в production — они замедляют игру и захламляют логи.

Уровень Info

Используйте: Для важных событий игры (загрузка уровня, подключение к серверу, сохранение).
Не делайте: Не логируйте Info каждый кадр или в Update() — используйте Debug или вообще не логируйте.

Уровень Warning

Используйте: Когда что-то не так, но игра может продолжаться (fallback на дефолтные значения).
Не делайте: Не используйте Warning для нормальных ситуаций.

Уровень Error

Используйте: Когда операция не удалась (не загрузился файл, не установилось соединение).
Не делайте: Не используйте Error для предупреждений — они фильтруются отдельно.

Уровень Fatal

Используйте: Перед вылетом/закрытием игры при критической ошибке.
Не делайте: Не используйте Fatal для обычных ошибок — он предполагает невозможность продолжения.

Уровень Wtf

Используйте: В коде, который "никогда не должен выполниться" (например, default в switch с полным перечислением).
Не делайте: Не используйте Wtf вместо Error — это уровень для логической невозможности.

Типичные сценарии

Централизованная система логирования

public static class GameLogger
{
    private static LoggingContext? _context;
    
    public static ILogger Physics { get; private set; } = null!;
    public static ILogger Audio { get; private set; } = null!;
    public static ILogger Network { get; private set; } = null!;
    public static ILogger UI { get; private set; } = null!;
    
    public static void Initialize()
    {
        var fileWriter = new StreamWriter("game.log", append: true);
        _context = new LoggingContext(Console.Out, fileWriter);
        
        #if DEBUG
            _context.Level = LoggingLevel.All;
        #else
            _context.Level = LoggingLevel.OnlyCritical;
        #endif
        
        Physics = LoggerFactory.CreateLogger("Physics", _context);
        Audio = LoggerFactory.CreateLogger("Audio", _context);
        Network = LoggerFactory.CreateLogger("Network", _context);
        UI = LoggerFactory.CreateLogger("UI", _context);
    }
    
    public static void Shutdown()
    {
        _context?.Dispose();
    }
}

// Использование в игре
GameLogger.Initialize();
GameLogger.Physics.Info("Physics engine initialized");
GameLogger.Audio.Warning("Sound device not found, using default");
GameLogger.Shutdown();

Логирование ошибок с fallback

var logger = LoggerFactory.CreateLogger("AssetLoader", context);

Texture? LoadTexture(string path)
{
    try
    {
        var texture = Texture.Load(path);
        logger.Info("Loaded texture: {0}", path);
        return texture;
    }
    catch (FileNotFoundException ex)
    {
        logger.Warning("Texture not found: {0}, using default", path);
        return Texture.DefaultTexture;
    }
    catch (Exception ex)
    {
        logger.Error("Failed to load texture {0}: {1}", path, ex.Message);
        return null;
    }
}

Раздельные логи для разных этапов

// Лог загрузки
using var bootContext = new LoggingContext(new StreamWriter("boot.log"));
var bootLogger = LoggerFactory.CreateLogger("Boot", bootContext);
bootLogger.Info("Starting game...");

// Лог игровой сессии
using var gameContext = new LoggingContext(Console.Out, new StreamWriter("session.log"));
gameContext.Level = LoggingLevel.Default;
var gameLogger = LoggerFactory.CreateLogger("Game", gameContext);
gameLogger.Info("Player joined");

Отладочные утверждения

void ProcessEntity(Entity entity)
{
    if (entity == null)
    {
        logger.Wtf("Entity is null in ProcessEntity — should never happen!");
        return;
    }
    
    if (entity.Health < 0)
    {
        logger.Warning("Entity {0} has negative health: {1}", entity.Name, entity.Health);
        entity.Health = 0;
    }
    
    logger.Debug("Processing entity {0}", entity.Name);
}

Архитектура


Проект: Igdrasil Engine
Автор: Alexander Izmailov
Собственность: Igdrasil Project
Версия: 1.0
Лицензия: Proprietary License

© 2026 Alexander Izmailov. Все права защищены.
Этот программный продукт является собственностью студии Igdrasil Project.