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
Note

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 :

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" } }

Lorsque W3C Trace Context est actif, trace_id et span_id sont inclus aux côtés des champs standard :

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" } }

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.

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

Une 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 :

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

Note

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 :

bash
ACCESS_LOG=all

Si 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 :

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"}}

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 :

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)

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 :

bash
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 :

bash
TRACE_CONTEXT=true

Et 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

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"

Bonnes pratiques

  • Utilisez ACCESS_LOG=error en 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.

prompt
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

Une erreur ? Signalez-la →