Журнал доступу

OxPHP записує структуровані журнали доступу у форматі JSON для кожного HTTP-запиту й виводить їх у stdout. Журналювання асинхронне, тож ніколи не блокує обробку запитів.

Як це працює

Коли задано ACCESS_LOG, OxPHP після кожного завершеного запиту записує в stdout один рядок JSON. Ці записи буферизуються у фоновому потоці запису, тому журналювання ніколи не блокує конвеєр обробки запитів.

Режим визначає, що саме потрапляє до журналу. ACCESS_LOG=all журналює кожен запит; ACCESS_LOG=error — лише відповіді зі статусом 400 і вище.

Чи записуються ці рядки, вирішує тільки ACCESS_LOG; LOG_LEVEL на них не впливає. Сервер, діагностику якого налаштовано на warn, однаково веде журнал доступу, увімкнений через ACCESS_LOG. Єдиний виняток — директива RUST_LOG, яка явно називає ціль access_log; див. Детальне фільтрування.

Кожен рядок журналу містить поле request_id, що дає змогу зіставляти записи журналу доступу з журналами вашого застосунку. Коли ввімкнено поширення W3C Trace Context, записи також містять поля trace_id і span_id.

Налаштування

Змінна За замовчуванням Опис
ACCESS_LOG (не задано) Режим журналу доступу. all журналює кожен запит; error — лише відповіді 4xx і 5xx. Залиште незаданою або порожньою, щоб вимкнути
Note

Допустимі лише значення all та error. Нерозпізнане значення призводить до попередження в журналі й вимикає журнал доступу.

Формат журналу

Кожен запис журналу доступу — це один рядок 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 string Унікальний ідентифікатор запиту. Див. Ідентифікатори запитів
method string HTTP-метод (GET, POST тощо)
path string Шлях URI запиту
status number Код статусу HTTP-відповіді
duration_us number Загальний час обробки запиту в мікросекундах
remote_ip string IP-адреса клієнта (без порту). Якщо налаштовано TRUSTED_PROXIES, містить справжню IP-адресу клієнта, отриману із заголовків переспрямування; IP-адресу проксі — лише тоді, коли вони не дають жодної адреси (див. Довірені проксі)
user_agent string Заголовок User-Agent запиту (перший, якщо їх надіслано кілька), обрізаний до 512 байтів (разом із позначкою …(truncated)), якщо він довший. Байти, що не є коректним UTF-8, замінюються на U+FFFD. Відсутнє, якщо запит його не надіслав
trace_id string Ідентифікатор трасування W3C (лише коли TRACE_CONTEXT=true)
span_id string Ідентифікатор спану W3C (лише коли TRACE_CONTEXT=true)

Детальне фільтрування

OxPHP записує журнал доступу під внутрішньою ціллю журналювання access_log і додає директиву для цієї цілі до власного фільтра журналу щоразу, коли ACCESS_LOG вимагає журналу доступу. Завдяки цьому два налаштування незалежні: LOG_LEVEL=warn приглушує сервер, а журнал доступу продовжує працювати без жодних додаткових налаштувань.

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

Директива RUST_LOG, що називає ціль повністю, має пріоритет для цієї цілі — зокрема й над директивою, яку додає OxPHP. Самий лише рівень її не перекриває, так само як і LOG_LEVEL (RUST_LOG=off приглушує власну діагностику сервера, а журнал доступу продовжує працювати). Не перекриває її й скорочена назва на кшталт access=off: із двох директив вона менш специфічна, тож програє. Щоб вимкнути лише журнал доступу, використовуйте директиву з повною назвою:

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_LOG.

Note

Ціль access_log використовується для фільтрування через RUST_LOG, але не потрапляє до виводу JSON. Щоб розпізнавати записи журналу доступу в подальших системах, орієнтуйтеся на поле "message": "request completed" і характерний набір полів (method, path, status, duration_us).

Усунення несправностей

Записи журналу доступу не з’являються

Журнал доступу вимкнено, коли 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) ніколи до цього не призводить: самий лише рівень застосовується до всіх цілей одразу, а директива, що називає саме цю ціль, специфічніша з двох.

Журнал доступу працює в режимі `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, щоб зменшити обсяг журналів і водночас фіксувати всі невдалі запити. Успішні запити не журналюються, але помилки з будь-яким статусом 400+ фіксуються завжди.
  • Додавайте ідентифікатор запиту до журналів застосунку через oxphp_request_id(), щоб зіставляти записи журналів на рівні PHP із записами журналу доступу.
  • Використовуйте агрегатор структурованих журналів, як-от Elasticsearch, Loki чи Datadog, щоб ефективно шукати й фільтрувати рядки журналів JSON.

Інтеграція

Оскільки журнали — це рядки JSON у stdout, вони безпосередньо інтегруються з драйверами журналів контейнерів та інструментами агрегації:

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

Жоден sidecar-контейнер чи доставка журналів через файли не потрібні.

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

Ці промпти передбачають, що до агента підключено MCP-сервер документації OxPHP — потрібні сторінки він прочитає сам.

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

Див. також