Логирование доступа

OxPHP формирует структурированные access-логи в формате JSON для каждого HTTP-запроса и пишет их в stdout. Логирование выполняется асинхронно, поэтому никогда не блокирует обработку запросов.

Как это работает

Когда задана переменная ACCESS_LOG, OxPHP после каждого завершённого запроса пишет в stdout одну JSON-строку. Эти записи буферизуются в фоновом потоке-писателе, поэтому логирование никогда не блокирует конвейер обработки запросов.

Режим определяет, что именно записывается. ACCESS_LOG=all логирует каждый запрос; ACCESS_LOG=error логирует только ответы со статусом 400 и выше.

Будут ли записываться эти строки, определяет только ACCESS_LOG; LOG_LEVEL на них не влияет. Сервер, у которого диагностика выставлена на warn, всё равно пишет access-лог, включённый через ACCESS_LOG. Единственное исключение — директива RUST_LOG, в которой цель access_log названа явно; см. Тонкая фильтрация.

Каждая строка лога содержит поле request_id, которое связывает записи access-лога с логами вашего приложения. Когда включено распространение W3C Trace Context, записи также содержат поля trace_id и span_id.

Конфигурация

Переменная Значение по умолчанию Описание
ACCESS_LOG (не задано) Режим access-лога. all логирует каждый запрос; error логирует только ответы 4xx и 5xx. Оставьте не заданной или пустой, чтобы отключить
Note

Допустимы только значения all и error. Задание нераспознанного значения приводит к записи предупреждения и отключает access-логирование.

Формат лога

Каждая запись access-лога — это одна JSON-строка, записываемая в stdout:

json
{ "timestamp": "2026-02-11T12:34:56.789012Z", "level": "INFO", "fields": { "request_id": "67890abc12341a2b0042", "method": "GET", "path": "/api/users", "status": 200, "duration_us": 1234, "remote_ip": "10.0.0.1", "user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0", "message": "request completed" } }

Когда активен W3C Trace Context, поля trace_id и span_id включаются наряду со стандартными полями:

json
{ "timestamp": "2026-02-11T12:34:56.789012Z", "level": "INFO", "fields": { "request_id": "67890abc12341a2b0042", "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736", "span_id": "00f067aa0ba902b7", "method": "POST", "path": "/api/orders", "status": 201, "duration_us": 8421, "remote_ip": "10.0.0.1", "user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0", "message": "request completed" } }

Поля

Поле Тип Описание
request_id строка Уникальный идентификатор запроса. См. Идентификаторы запросов
method строка HTTP-метод (GET, POST и т. д.)
path строка Путь URI запроса
status число Код статуса HTTP-ответа
duration_us число Общее время обработки запроса в микросекундах
remote_ip строка IP-адрес клиента (без порта). Когда настроена переменная TRUSTED_PROXIES, отображается реальный IP клиента, извлечённый из заголовков перенаправления; IP прокси — только если они не дают ни одного адреса (см. Доверенные прокси)
user_agent строка Заголовок User-Agent запроса (первый, если их было отправлено несколько), обрезанный до 512 байт (включая маркер …(truncated)), если он длиннее. Байты, не являющиеся корректным UTF-8, заменяются на U+FFFD. Отсутствует, если запрос его не отправил
trace_id строка W3C trace ID (присутствует только при TRACE_CONTEXT=true)
span_id строка W3C span ID (присутствует только при TRACE_CONTEXT=true)

Тонкая фильтрация

OxPHP пишет записи access-лога под внутренней целью логирования access_log и, как только ACCESS_LOG запрашивает access-лог, добавляет в собственный фильтр логов директиву для этой цели. Благодаря этому две настройки не зависят друг от друга: LOG_LEVEL=warn приглушает сервер, а access-лог продолжает работать — без какой-либо дополнительной настройки.

bash
# Quiet diagnostics, access log intact LOG_LEVEL=warn ACCESS_LOG=all

Директива RUST_LOG, которая называет цель полным именем, имеет приоритет для этой цели, в том числе над директивой, которую добавляет OxPHP. Голый уровень её не переопределяет — так же, как и LOG_LEVEL (RUST_LOG=off приглушает собственную диагностику сервера, а access-лог продолжает работать). Не переопределяет её и сокращённое имя вроде access=off: из двух директив она менее специфичная и проигрывает. Чтобы отключить только access-лог, используйте директиву с полным именем:

bash
# Access log off, warnings and errors still reported ACCESS_LOG=all RUST_LOG=warn,access_log=off # `warn` here is what LOG_LEVEL would have been
Warning

