MAATRIX / Блог / Регламент хранения логов: что писать, что сжимать, что удалять

Регламент хранения логов: что писать, что сжимать, что удалять

MAATRIX

«Логи занимают весь диск» и «в логах не нашлось нужной записи» — это две стороны одной и той же нерешённой задачи: на сервере нет явного регламента, что писать, что сжимать и что удалять. Без него команда обычно шарахается между крайностями — то включает максимальный уровень детализации везде и получает забитый диск, то в панике чистит всё подряд и остаётся без данных для расследования инцидента, который случится завтра. Разберём, как выстроить регламент хранения логов из трёх осмысленных фаз — запись, сжатие, удаление — и закрепить его конкретными конфигами logrotate, journald и Docker, а не устной договорённостью.

Три фазы жизни лога, а не один рубильник

Типичная ошибка — воспринимать логирование как один переключатель: «логируем подробно» или «логируем скупо», «храним долго» или «чистим быстро». На деле у лога есть три разных этапа, и у каждого своя экономика риска.

  • Запись — решение о том, что и с какой детализацией фиксируется прямо сейчас. Риск здесь двусторонний: слишком мало — и в момент инцидента нечего анализировать; слишком много — и полезный сигнал тонет в шуме, а диск и I/O тратятся на данные, которые никто никогда не откроет.
  • Сжатие — что происходит с логом, когда он перестал быть «горячим», то есть нужным для чтения прямо сейчас, но ещё не устарел настолько, чтобы его выбросить. Здесь риск — забыть сжать вовремя и потерять место, либо сжать слишком рано и усложнить себе быстрый grep по вчерашнему инциденту.
  • Удаление — момент, когда лог теряет ценность быстрее, чем растёт стоимость его хранения. Риск — удалить раньше, чем понадобилось для расследования, либо хранить бесконечно и платить диском (а иногда и соответствием требованиям по персональным данным) за то, что никто не читает.

Одно правило на все случаи — например «ротируем логи раз в неделю» — не устраивает никого: для журнала аутентификации неделя может быть слишком коротким сроком, а для подробного debug-лога веб-сервера — избыточно долгим. Регламент нужен, чтобы развести источники логов по трём фазам с разными параметрами для каждого.

Фаза записи: что логировать подробно, а что — нет

Первый и самый недооценённый рычаг экономии места — не ротация и не сжатие, а решение не писать лишнее с самого начала. Уровень детализации логирования должен зависеть от того, в проде вы или нет, и от того, что именно логируется.

Уровни логирования приложения. В production-окружении разумный уровень по умолчанию — info или warning, а не debug. Debug-уровень печатает служебные детали на каждый шаг обработки запроса и на нагруженном сервисе способен раздуть лог в десятки раз без единой полезной строки для типичного инцидента. Включать debug стоит точечно и временно — на время разбора конкретной проблемы, с явным планом вернуть уровень обратно, а не «просто на всякий случай пусть будет». Это управляется одной переменной окружения:

# .env продакшена
LOG_LEVEL=warning

# временно, только на время расследования
LOG_LEVEL=debug

Что не стоит писать целиком. Частые источники раздувания даже при разумном уровне логирования: полные тела HTTP-запросов и ответов, дампы SQL-запросов с параметрами, служебные health-check пинги от балансировщика, которые прилетают каждые несколько секунд без диагностической ценности. Отдельная опасность — попадание в лог токенов, паролей и персональных данных: это уже не трата места, а риск утечки. Подробнее, почему привычка «пишем всё подряд, вдруг пригодится» обходится дороже, чем кажется, — в статье про антипаттерн записи всего подряд на всякий случай.

Health-check запросы стоит исключать из access-лога веб-сервера явно, а не мириться с их постоянным присутствием. В nginx это делается через map:

# в http {}
map $request_uri $loggable {
    /health   0;
    /healthz  0;
    default   1;
}

# в server {} или location {}
access_log /var/log/nginx/access.log combined if=$loggable;

Структурированный формат. Если приложение позволяет выбрать формат лога, JSON-логи (или хотя бы предсказуемый ключ=значение) окупаются на следующих двух фазах: по полю легко фильтровать при сжатии и находить нужный тип события при частичном удалении истории. Неструктурированный текст тоже годится, но требует больше ручной работы через grep/awk.

Разделение по назначению. Логи безопасности (аутентификация, sudo, fail2ban), логи доступа веб-сервера и отладочные логи приложения имеют разную ценность и разный профиль роста. Смешивать их в один файл — значит терять возможность настроить для каждого свою политику на следующих фазах. Уже на этапе записи разумно развести их по разным файлам или логгерам с разными путями.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Фаза сжатия: когда лог перестаёт быть горячим

Сжатие — не экономия места ради экономии, а разделение лога на «горячую» часть, которую читаете руками при разборе текущей ситуации, и «холодную», которая нужна редко и может лежать в компактном виде.

