Логирование доступа
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. Оставьте не заданной или пустой, чтобы отключить |
Допустимы только значения all и error. Задание нераспознанного значения приводит к записи предупреждения и отключает access-логирование.
Формат лога
Каждая запись access-лога — это одна JSON-строка, записываемая в stdout:
{
"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 включаются наряду со стандартными полями:
{
"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-лог продолжает работать — без какой-либо дополнительной настройки.
# Quiet diagnostics, access log intact
LOG_LEVEL=warn
ACCESS_LOG=allДиректива RUST_LOG, которая называет цель полным именем, имеет приоритет для этой цели, в том числе над директивой, которую добавляет OxPHP. Голый уровень её не переопределяет — так же, как и LOG_LEVEL (RUST_LOG=off приглушает собственную диагностику сервера, а access-лог продолжает работать). Не переопределяет её и сокращённое имя вроде access=off: из двух директив она менее специфичная и проигрывает. Чтобы отключить только access-лог, используйте директиву с полным именем:
# 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 beenRUST_LOG, если он задан и корректно разбирается, заменяет LOG_LEVEL, а не дополняет его, поэтому укажите в нём свой уровень. Кроме того, приведённая выше комбинация противоречива (access-лог включается, а затем отфильтровывается), поэтому сервер один раз сообщает о ней при запуске, называя оба значения. Лучше просто уберите ACCESS_LOG.
Цель access_log используется для фильтрации через RUST_LOG, но не включается в JSON-вывод. Чтобы идентифицировать записи access-лога в нижестоящих системах, используйте поле "message": "request completed" и характерный набор полей (method, path, status, duration_us).
Устранение неполадок
Записи access-лога не появляются
Access-логирование отключено, когда переменная ACCESS_LOG не задана или пуста.
Решение: задайте переменной значение all или error:
ACCESS_LOG=allЕсли она уже задана, остаётся одна возможность — RUST_LOG, который называет цель access_log и отключает её; собственная директива оператора побеждает директиву, добавляемую OxPHP. Сервер сообщает об этом при запуске:
{"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, так что об этой комбинации сообщается всегда:
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, если нужно логировать все запросы.
Проверка: подтвердите активную настройку:
curl -s http://localhost:9090/config | jq '.access_log'Записи лога появляются без `trace_id` и `span_id`
Поля контекста трассировки присутствуют только при включённом распространении W3C Trace Context.
Решение: включите его так:
TRACE_CONTEXT=trueИ убедитесь, что вышестоящий клиент или балансировщик нагрузки отправляет заголовок traceparent в запросах.
Пример для Docker
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 — нужные страницы он прочитает сам.
Заведи access-логи OxPHP в мой агрегатор логов. Прочитай страницу `features/access-logging` через MCP-сервер документации OxPHP.
Выбери режим, в котором должен работать этот проект, проверь, что мои настройки `LOG_LEVEL` или `RUST_LOG` не отфильтровывают цель `access_log`, и покажи, как мой агрегатор должен разбирать поля JSON рядом с логами приложения, которые этот репозиторий уже пишет.См. также
- Идентификаторы запросов — каждая запись лога содержит
request_idдля трассировки - Справочник по конфигурации — полный справочник переменных окружения