Описание и API
Подробное описание работы мода логирования. Для краткого обзора - см. главную страницу, для подключения к своему моду - Интеграцию.
Состав мода
MPG_ModsLogger/
├── config.cpp # CfgPatches
└── Scripts/
├── 1_Core/enums.c # enum MPG_ModsLoggerLevel (TRACE..FATAL)
├── 3_Game/MPG_ModsLogger.c # класс MPG_ModsLogger
└── Common/defines.c # #define MPG_MODS_LOGGERЗачем 1_Core
enum MPG_ModsLoggerLevel объявлен в самом раннем скрипт-слое (engineScriptModule), чтобы компилироваться до всех остальных модулей. Без этого 3_Game-слой мода не подключается к компиляции и тип MPG_ModsLogger остаётся невидимым для потребителей.
Как это работает
Класс MPG_ModsLogger построен по фабричному паттерну: сам класс живёт в моде, а каждый мод-потребитель создаёт свой экземпляр с изолированными настройками и хранит его в собственном static ref. Несколько модов могут логировать параллельно - каждый в свой файл, со своим уровнем и директорией.
g_MY_Logger = new MPG_ModsLogger("MyMod", "$profile:MyMod/Logs", 3);Конструктор:
- открывает файл
<logDir>/<modName>_Log.log; - создаёт директорию и архивную папку (рекурсивно, с поддержкой
$profile:); - архивирует предыдущий лог (если файл уже существует);
- запускает таймер
CallLater(FlushBuffer, 500, true)- периодический сброс буфера.
Деструктор (~MPG_ModsLogger):
- снимает таймер;
- финальный flush буфера;
- пишет
Logger Closed: <timestamp>, закрывает файл.
Жизненный цикл экземпляра
Логгер создаётся в MissionServer.OnInit (после загрузки конфига потребителя - чтобы взять logLevel) и обнуляется в OnMissionFinish. Миссии перезапускаются без перезапуска процесса, поэтому обнуление обязательно - иначе статический ref переживёт миссию и укажет на «висячий» файловый дескриптор.
Возможности
Буферизация записи
Строки не пишутся на диск при каждом вызове. Накапливаются в m_Buffer и сбрасываются раз в 0.5с через CallLater (категория CALL_CATEGORY_SYSTEM). Это снижает нагрузку на диск при интенсивном логировании (например, TRACE при отладке спавна - сотни строк в секунду).
Исключение: сообщения уровней ERROR и FATAL сбрасывают буфер немедленно. Критичные ошибки не теряются при крахе процесса между плановыми flush'ами.
Шесть уровней логирования
| Уровень | Значение | Назначение | Flush |
|---|---|---|---|
TRACE | 1 | Детальная трассировка каждого шага | buffered |
DEBUG | 2 | Отладочная информация | buffered |
INFO | 3 | Информационные сообщения (по умолчанию) | buffered |
WARN | 4 | Предупреждения | buffered |
ERROR | 5 | Ошибки | немедленный |
FATAL | 6 | Критические ошибки | немедленный |
Фильтрация: сообщение пишется только если level >= m_MinLevel. m_MinLevel задаётся в конструкторе (обычно берётся из конфига потребителя). Это позволяет одной настройкой переключать детализацию: 1 - весь TRACE для глубокой отладки, 3 - рабочая информация, 5 - только ошибки.
Методы-предикаты IsTrace() … IsFatal() позволяют потребителю проверять, будет ли сообщение данного уровня записано, до формирования тяжёлой строки (экономия на форматировании).
Двойной вывод: файл + консоль
Каждое сообщение может дублироваться в серверную консоль (.RPT) через Print:
- флаг
consoleOutput(по умолчаниюtrue) - включить/выключить дублирование; consoleMinLevel(по умолчанию4= WARN) - начиная с какого уровня дублировать.
Типичный сценарий: файл хранит детальный лог (TRACE+), а в консоль падают только предупреждения и ошибки - чтобы не засорять .RPT.
Архивация и ротация
При старте логгера, если файл лога уже существует (от предыдущей сессии):
- он копируется в
LogArchives/с именем<дата>_<время>.log; - оригинал удаляется, начинается новый лог.
Старые архивы автоматически чистятся: хранится не более maxArchives (по умолчанию 500). Лишние (самые старые по сортировке) удаляются. Параметр задаётся в конструкторе.
Формат строки лога
<часы:минуты:секунды> <ticktime> [<УРОВЕНЬ>] [<ModName>] <сообщение>Пример:
14:23:07 00:15:42 [INFO ] [MPG_Spawner] Server started
14:23:09 00:15:44 [DEBUG] [MPG_Spawner] PointId: 5; spawn triggered
14:23:10 00:15:45 [WARN ] [MPG_Spawner] Low ammo: 3Два временных штампа:
- часы:минуты:секунды - реальное время (для сопоставления с другими логами);
- ticktime (
g_Game.GetTickTime()) - время с старта сервера (для измерения интервалов между событиями, не зависит от смены реального времени).
Временной префикс кэшируется по секунде: пересчёт строки происходит только когда секунда меняется, а не при каждой записи. При интенсивном логировании это экономит сотни вызовов GetHourMinuteSecond в секунду.
Хелпер FormatPosition
Статический метод MPG_ModsLogger.FormatPosition(vector) → "x, y, z". Удобно логировать координаты сущностей. Возвращает строку через запятую (читаемый формат), не через пробел - для лога, не для парсинга.
Преимущества
- Изоляция модов. Каждый мод пишет в свой файл (
MPG_Spawner_Log.log,MPG_BossHorde_Log.log), со своим уровнем и директорией. Логи не смешиваются в общем.RPT, искать проблемы конкретного мода проще. - Нет дублирования кода. До выделения в мод класс
MPG_ModsLoggerкопипастился в каждый мод. При установке двух модов с копией возникал конфликт определений класса - сервер не компилировался. Мод решает это: класс определён ровно один раз. - Опциональность. Потребитель компилируется и работает без мода - через
#ifdef MPG_ModsLogger. Если админ не поставил@MPG_ModsLogger, логирование деградируется доPrintв.RPT. Нет жёсткой зависимости. - Производительность. Буферизация (0.5с) снижает I/O при высокочастотном логировании. Кэширование временного префикса по секунде убирает избыточные вызовы. Немедленный flush для ERROR/FATAL гарантирует сохранность критичных данных.
- Управление объёмом. Архивация при каждом рестарте + ротация по
maxArchives- логи не разрастаются бесконечно. На серверах с автоперезапуском это критично: без ротации файл мог бы вырасти до гигабайтов за неделю. - Двойной контекст времени. Реальное время + ticktime в одной строке позволяют одновременно сопоставлять события с внешними логами и измерять внутренние интервалы.
Особенности и ограничения
- Только сервер. Мод -
type = "mod", но логирование серверное (потребитель создаёт логгер вMissionServer.OnInitподIsServer()). Клиенту логгер не нужен. - Зависимость от
g_Game. Таймер flush иGetTickTimeтребуют загруженного движка. Логгер нельзя создавать в статических инициализаторах или до старта миссии - только вMissionServer.OnInit. - Один файл на экземпляр. Перезапись (
FileMode.WRITE), не дозапись. При пересоздании логгера старый контент переносится в архив - не теряется, но в активном файле всегда текущая сессия. - Порядок загрузки.
@MPG_ModsLoggerдолжен идти в списке модов до потребителей - чтобы символMPG_ModsLoggerи тип были доступны при компиляции. - Конфиг загружается до логгера. Конструктор конфига потребителя использует
Print, а не логгер (логгера ещё нет в момент загрузки конфига). Логгер создаётся после - и берётlogLevelиз уже загруженного конфига.
API
| Элемент | Сигнатура | Описание |
|---|---|---|
| Конструктор | new MPG_ModsLogger(modName, logDir, minLevel = 3, consoleOutput = true, maxArchives = 500, consoleMinLevel = 4) | Создаёт экземпляр, открывает файл, запускает таймер flush |
Trace/Debug/Info/Warning/Error/Fatal | void X(string message) | Логирование с заданным уровнем |
IsTrace/IsDebug/IsInfo/IsWarn/IsError/IsFatal | bool X() | Проверка, будет ли уровень записан (для отсечения тяжёлого форматирования) |
FlushBuffer | void FlushBuffer() | Принудительный сброс буфера (вызывается таймером, можно вручную) |
FormatPosition | static string FormatPosition(vector pos) | "x, y, z" для логирования координат |
См. также
- Главная страница - краткое описание и список возможностей.
- Интеграция - как подключить мод к своему моду (для разработчиков).
