Журнал доступу
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. Залиште незаданою або порожньою, щоб вимкнути |
Допустимі лише значення all та error. Нерозпізнане значення призводить до попередження в журналі й вимикає журнал доступу.
Формат журналу
Кожен запис журналу доступу — це один рядок 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 |
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 приглушує сервер, а журнал доступу продовжує працювати без жодних додаткових налаштувань.
# Quiet diagnostics, access log intact
LOG_LEVEL=warn
ACCESS_LOG=allДиректива RUST_LOG, що називає ціль повністю, має пріоритет для цієї цілі — зокрема й над директивою, яку додає OxPHP. Самий лише рівень її не перекриває, так само як і LOG_LEVEL (RUST_LOG=off приглушує власну діагностику сервера, а журнал доступу продовжує працювати). Не перекриває її й скорочена назва на кшталт access=off: із двох директив вона менш специфічна, тож програє. Щоб вимкнути лише журнал доступу, використовуйте директиву з повною назвою:
# 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Якщо RUST_LOG задано й він коректно розбирається, він замінює LOG_LEVEL, а не доповнює його, тож вкажіть у ньому свій рівень. Наведена вище комбінація до того ж суперечлива (журнал доступу вмикається, а потім відфільтровується), тому сервер один раз повідомляє про це під час запуску, називаючи обидва значення. Краще приберіть ACCESS_LOG.
Ціль access_log використовується для фільтрування через RUST_LOG, але не потрапляє до виводу JSON. Щоб розпізнавати записи журналу доступу в подальших системах, орієнтуйтеся на поле "message": "request completed" і характерний набір полів (method, path, status, duration_us).
Усунення несправностей
Записи журналу доступу не з’являються
Журнал доступу вимкнено, коли 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) ніколи до цього не призводить: самий лише рівень застосовується до всіх цілей одразу, а директива, що називає саме цю ціль, специфічніша з двох.
Журнал доступу працює в режимі `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, щоб зменшити обсяг журналів і водночас фіксувати всі невдалі запити. Успішні запити не журналюються, але помилки з будь-яким статусом 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 — потрібні сторінки він прочитає сам.
Налаштуй передавання журналів доступу OxPHP до мого агрегатора журналів. Прочитай сторінку `features/access-logging` через MCP-сервер документації OxPHP.
Обери режим, у якому має працювати цей проєкт, перевір, що мої налаштування `LOG_LEVEL` чи `RUST_LOG` не відфільтровують ціль `access_log`, і покажи, як мій агрегатор має розбирати поля JSON поряд із журналами застосунку, які цей репозиторій уже записує.Див. також
- Ідентифікатори запитів — кожен запис журналу містить
request_idдля трасування - Довідник із налаштувань — повний довідник змінних середовища