RUST_LOG, если он задан и корректно разбирается, заменяет LOG_LEVEL, а не дополняет его, поэтому укажите в нём свой уровень. Кроме того, приведённая выше комбинация противоречива (access-лог включается, а затем отфильтровывается), поэтому сервер один раз сообщает о ней при запуске, называя оба значения. Лучше просто уберите ACCESS_LOG.

Note

Цель access_log используется для фильтрации через RUST_LOG, но не включается в JSON-вывод. Чтобы идентифицировать записи access-лога в нижестоящих системах, используйте поле "message": "request completed" и характерный набор полей (method, path, status, duration_us).

Устранение неполадок

Записи access-лога не появляются

Access-логирование отключено, когда переменная ACCESS_LOG не задана или пуста.

Решение: задайте переменной значение all или error:

bash
ACCESS_LOG=all

Если она уже задана, остаётся одна возможность — RUST_LOG, который называет цель access_log и отключает её; собственная директива оператора побеждает директиву, добавляемую OxPHP. Сервер сообщает об этом при запуске:

json
{"timestamp":"…","level":"WARN","fields":{"message":"ACCESS_LOG is set but the log filter drops the access_log target — no access log entries will be written","access_log":"all","filter":"warn,access_log=off"}}

Поле filter показывает действующие директивы — здесь это ровно те, что задали вы: директива, которую OxPHP добавляет для цели, отбрасывается везде, где её переопределяет ваша. Уберите из RUST_LOG написанную вами access_log=…, и записи снова начнут писаться.

Если фильтр настолько строгий, что отбрасывает и WARN, то же сообщение пишется в stderr обычной строкой, а не JSON, так что об этой комбинации сообщается всегда:

text
WARN ACCESS_LOG is set but the log filter drops the access_log target — no access log entries will be written (access_log=all, filter=error,access_log=off)

Обычный LOG_LEVEL (trace, debug, info, warn, error) никогда к этому не приводит: голый уровень применяется ко всем целям сразу, а директива, называющая эту цель, из двух более специфичная.

Access-логи в режиме `error`, но успешные запросы отсутствуют

ACCESS_LOG=error логирует только ответы со статусом 400 и выше. Это ожидаемое поведение — проверьте значение и переключитесь на all, если нужно логировать все запросы.

Проверка: подтвердите активную настройку:

bash
curl -s http://localhost:9090/config | jq '.access_log'
Записи лога появляются без `trace_id` и `span_id`

Поля контекста трассировки присутствуют только при включённом распространении W3C Trace Context.

Решение: включите его так:

bash
TRACE_CONTEXT=true

И убедитесь, что вышестоящий клиент или балансировщик нагрузки отправляет заголовок traceparent в запросах.

Пример для Docker

compose.yaml
services: app: image: ghcr.io/oxphp/oxphp:0.13.0 ports: - "80:80" - "9090:9090" volumes: - ./src:/var/www/html:ro environment: ACCESS_LOG: "all" ENTRY_FILE: "index.php" INTERNAL_ADDR: "0.0.0.0:9090"

Рекомендации

  • Используйте ACCESS_LOG=error в production, чтобы уменьшить объём логов, продолжая фиксировать все неудачные запросы. Успешные запросы не логируются, но ошибки с любым статусом 400+ фиксируются всегда.
  • Включайте идентификатор запроса в логи приложения через oxphp_request_id(), чтобы связывать записи логов уровня PHP с записями access-лога.
  • Используйте структурированный агрегатор логов, такой как Elasticsearch, Loki или Datadog, для эффективного поиска и фильтрации JSON-строк логов.

Интеграция

Поскольку логи — это JSON-строки в stdout, они напрямую интегрируются с драйверами логов контейнеров и инструментами агрегации:

  • Docker — собираются автоматически через драйвер логов контейнера (json-file, fluentd и другие)
  • Kubernetes — подхватываются агентом логов на узле (Fluentd, Fluent Bit, Filebeat и другие)
  • systemd — захватываются при запуске в виде службы systemd с логированием stdout через journald

Не требуется ни sidecar, ни доставка логов на основе файлов.

Промпты для вашего агента

Эти промпты рассчитаны на агента с подключённым MCP-сервером документации OxPHP — нужные страницы он прочитает сам.

prompt
Заведи access-логи OxPHP в мой агрегатор логов. Прочитай страницу `features/access-logging` через MCP-сервер документации OxPHP. Выбери режим, в котором должен работать этот проект, проверь, что мои настройки `LOG_LEVEL` или `RUST_LOG` не отфильтровывают цель `access_log`, и покажи, как мой агрегатор должен разбирать поля JSON рядом с логами приложения, которые этот репозиторий уже пишет.

См. также

Нашли ошибку? Сообщите →