Journalisation des accès
OxPHP émet des journaux d'accès JSON structurés pour chaque requête HTTP, écrits sur stdout. La journalisation est asynchrone, elle ne bloque donc jamais le traitement des requêtes.
Fonctionnement
Lorsque ACCESS_LOG est défini, OxPHP écrit une ligne JSON sur stdout après chaque requête terminée. Ces écritures sont mises en tampon dans un thread d'écriture en arrière-plan, si bien que la journalisation ne bloque jamais le pipeline de requêtes.
Le mode contrôle ce qui est enregistré. ACCESS_LOG=all journalise chaque requête ; ACCESS_LOG=error journalise uniquement les réponses dont le statut est 400 ou supérieur.
ACCESS_LOG décide seul de l'écriture de ces lignes ; LOG_LEVEL n'a aucun effet sur elles. Un serveur dont les diagnostics sont réglés sur warn écrit quand même le journal d'accès que ACCESS_LOG active. La seule exception est une directive RUST_LOG qui nomme explicitement la cible access_log ; voir Filtrage fin.
Chaque ligne de journal porte un champ request_id qui met en corrélation les entrées du journal d'accès avec les journaux de votre application. Lorsque la propagation W3C Trace Context est activée, les entrées incluent également les champs trace_id et span_id.
Configuration
| Variable | Par défaut | Description |
|---|---|---|
ACCESS_LOG |
(non défini) | Mode de journalisation des accès. all journalise chaque requête ; error journalise uniquement les réponses 4xx et 5xx. Laissez la variable non définie ou vide pour désactiver |
Les seules valeurs acceptées sont all et error. Définir une valeur non reconnue génère un avertissement et désactive la journalisation des accès.
Format des journaux
Chaque entrée du journal d'accès est une unique ligne JSON écrite sur 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"
}
}Lorsque W3C Trace Context est actif, trace_id et span_id sont inclus aux côtés des champs standard :
{
"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"
}
}Champs
| Champ | Type | Description |
|---|---|---|
request_id |
string | Identifiant unique de requête. Voir ID de requête |
method |
string | Méthode HTTP (GET, POST, etc.) |
path |
string | Chemin de l'URI de la requête |
status |
number | Code de statut de la réponse HTTP |
duration_us |
number | Temps total de traitement de la requête en microsecondes |
remote_ip |
string | Adresse IP du client (sans port). Lorsque TRUSTED_PROXIES est configuré, affiche la véritable IP du client extraite des en-têtes de transfert ; l'IP du proxy uniquement lorsqu'ils ne fournissent aucune adresse (voir Proxys de confiance) |
user_agent |
string | L'en-tête User-Agent de la requête (le premier, si elle en a envoyé plusieurs), tronqué à 512 octets (marqueur …(truncated) compris) lorsqu'il est plus long. Les octets qui ne sont pas de l'UTF-8 valide sont remplacés par U+FFFD. Absent lorsque la requête n'en a envoyé aucun |
trace_id |
string | ID de trace W3C (présent uniquement lorsque TRACE_CONTEXT=true) |
span_id |
string | ID de span W3C (présent uniquement lorsque TRACE_CONTEXT=true) |
Filtrage fin
OxPHP émet les entrées du journal d'accès sous la cible de journalisation interne access_log, et ajoute une directive pour cette cible à son propre filtre de journaux dès que ACCESS_LOG demande un journal d'accès. Les deux réglages restent ainsi indépendants : LOG_LEVEL=warn rend le serveur plus silencieux et laisse le journal d'accès fonctionner, sans aucune configuration supplémentaire.
# Quiet diagnostics, access log intact
LOG_LEVEL=warn
ACCESS_LOG=allUne directive RUST_LOG qui nomme la cible en entier l'emporte pour cette cible, y compris sur la directive ajoutée par OxPHP. Un niveau seul ne la remplace pas, pas plus que LOG_LEVEL (RUST_LOG=off fait taire les diagnostics propres du serveur et laisse le journal d'accès fonctionner). Un nom abrégé comme access=off ne la remplace pas non plus : c'est la moins spécifique des deux directives, et elle perd. Utilisez une directive au nom complet pour couper uniquement le journal d'accès :
# 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, lorsqu'il est défini et analysable, remplace LOG_LEVEL au lieu de s'y ajouter : incluez-y donc votre niveau. La combinaison ci-dessus est en outre contradictoire (le journal d'accès est activé puis filtré), si bien que le serveur la signale une fois au démarrage, en nommant les deux valeurs. Préférez retirer ACCESS_LOG.
La cible access_log sert au filtrage RUST_LOG mais n'est pas incluse dans la sortie JSON. Pour identifier les entrées du journal d'accès dans les systèmes en aval, utilisez le champ "message": "request completed" et l'ensemble de champs caractéristique (method, path, status, duration_us).
Dépannage
Aucune entrée du journal d'accès n'apparaît
La journalisation des accès est désactivée lorsque ACCESS_LOG est non défini ou vide.
Correctif : définissez la variable sur all ou error :
ACCESS_LOG=allSi elle est déjà définie, la possibilité restante est un RUST_LOG qui nomme la cible access_log et la désactive ; la directive d'un opérateur l'emporte sur celle qu'ajoute OxPHP. Le serveur le signale au démarrage :
{"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"}}Le champ filter indique les directives en vigueur, qui sont ici exactement celles que vous avez définies : la directive qu'OxPHP ajoute pour la cible est abandonnée partout où la vôtre la remplace. Retirez de RUST_LOG le access_log=… que vous avez écrit, et les entrées sont de nouveau écrites.
Si le filtre est assez restrictif pour écarter aussi WARN, le même signalement est écrit sur stderr sous forme de ligne brute plutôt qu'en JSON, de sorte que cette combinaison est toujours signalée :
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)Un simple LOG_LEVEL (trace, debug, info, warn, error) n'en est jamais la cause : un niveau seul s'applique à toutes les cibles à la fois, et la directive qui nomme celle-ci est la plus spécifique des deux.
Les journaux d'accès affichent le mode `error` mais les requêtes réussies sont absentes
ACCESS_LOG=error ne journalise que les réponses dont le statut est 400 ou supérieur. C'est intentionnel — vérifiez la valeur et passez à all si vous avez besoin de journaliser toutes les requêtes.
Vérification : confirmez le paramètre actif :
curl -s http://localhost:9090/config | jq '.access_log'Les entrées de journal apparaissent sans `trace_id` ni `span_id`
Les champs de contexte de trace ne sont présents que lorsque la propagation W3C Trace Context est activée.
Correctif : activez-la avec :
TRACE_CONTEXT=trueEt assurez-vous que votre client en amont ou votre répartiteur de charge envoie un en-tête traceparent sur les requêtes.
Exemple 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"Bonnes pratiques
- Utilisez
ACCESS_LOG=erroren production pour réduire le volume des journaux tout en capturant toutes les requêtes en échec. Les requêtes réussies ne sont pas journalisées, mais les erreurs à tout statut 400+ sont toujours capturées. - Incluez l'ID de requête dans les journaux de votre application via
oxphp_request_id()afin de pouvoir corréler les entrées de journal au niveau PHP avec les entrées du journal d'accès. - Utilisez un agrégateur de journaux structurés tel qu'Elasticsearch, Loki ou Datadog pour interroger et filtrer efficacement les lignes de journal JSON.
Intégration
Comme les journaux sont des lignes JSON sur stdout, ils s'intègrent directement avec les pilotes de journalisation de conteneurs et l'outillage d'agrégation :
- Docker — collectés automatiquement via le pilote de journalisation du conteneur (json-file, fluentd, et autres)
- Kubernetes — récupérés par l'agent de journalisation du nœud (Fluentd, Fluent Bit, Filebeat, et autres)
- systemd — capturés lors de l'exécution en tant que service systemd avec journalisation stdout via journald
Aucun sidecar ni acheminement de journaux basé sur des fichiers n'est nécessaire.
Prompts pour votre agent
Ces prompts supposent un agent connecté au serveur MCP de la documentation OxPHP — il lira lui-même les pages dont il a besoin.
Fais remonter les journaux d'accès d'OxPHP dans mon agrégateur de journaux. Lis la page `features/access-logging` via le serveur MCP de la documentation OxPHP.
Choisis le mode dans lequel ce projet doit tourner, vérifie que mes réglages `LOG_LEVEL` ou `RUST_LOG` ne filtrent pas la cible `access_log`, et montre-moi comment mon agrégateur doit analyser les champs JSON à côté des journaux applicatifs que ce dépôt écrit déjà.Voir aussi
- ID de requête — chaque entrée de journal inclut un
request_idpour le traçage - Référence de configuration — référence complète des variables d'environnement