Выберите продукт

Web server logs: Nginx vs Caddy vs Traefik — access/error, JSON и уровни ошибок

Неструктурированные логи превращают разбор инцидентов в квест. В статье сравним Nginx, Caddy и Traefik по настройке access/error логов: JSON-формат, уровни ошибок, request_id корреляция, выбор journald или файлов и ротация.
Web server logs: Nginx vs Caddy vs Traefik — access/error, JSON и уровни ошибок

Логи веб-сервера — это не «архив для галочки», а главный источник правды о том, что реально происходило на периметре: кто пришёл, куда пошёл, сколько заняло времени, какой код ответа вернулся, какой апстрим тормозил и где возникла ошибка.

В продакшене всё чаще ожидают structured logging: когда каждая запись — это объект (обычно JSON), который удобно парсить, фильтровать и коррелировать.

Ниже — практичное сравнение логирования в трёх популярных решениях: Nginx (классика), Caddy (простая конфигурация и structured logs «из коробки») и Traefik (edge-прокси, часто в Docker/Kubernetes). Фокус: access/error, JSON-формат, уровни ошибок, journald vs файлы, ротация и корреляция по request_id.

Что считать «хорошими» логами для веб-периметра

Перед сравнением зафиксируем цели. Схема логирования должна отвечать на вопросы:

  • Кто (клиент/реальный IP/прокси-цепочка) сделал запрос и куда (host, uri, method)?
  • Что получилось (status, bytes, cache status) и как быстро (общая задержка, времена апстрима)?
  • Почему сломалось (уровень ошибки, контекст, апстрим, таймауты)?
  • Как связать события в одну цепочку edge → app → db (корреляция по request id / trace id)?

Практичный минимум полей для JSON access-лога: timestamp, host, method, uri, status, bytes, referer, user_agent, remote_addr, x_forwarded_for, request_time, upstream_addr, upstream_status, upstream_response_time, request_id.

Если вы настраиваете централизованный сбор, полезно сразу договориться о едином имени поля: например, везде использовать request_id в JSON и заголовок X-Request-Id на проводе.

Journald vs файлы: что выбрать

Оба подхода рабочие, но под разные сценарии:

  • Файлы удобны там, где вы привыкли к ротации, бэкапам и «прочитать хвост» через tail. Для Nginx это стандарт де-факто.
  • journald удобен в systemd-мире: единый сбор, лимиты диска политиками journald, быстрый поиск через journalctl. Часто выбирают для Caddy/Traefik и для контейнеров (stdout/stderr собирает платформа).

Главное правило: куда бы вы ни писали, формат должен быть одинаковым и машиночитаемым. Смешивать «красивые» строки и JSON в одном потоке — почти всегда боль для наблюдаемости.

Если вы строите инфраструктуру на systemd и хотите подтянуть алерты/ретеншн «по-взрослому», пригодится статья про централизованный сбор journald через systemd-journal-remote.

Nginx: JSON access logs, уровни ошибок и корреляция

Nginx исторически ориентирован на файловые логи. Structured logging делается через log_format и подбор переменных. Плюс: максимальная гибкость и контроль. Минус: формат нужно аккуратно собирать и поддерживать, иначе легко получить «почти JSON», который ломает парсер.

Access JSON: базовый формат и полезные поля

Пример практичного JSON access-формата (обратите внимание на времена апстрима и идентификатор запроса):

http {
  log_format json_access escape=json '{'
    '"ts":"$time_iso8601",'
    '"remote_addr":"$remote_addr",'
    '"xff":"$http_x_forwarded_for",'
    '"host":"$host",'
    '"method":"$request_method",'
    '"uri":"$request_uri",'
    '"status":$status,'
    '"bytes":$body_bytes_sent,'
    '"referer":"$http_referer",'
    '"ua":"$http_user_agent",'
    '"request_time":$request_time,'
    '"upstream_addr":"$upstream_addr",'
    '"upstream_status":"$upstream_status",'
    '"upstream_response_time":"$upstream_response_time",'
    '"request_id":"$request_id"'
  '}';

  access_log /var/log/nginx/access.json json_access;
}

Ключевые моменты:

  • escape=json защищает JSON от кавычек и спецсимволов в User-Agent, referer и прочих строковых полях.
  • $request_time — полное время обработки на стороне Nginx (включая ожидание апстрима).
  • $upstream_response_time и $upstream_status — база для диагностики 502/504 и ответа на вопрос «кто тормозит».

Корреляция по request id: чтобы реально работало

Идея простая: один и тот же идентификатор должен пройти через edge и приложение, и попасть в логи с одинаковым именем. На практике важны два шага: (1) принять входящий X-Request-Id, если он уже пришёл от CDN/балансировщика, (2) если его нет — сгенерировать и дальше везде пробрасывать.

Типовой паттерн проброса в апстрим:

location / {
  proxy_set_header X-Request-Id $request_id;
  proxy_pass http://app_upstream;
}

Дальше приложение логирует X-Request-Id как request_id — и вы склеиваете цепочку без APM: фильтруете по одному значению и видите весь путь запроса.

Развёрнутый разбор (включая нюансы с проксированием и тем, где именно удобнее генерировать id) есть в материале про X-Request-Id для Nginx и PHP-FPM.

Error log levels: что включать на проде

У Nginx error-лог — отдельный поток, где важны уровни: debug, info, notice, warn, error, crit, alert, emerg. На бою обычно ставят warn или error, а debug включают точечно и на короткий период.

error_log /var/log/nginx/error.log warn;

Если расследуете нестабильные 499/502/504, временно поднимайте уровень до info или notice и заранее убедитесь, что ротация не даст забить диск.

logrotate для Nginx: безопасная ротация без потери логов

Nginx держит файловые дескрипторы открытыми; простого переименования файла недостаточно — нужно послать сигнал на переоткрытие логов. Классический вариант — USR1.

Шаблон правила logrotate:

/var/log/nginx/*.log /var/log/nginx/*.json {
  daily
  rotate 14
  missingok
  notifempty
  compress
  delaycompress
  sharedscripts
  postrotate
    /usr/sbin/nginx -t -q
    /bin/systemctl kill -s USR1 nginx
  endscript
}
  • kill -s USR1 заставляет Nginx закрыть старые дескрипторы и открыть новые файлы.
  • delaycompress снижает риск гонок, когда кто-то ещё читает «вчерашний» файл.

Если вам нужен предсказуемый периметр с изоляцией сервисов и удобным доступом к логам (и при этом вы хотите полный контроль над конфигурацией), часто проще вынести edge на отдельную виртуальную машину. Для таких задач обычно выбирают VDS под Nginx/Traefik с понятной политикой хранения логов и отдельным диском под ретеншн.

Схема: JSON access-лог в Nginx и безопасная ротация через USR1

Caddy: structured logging «из коробки»

Caddy часто выбирают за простоту: HTTPS автоматом, конфиг лаконичный, и при этом логи пригодны для наблюдаемости без «самосборного» JSON. Он нормально живёт и с файлами, и с journald (через systemd unit и вывод).

Access JSON в Caddyfile: минимум боли

В Caddy логирование настраивается декларативно: вы задаёте логгер и формат (JSON — стандартный выбор). Пример, который обычно устраивает прод:

{
  log {
    format json
    level INFO
  }
}

example.com {
  reverse_proxy 127.0.0.1:8080
  log {
    format json
    output file /var/log/caddy/access.json
  }
}
  • JSON-формат в Caddy реально структурированный и удобный для отправки в Loki/ELK/ClickHouse.
  • level влияет на системные логи Caddy, а access-лог настраивается отдельно.

Корреляция запросов: как жить без «родного» request_id

В реальности request id почти всегда генерируется на edge. Если ваш edge — Caddy, проще всего стандартизировать заголовок X-Request-Id: принимать входящий (если он уже пришёл) и пробрасывать в апстрим. Дальше приложение пишет этот идентификатор в свои JSON-логи.

Даже если в вашей схеме access-логов Caddy заголовок не выведен отдельным полем «как есть», он всё равно останется в запросе и будет жить в логах приложения, а на edge вы сможете коррелировать по системным событиям/маршрутизации и времени.

Файлы или journald

Для systemd-сервиса типовая схема такая: access-лог — в файл (чтобы проще считать объём и ретеншн «по трафику»), а системные события (ошибки, перезапуски, ACME) — в journald. Так вы разделяете «шум» и «трафик» и проще настраиваете хранение.

Виртуальный хостинг FastFox
Виртуальный хостинг для сайтов
Универсальное решение для создания и размещения сайтов любой сложности в Интернете от 95₽ / мес

Traefik: JSON access logs и контроль шума

Traefik — edge-прокси, который часто живёт в Docker/Kubernetes. В таких окружениях естественный путь — писать в stdout/stderr, а сбор и ретеншн делегировать платформе. При этом Traefik умеет access-логи и JSON, а ещё позволяет управлять полями и заголовками, сдерживая объём.

Access log: JSON и выбор полей

Цель — получить JSON с предсказуемыми ключами и не захлебнуться в объёме. Концептуальный пример статической конфигурации:

accessLog:
  format: json
  fields:
    defaultMode: keep
    headers:
      defaultMode: drop
      names:
        X-Request-Id: keep
        X-Forwarded-For: keep
        User-Agent: keep
  • По умолчанию оставляем основные поля запроса/ответа.
  • Заголовки — дорогие по объёму, поэтому «дропаем всё» и сохраняем только то, что нужно для корреляции и диагностики.

Уровни ошибок и «почему так шумно»

В Traefik важно различать два потока:

  • Access logs: запись на каждый запрос (самый большой поток).
  • Сервисные логи: конфигурация, провайдеры, ACME, ошибки роутинга, рестарты.

Если поднять слишком подробный уровень сервисных логов, можно получить лавину событий при флапающих апстримах или частых обновлениях конфигурации (особенно в Kubernetes). На проде держите уровень пониже и включайте детализацию точечно.

request_id correlation в Traefik

Traefik часто стоит «первым» в цепочке, поэтому удобен как точка стандартизации: один заголовок X-Request-Id, проброс дальше, логирование на каждом уровне. Даже без полноценного трейсинга это сильно ускоряет расследования.

Схема: выбор полей в access-логах Traefik и проброс X-Request-Id

Сравнение: что выбрать под ваши задачи

Когда Nginx выигрывает

  • Нужен детальный контроль формата, максимум переменных и тонкая настройка.
  • У вас уже выстроен пайплайн вокруг файлов и logrotate.
  • Критичны метрики по апстримам в access-логах (upstream timings) и привычная модель error-логов.

Когда Caddy удобнее

  • Хотите меньше конфигов и при этом сразу structured logging в JSON.
  • Нужно быстро поднять периметр с понятным логированием без доводки форматов.
  • Нравится гибрид: системные события в journald, access — в файл (или тоже в journald, если так проще).

Когда Traefik логичнее

  • Вы в Docker/Kubernetes и хотите нативный подход: stdout + централизованный сбор.
  • Нужно управлять объёмом access-логов через выбор полей/заголовков.
  • Важна интеграция с динамическими провайдерами и частыми изменениями маршрутизации.

Практический чек-лист: приводим логи к виду «готово для observability»

  1. Единый JSON-формат для access-логов на всех edge-узлах.
  2. Единый request id: заголовок + запись в логи на edge и в приложении.
  3. Минимально достаточный набор полей: не логируйте всё подряд, особенно заголовки.
  4. Разделяйте потоки: access отдельно от error/service.
  5. Ретеншн и ротация: для файлов — logrotate + корректный reopen; для journald — лимиты и политика хранения.
  6. Проверка под нагрузкой: включите больше полей на час в пиковое время и оцените рост объёма/стоимости хранения.
FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Типовые ошибки, из-за которых расследования затягиваются

  • Нет корреляции: edge и приложение логируют «каждый своё», request id отсутствует.
  • Смешаны форматы: часть строк JSON, часть — произвольный текст, парсер ломается.
  • Нет upstream timing: 504 получается «в вакууме», непонятно, кто виноват.
  • Слишком высокий уровень ошибок постоянно: error-лог превращается в поток, где сложно найти сигнал.
  • Ротация сделана неправильно: дескрипторы не переоткрываются, место на диске не освобождается, логи «пропадают».

Итог

Если нужны максимально контролируемые и «богатые» логи, Nginx остаётся сильным выбором, но требует дисциплины в форматах и ротации. Caddy даёт быстрый путь к structured logging и удобен там, где важны скорость внедрения и читаемость конфигурации. Traefik особенно хорош в контейнерных сценариях: JSON access-логи, управление полями и естественная интеграция с централизованным сбором.

Вне зависимости от выбора, наибольший эффект обычно дают два шага: стандартизировать JSON access-лог и внедрить корреляцию по request_id по всей цепочке.

Поделиться статьей

Вам будет интересно

DNS over HTTPS/3 в 2026: Unbound vs dnscrypt-proxy vs AdGuard Home и роль ECH OpenAI Статья написана AI (GPT 5)

DNS over HTTPS/3 в 2026: Unbound vs dnscrypt-proxy vs AdGuard Home и роль ECH

Разбираем DoH и DoH3 в 2026 на практике: что выбрать между Unbound, dnscrypt-proxy и AdGuard Home. Обсудим кэш и задержки, split D ...
Vaultwarden vs Passbolt vs Bitwarden self-hosted в 2026: команды, 2FA/SSO, LDAP и бэкапы OpenAI Статья написана AI (GPT 5)

Vaultwarden vs Passbolt vs Bitwarden self-hosted в 2026: команды, 2FA/SSO, LDAP и бэкапы

Сравнение Vaultwarden, Passbolt и Bitwarden self-hosted глазами админа: эксплуатация и архитектура, 2FA/SSO/LDAP, выбор PostgreSQL ...
Apache vs Nginx vs Caddy: статика, sendfile, HTTP/2 и HTTP/3 OpenAI Статья написана AI (GPT 5)

Apache vs Nginx vs Caddy: статика, sendfile, HTTP/2 и HTTP/3

Сравниваем Apache, Nginx и Caddy для раздачи static files и больших загрузок: sendfile и aio, tcp_nopush, open_file_cache, ETag/La ...