Сколько держать несжатым. Практичный ориентир — одни-двое суток в открытом виде: этого хватает, чтобы прогрепать вчерашний и сегодняшний лог без распаковки. В logrotate за это отвечает директива delaycompress: она откладывает сжатие свежеротированного файла на один цикл.

/var/log/myapp/*.log {
    daily
    rotate 30
    compress
    delaycompress
    dateext
    dateformat -%Y-%m-%d
    missingok
    notifempty
    sharedscripts
    postrotate
        systemctl reload myapp >/dev/null 2>&1 || true
    endscript
}

Директивы, которые чаще всего настраивают неверно или не настраивают вовсе: dateext вместе с dateformat -%Y-%m-%d даёт архивам понятные имена вида app.log-2026-08-25.gz вместо безликих app.log.1.gz — при разборе инцидента по дате это экономит время. sharedscripts заставляет postrotate-скрипт отработать один раз для всей группы файлов, а не на каждый отдельно. postrotate с перезагрузкой службы нужен, если приложение держит файл открытым по дескриптору и не подхватит новый само; для приложений без такой поддержки логичнее copytruncate.

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

/var/log/myapp/access.log {
    daily
    rotate 14
    maxsize 200M
    compress
    delaycompress
    missingok
    notifempty
}

maxsize заставляет logrotate ротировать файл, как только он превысит указанный размер, даже если по расписанию ещё не время — это защищает от исчерпания диска между плановыми прогонами cron.

Журнал systemd. journald сжимает записи автоматически (директива Compress=yes включена по умолчанию в /etc/systemd/journald.conf), поэтому отдельно настраивать сжатие для него обычно не требуется — важнее задать верхний предел размера, о чём в следующей фазе.

Насколько сжатие реально экономит место. Точный коэффициент зависит от структуры конкретного лога, поэтому конкретные проценты приводить не будем — это ориентир, который стоит проверить командой gzip -l после первой же ротации, а не считать универсальной константой. Текстовые логи с повторяющейся структурой обычно сжимаются заметно лучше хаотичных бинарных дампов, но точную цифру для вашего проекта покажет только замер.

Фаза удаления: где проходит граница разумного хранения

Удаление — самая спорная фаза регламента: здесь сталкиваются два противоположных интереса — чем дольше история логов, тем больше шансов найти причину проблемы, которая всплыла не сразу; чем короче — тем меньше диска и меньше юридических рисков, если в логах случайно оказались персональные данные.

Разные сроки для разных типов логов — не роскошь, а необходимость. Смешивать всё под одно число «храним 30 дней» так же неверно, как выбирать один уровень логирования для всех сервисов. Логи безопасности стоит хранить дольше остальных: инцидент со взломом или подбором пароля может обнаружиться далеко не в день события. Подробные access- и debug-логи можно чистить агрессивнее — их ценность резко падает уже через одну-две недели, если не идёт активное расследование.

Если проект обязан хранить определённые категории логов по требованию закона (например, логи доступа или VPN-подключений в некоторых юрисдикциях и отраслях), это отдельное ограничение поверх технической целесообразности — с ним стоит свериться прежде, чем настраивать автоматическую чистку. Требования по типам данных и срокам разобраны в статье о том, что нужно хранить в логах по закону, а общий подход к выбору срока хранения — в статье сколько хранить логи.

Механика удаления в logrotate. Директива rotate N определяет, сколько ротированных копий держать, прежде чем самая старая удаляется безвозвратно:

/var/log/myapp/security.log {
    weekly
    rotate 26
    compress
    delaycompress
    missingok
    notifempty
}

Здесь weekly + rotate 26 даёт примерно полгода истории для лога безопасности — заметно дольше, чем разумно держать подробный access-лог того же сервиса.

Файлы вне зоны ответственности logrotate. Не всё попадает под ротацию через штатные правила — старые архивы после смены формата логирования, экспортированные вручную дампы, файлы приложений, которые сами пишут архивы рядом с логом. Для них нужна отдельная, явная чистка через cron:

# /etc/cron.d/log-cleanup
0 4 * * * root find /var/log/archive -type f -name "*.gz" -mtime +180 -delete

Такое правило безвозвратно удаляет архивы старше 180 дней — важно проговорить этот срок в регламенте отдельной строкой, а не оставлять «где-то в cron», где его никто не пересматривает.

journald. Для журнала systemd срок хранения и предельный размер задаются вместе в /etc/systemd/journald.conf:

[Journal]
SystemMaxUse=1G
MaxRetentionSec=90day

SystemMaxUse ограничивает суммарный объём журнала на диске, MaxRetentionSec — предельный возраст записей: всё, что старше, journald удаляет сам при следующей ротации. Разовую принудительную чистку можно выполнить командой journalctl --vacuum-time=90d или journalctl --vacuum-size=1G.

Практические конфиги для типовых источников логов

Регламент должен закрывать не только собственное приложение, но и стандартные компоненты стека — веб-сервер, контейнеры, СУБД.

Docker. По умолчанию драйвер json-file не ограничивает размер лога контейнера, и это одна из самых частых причин внезапно закончившегося места на сервере с контейнерами. Ограничение задаётся в /etc/docker/daemon.json глобально для всех новых контейнеров:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "50m",
    "max-file": "5"
  }
}

После правки конфига нужно перезапустить демон (systemctl restart docker) — настройка применяется только к новым контейнерам, для уже запущенных нужно пересоздать их (docker compose up -d --force-recreate). Это даёт до 250 МБ на контейнер (5 файлов по 50 МБ) с автоматической ротацией без сжатия.

PostgreSQL. Встроенный сборщик логов ротирует файлы сам, но не удаляет старые — это остаётся на регламенте:

# postgresql.conf
logging_collector = on
log_rotation_age = 1d
log_rotation_size = 0
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'

log_rotation_age = 1d ротирует раз в сутки, log_rotation_size = 0 отключает ротацию по объёму, оставляя только по времени (можно включить оба триггера одновременно, задав ненулевой размер). Поскольку сам Postgres не удаляет старые файлы, отдельной строкой в регламенте нужен cron:

0 3 * * * postgres find /var/log/postgresql -name "*.log" -mtime +30 -delete

Nginx через logrotate. В большинстве дистрибутивов базовое правило для nginx уже есть в /etc/logrotate.d/nginx, но его стоит сверить с общим регламентом проекта, а не оставлять значение по умолчанию без проверки — особенно rotate N, которое в дистрибутивном конфиге обычно рассчитано на общий случай, а не на объём трафика конкретного сайта.

Матрица регламента: сколько хранить и в каком виде

Свести регламент в одну таблицу — надёжный способ, чтобы он пережил конкретного инженера, который его придумал. Ориентир ниже стоит адаптировать под профиль проекта — это стартовая точка, а не догма.

Тип логаУровень записиНесжатый периодСжатиеСрок храненияИнструмент
Логи безопасности (auth, sudo, fail2ban)Полная детализация всегда2 дняgzip после несжатого периода90–180 днейlogrotate, weekly+rotate 26
Access-лог веб-сервераПолная детализация, без health-check запросов2 дняgzip после несжатого периода14–30 днейlogrotate, daily+maxsize
Debug/отладочный лог приложенияwarning в проде, debug только временно1 деньgzip сразу после ротации7–14 днейlogrotate, daily+rotate 14
Журнал systemd (journald)По умолчанию сервисавстроенное, автоматическиПо лимиту размера/возрастаSystemMaxUse, MaxRetentionSec
Логи контейнеров DockerКак в приложениине сжимаются штатноПо лимиту размераmax-size+max-file в daemon.json
Логи СУБД (PostgreSQL и подобные)Ошибки и медленные запросы1 деньgzip внешним cron при необходимости30 днейlog_rotation_age + cron-удаление

Столбец «Срок хранения» — техническая рекомендация, а не юридическое требование: если закон обязывает хранить конкретные категории логов дольше, приоритет за ним, а таблицу нужно скорректировать. Регламент стоит пересматривать не реже раза в год и сразу после любого инцидента, где не хватило исторических логов — это лучший сигнал, что сроки в таблице выбраны неверно. Общий еженедельный чек-лист обслуживания сервера, куда естественно встраивается ревизия логов, разобран в статье про еженедельный регламент обслуживания сервера.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

С чего начать, если сейчас на сервере вообще нет регламента логов?

С аудита: du -sh /var/log/* | sort -h, чтобы увидеть, что реально занимает место, и ls /etc/logrotate.d/, чтобы понять, что уже настроено штатно. Дальше — развести источники по таблице выше и закрыть дыры: обычно это отсутствие лимита у Docker и journald.

Можно ли просто удалить старые логи вручную одной командой и не настраивать регламент?

Разово — можно, это снимет симптом. Но без регламента диск заполнится снова через тот же срок, а ручная чистка рискует задеть логи, которые ещё нужны для текущего расследования — отсюда и нужен явный, повторяемый процесс.

Что если для расследования инцидента понадобился лог, который уже удалён по регламенту?

Значит, для этого типа лога срок хранения в таблице выбран слишком коротко — это повод пересмотреть регламент, а не разовое исключение. Если инцидент разбирается регулярно позже определённого срока, увеличьте rotate N именно для этой категории.

Нужно ли то же самое на одной небольшой VPS, или регламент оправдан только для больших инфраструктур?

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

Как проверить конфиг logrotate перед тем, как довериться расписанию cron?

Тестовым прогоном без реальных изменений: logrotate -d /etc/logrotate.d/myapp — ключ -d показывает, что именно сделает logrotate, не трогая файлы. Принудительный реальный прогон — logrotate -f.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →