Traçage distribué et APM

OxPHP prend en charge la propagation W3C Trace Context, l'export OpenTelemetry (OTel) et une surveillance des performances applicatives (APM) intégrée. Les en-têtes traceparent entrants sont analysés et poursuivis, les IDs de trace sont accessibles en PHP via $_SERVER, les journaux d'accès incluent des champs de trace, et les spans peuvent être exportés vers Jaeger, Grafana Tempo, Zipkin ou tout backend compatible OTLP.

Le plugin APM ajoute trois couches de traçage par-dessus la fondation OTel :

  • Instrumentation automatique — les fonctions PHP internes (PDO, mysqli, cURL, Redis, Memcached, E/S de fichiers) sont interceptées au niveau du moteur ; chaque appel devient un span, sans aucune modification de code
  • Traçage par attribut — annotez n'importe quelle fonction ou méthode PHP avec #[OxPHP\Apm\Trace] pour créer des spans automatiquement
  • SDK PHP — 10 fonctions oxphp_apm_*() pour la création manuelle de spans, les attributs, les événements et l'enregistrement d'erreurs

Fonctionnement

  1. Requête entrante — OxPHP lit les en-têtes traceparent et tracestate conformément à la spécification W3C Trace Context
  2. Nouveau span — un nouvel ID de span est généré pour ce saut. L'ID de span entrant devient le parent
  3. Propagation vers PHP — les IDs de trace sont injectés dans $_SERVER['OXPHP_TRACE_ID'], $_SERVER['OXPHP_SPAN_ID'] et $_SERVER['OXPHP_PARENT_SPAN_ID']
  4. Journal d'accès — les journaux JSON structurés incluent les champs trace_id et span_id pour la corrélation des logs
  5. En-têtes de réponse — l'en-tête traceparent mis à jour (avec l'ID de span d'OxPHP) est ajouté à la réponse, afin que les services en aval puissent poursuivre la trace
  6. Export OTel (facultatif) — lorsque le plugin OTel est activé, chaque requête devient un span exporté via OTLP avec des attributs de convention sémantique HTTP

En l'absence d'en-tête traceparent, OxPHP génère un nouvel ID de trace et un nouvel ID de span et démarre une trace inédite.

Configuration

W3C Trace Context (intégré)

Variable Valeur par défaut Description
TRACE_CONTEXT false Active la propagation W3C Trace Context. Réglez sur true ou 1

Plugin OpenTelemetry

Le plugin OTel est une fonctionnalité activée à la compilation (plugin-otel). Une fois activé, il active automatiquement la propagation du contexte de trace (le même effet que de définir TRACE_CONTEXT=true).

Variable Valeur par défaut Description
OTEL_ENABLED false Active le plugin OpenTelemetry. Booléen — voir Valeurs booléennes
OTEL_EXPORTER_OTLP_PROTOCOL grpc Protocole d'export : grpc ou http/protobuf
OTEL_EXPORTER_OTLP_ENDPOINT http://localhost:4317 (gRPC) ou http://localhost:4318 (HTTP) Endpoint du collecteur OTLP. Une URL https:// est exportée via TLS sur les deux transports, vérifiée par rapport au magasin de confiance système (l'image d'exécution doit embarquer un bundle de CA tel que ca-certificates — l'image officielle l'installe) ; les bundles de CA personnalisés et le mTLS ne sont pas encore pris en charge
OTEL_EXPORTER_OTLP_TIMEOUT 10000 Délai d'expiration de l'export en millisecondes
OTEL_EXPORTER_OTLP_HEADERS (non défini) En-têtes d'authentification : key=value,key2=value2
OTEL_SERVICE_NAME oxphp Nom du service dans les spans exportés
OTEL_SERVICE_VERSION (non défini) Attribut de version du service
OTEL_RESOURCE_ATTRIBUTES (non défini) Attributs de ressource supplémentaires : env=prod,region=us-east-1
OTEL_TRACES_SAMPLER parentbased_traceidratio Stratégie d'échantillonnage : always_on, always_off, traceidratio, parentbased_always_on, parentbased_always_off, parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG 1.0 Ratio d'échantillonnage (0.0–1.0) pour les échantillonneurs basés sur un ratio
Note

Les valeurs invalides ou hors plage de OTEL_TRACES_SAMPLER_ARG sont ramenées dans l'intervalle [0.0, 1.0] et journalisées au niveau warn. Les valeurs inconnues de OTEL_TRACES_SAMPLER reviennent à parentbased_traceidratio et sont journalisées.

Plugin APM

Le plugin APM est une fonctionnalité activée à la compilation (plugin-apm) qui dépend du plugin OTel. Il ajoute l'instrumentation automatique, le décorateur #[OxPHP\Apm\Trace] et le SDK de traçage PHP.

Variable Valeur par défaut Description
OTEL_APM_ENABLED false Active l'APM : auto-instrumentation, capture d'erreurs, SDK PHP. Nécessite OTEL_ENABLED=true. Booléen — voir Valeurs booléennes
OTEL_APM_SLOW_QUERY_MS 100 Seuil de requête lente en millisecondes. Les requêtes au-dessus de ce seuil reçoivent oxphp.db.slow=true sur leurs spans
OTEL_APM_DB_CAPTURE_PARAMS_ENABLED false Enregistre les paramètres liés dans l'attribut de span db.params. Booléen — voir Valeurs booléennes
OTEL_APM_STACKTRACE_MAX_BYTES 8192 Taille maximale en octets de l'attribut exception.stacktrace. Au-delà du plafond, la trace d'appels est tronquée depuis la fin (la frame racine est conservée) avec un marqueur …(truncated). 0 désactive la troncature
OTEL_APM_MESSAGE_MAX_BYTES 4096 Taille maximale en octets de l'attribut exception.message (la valeur par défaut correspond à la limite de valeur par attribut de New Relic). Au-delà du plafond, le message est tronqué depuis la fin avec un marqueur …(truncated). 0 désactive la troncature

Contexte de trace en PHP

Lorsque TRACE_CONTEXT=true, trois variables $_SERVER sont disponibles dans vos scripts PHP :

Variable Description Exemple
OXPHP_TRACE_ID ID de trace W3C (32 caractères hexadécimaux) 4bf92f3577b34da6a3ce929d0e0e4736
OXPHP_SPAN_ID ID de span d'OxPHP pour cette requête (16 caractères hexadécimaux) 00f067aa0ba902b7
OXPHP_PARENT_SPAN_ID ID de span parent entrant (16 caractères hexadécimaux, vide si nouvelle trace) a3ce929d0e0e4736

Utilisez-les pour propager le contexte de trace vers les services en aval :

php
<?php $traceId = $_SERVER['OXPHP_TRACE_ID'] ?? ''; $spanId = $_SERVER['OXPHP_SPAN_ID'] ?? ''; if ($traceId) { // Build a traceparent header for downstream calls $traceparent = "00-{$traceId}-{$spanId}-01"; $response = file_get_contents('https://api.example.com/data', false, stream_context_create([ 'http' => [ 'header' => "traceparent: {$traceparent}\r\n", ], ]) ); }

Avec Guzzle

php
<?php $traceId = $_SERVER['OXPHP_TRACE_ID'] ?? ''; $spanId = $_SERVER['OXPHP_SPAN_ID'] ?? ''; $client = new \GuzzleHttp\Client(); $response = $client->get('https://api.example.com/users', [ 'headers' => [ 'traceparent' => "00-{$traceId}-{$spanId}-01", ], ]);

Corrélation avec le journal d'accès

Lorsque le contexte de trace est activé, les journaux d'accès JSON structurés incluent les champs trace_id et span_id :

json
{ "timestamp": "2026-03-23T10:15:30.123Z", "level": "INFO", "fields": { "request_id": "4bf92f3577b34da6-00f067aa", "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736", "span_id": "00f067aa0ba902b7", "method": "GET", "path": "/api/users", "status": 200, "duration_us": 1523, "remote_ip": "10.0.0.1", "message": "request completed" } }

Vous pouvez ensuite rechercher les logs par ID de trace dans les systèmes d'agrégation de logs (Loki, Elasticsearch, Splunk, CloudWatch) pour retrouver chaque entrée de log d'une trace distribuée.

En-têtes de réponse

OxPHP ajoute l'en-tête traceparent à chaque réponse, avec l'ID de span propre à OxPHP :

http
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01

Si la requête entrante comportait un en-tête tracestate, il est également transmis dans la réponse.

Intégration OpenTelemetry

Lorsque le plugin OTel est activé, chaque requête HTTP devient un span exporté vers votre backend de traçage via OTLP.

Attributs de span

Les spans exportés incluent les attributs standard de convention sémantique HTTP :

Attribut Description
http.request.method Méthode HTTP (GET, POST, etc.)
url.path Chemin de la requête
http.response.status_code Code de statut de la réponse
client.address Adresse IP du client
server.address Adresse d'écoute du serveur
oxphp.request_id ID de requête OxPHP
http.request.body.size Taille du corps de la requête en octets (si non nul)
http.response.body.size Taille du corps de la réponse en octets (si non nul)

Les réponses 5xx sont marquées comme spans en erreur.

Événements de span

Les spans enfants portent également des événements de span — des annotations horodatées exportées en tant qu'événements OpenTelemetry et rendues nativement par Jaeger, Grafana Tempo et d'autres backends OTLP. Un attribut oxphp.event.kind sur chaque événement en identifie le type :

oxphp.event.kind Source Attributs de l'événement
exception Une fonction #[OxPHP\Apm\Trace] ayant levé une exception, ou oxphp_apm_error() exception.type, exception.message, exception.stacktrace
custom oxphp_apm_event() fourni par l'utilisateur
mark annotation #[Mark] du profileur fourni par l'utilisateur
slow dépassement du #[SlowThreshold] du profileur threshold_ms, elapsed_ms
memory_spike dépassement du #[MemoryThreshold] du profileur threshold_kb, delta_bytes

L'attribut oxphp.event.kind peut en outre porter sql, http ou alloc sur les événements générés par l'instrumentation APM.

ID de requête avec OTel

Lorsque le plugin OTel est actif, les IDs de requête sont dérivés du contexte de trace : les 16 premiers caractères de l'ID de trace et les 8 premiers caractères de l'ID de span, séparés par un tiret. Cela apparaît dans les logs, dans l'en-tête de réponse X-Request-ID et via oxphp_request_id() en PHP.

APM : instrumentation automatique

Lorsque le plugin APM est activé, OxPHP intercepte automatiquement 33 fonctions PHP internes au niveau du moteur. Chaque appel à une fonction interceptée crée un span enfant sous le span racine de la requête en cours — sans aucune modification de code requise.

Fonctions interceptées

Catégorie Fonctions
PDO PDO::__construct, PDO::query, PDO::exec, PDO::prepare, PDOStatement::execute
mysqli mysqli::__construct, mysqli::query, mysqli::prepare, mysqli_stmt::execute
cURL curl_init, curl_setopt, curl_exec, curl_multi_exec
Redis Redis::connect, Redis::get, Redis::set, Redis::del, Redis::mget, Redis::mset, Redis::hget, Redis::hset, Redis::lpush, Redis::rpush
Memcached Memcached::get, Memcached::set, Memcached::delete, Memcached::getMulti, Memcached::setMulti
E/S de fichiers fopen, fread, fwrite, file_get_contents, file_put_contents

Les hooks ne sont installés que pour les extensions réellement chargées. Si votre build n'inclut pas l'extension Redis, les hooks Redis sont silencieusement ignorés.

Installation des hooks

L'installation des hooks repose sur une conception en deux phases pour la sûreté vis-à-vis des threads sous PHP ZTS :

  1. Phase 1 (MINIT) — durant l'initialisation du module, OxPHP valide chaque fonction cible par rapport aux extensions chargées et capture les pointeurs de handler d'origine dans une liste approuvée en lecture seule
  2. Phase 2 (RINIT) — à la première requête par thread worker, les hooks approuvés sont installés dans les tables de fonctions de ce thread

Cela garantit que chaque thread worker ZTS dispose de modifications cohérentes des tables de fonctions et d'un état thread-local.

APM : traçage par attribut

L'attribut #[OxPHP\Apm\Trace] crée des spans automatiquement autour des fonctions et méthodes décorées. Contrairement aux hooks d'auto-instrumentation (qui ciblent des fonctions C internes), celui-ci fonctionne sur du code PHP défini par l'utilisateur.

php
<?php use OxPHP\Apm\Trace; #[Trace] function processOrder(int $orderId): void { // A span named "processOrder" is created on entry and closed on exit. // If an exception is thrown, the span is marked as error and an // "exception" span event records exception.type, exception.message // and exception.stacktrace. } class PaymentService { #[Trace] public function charge(float $amount): bool { // Span named "PaymentService::charge" return true; } }

L'attribut #[Trace] cible à la fois les fonctions et les méthodes. Aucun appel d'enregistrement n'est nécessaire — le plugin APM enregistre le décorateur automatiquement lors de l'initialisation.

Si la fonction décorée lève une exception, le statut du span est passé à error et un événement exception est enregistré avec l'ensemble des données de convention sémantique OpenTelemetry : exception.type (la classe), exception.message (le message) et exception.stacktrace (la pile d'appels issue de getTraceAsString()). Le message est tronqué à OTEL_APM_MESSAGE_MAX_BYTES octets (4096 par défaut) et la trace d'appels à OTEL_APM_STACKTRACE_MAX_BYTES octets (8192 par défaut) ; 0 désactive l'un ou l'autre plafond. La capture des arguments dans les frames suit le paramètre zend.exception_ignore_args propre à PHP.

APM : SDK de traçage PHP

Le plugin APM enregistre 10 fonctions oxphp_apm_*() pour la gestion manuelle des spans. Toutes les fonctions sont des no-ops sûrs lorsque l'APM est désactivé, de sorte que votre code fonctionne sans modification dans n'importe quel environnement.

Créer des spans

php
<?php // Start a span and get its local ID $spanId = oxphp_apm_start('cache.warm', ['cache.size' => '1024']); // ... do work ... // Close the span oxphp_apm_end($spanId);

Ajouter des attributs et des événements

php
<?php $spanId = oxphp_apm_start('order.process'); // Add attributes to the current span (or a specific one) oxphp_apm_attribute('order.id', $orderId); oxphp_apm_attribute('order.total', $total, $spanId); // Record an event on the span oxphp_apm_event('payment.authorized', [ 'provider' => 'stripe', 'amount' => (string) $amount, ]); oxphp_apm_end($spanId);

Enregistrement d'erreurs

php
<?php $spanId = oxphp_apm_start('external.api'); try { $result = callExternalApi(); } catch (\Throwable $e) { // Mark the span as error oxphp_apm_error($e, $spanId); throw $e; } finally { oxphp_apm_end($spanId); }

Propager le contexte de trace

php
<?php // Get the current trace ID and span ID $traceId = oxphp_apm_trace_id(); $currentSpanId = oxphp_apm_span_id(); // Or get a ready-to-use traceparent header value $traceparent = oxphp_apm_header(); // "00-{trace_id}-{span_id}-01" // Propagate to downstream services $response = file_get_contents('https://api.example.com/data', false, stream_context_create([ 'http' => [ 'header' => "traceparent: {$traceparent}\r\n", ], ]) );

Référence des fonctions

Fonction Retourne Description
oxphp_apm_trace(name, callback, ?attributes) void Exécute un callback à l'intérieur d'un span (réservé à un usage futur)
oxphp_apm_start(name, ?attributes) int Ouvre un span et retourne son ID local. 0 lorsque l'APM est désactivé
oxphp_apm_end(span_id) void Ferme le span portant l'ID local donné
oxphp_apm_attribute(key, value, ?span_id) void Définit un attribut sur le span courant ou spécifié
oxphp_apm_event(name, ?attributes, ?span_id) void Enregistre un événement horodaté sur le span courant ou spécifié
oxphp_apm_error(exception, ?span_id) void Marque le span courant ou spécifié comme en erreur et enregistre un événement exception. Un objet Throwable fournit exception.type, exception.message et exception.stacktrace ; un simple argument de type chaîne est enregistré comme exception.message sous un exception.type générique Error (afin que l'événement reste visible dans les backends qui regroupent par type)
oxphp_apm_status(code, ?description, ?span_id) void Définit le statut du span : 0 = Unset, 1 = Ok, 2 = Error
oxphp_apm_trace_id() string ID de trace courant (32 caractères hexadécimaux). Vide lorsque l'APM est désactivé
oxphp_apm_span_id() string ID de span courant (16 caractères hexadécimaux). Vide en l'absence de span actif
oxphp_apm_header() string Valeur de l'en-tête W3C traceparent pour le contexte de span courant

Pour la référence complète des signatures de fonctions, voir Fonctions PHP.

Exemple Docker

Variantes compose.yaml prêtes à l'emploi :

Activer la propagation de trace W3C sans backend externe :

compose.yaml
services: app: image: ghcr.io/oxphp/oxphp:0.10.0 ports: - "80:80" environment: - TRACE_CONTEXT=true - INTERNAL_ADDR=0.0.0.0:9090
Note

La fonctionnalité Cargo plugin-apm doit être activée au moment du build. L'image OxPHP officielle l'inclut par défaut.

La stack d'observabilité

OxPHP fournit trois piliers d'observabilité qui fonctionnent de concert :

Pilier Fonctionnalité Corrélation
Métriques Compteurs et histogrammes Prometheus sur /metrics Données de performance agrégées
Journalisation Journaux d'accès JSON structurés avec ACCESS_LOG Détail par requête, recherchable par trace_id
Traçage W3C Trace Context + export OTLP Flux de requête distribué de bout en bout

Les trois partagent le même trace_id et le même request_id, de sorte que vous pouvez descendre d'une alerte de tableau de bord Grafana à une trace Tempo puis aux lignes de log Loki d'une requête unique.

Dépannage

Les en-têtes de trace n'apparaissent pas dans les réponses

TRACE_CONTEXT n'est pas activé.

Solution : Définissez TRACE_CONTEXT=true ou activez le plugin OTel avec OTEL_ENABLED=true (ce qui active le contexte de trace automatiquement).

Les variables de trace $_SERVER sont vides

Le contexte de trace est désactivé, ou les variables sont vérifiées en dehors d'OxPHP.

Vérification : Les variables OXPHP_TRACE_ID, OXPHP_SPAN_ID et OXPHP_PARENT_SPAN_ID n'existent que lorsque TRACE_CONTEXT=true et que la requête est servie par OxPHP. Testez avec :

php
<?php echo $_SERVER['OXPHP_TRACE_ID'] ?? 'trace context not enabled';
Les spans n'apparaissent pas dans Jaeger/Tempo

Vérification : Assurez-vous que l'endpoint OTLP est joignable depuis le conteneur OxPHP :

bash
docker compose exec app curl -v http://jaeger:4317

Vérification : Assurez-vous que le plugin est activé :

bash
curl -s http://localhost:9090/config | jq '.plugins'

Solution : Vérifiez que OTEL_ENABLED=true et que OTEL_EXPORTER_OTLP_ENDPOINT pointe vers la bonne adresse de collecteur.

Volume d'échantillonnage élevé en production

Exporter chaque span est coûteux à fort volume de trafic.

Solution : Réduisez le ratio d'échantillonnage :

bash
OTEL_TRACES_SAMPLER=parentbased_traceidratio OTEL_TRACES_SAMPLER_ARG=0.1 # Sample 10% of traces

L'échantillonnage basé sur le parent signifie que si une requête entrante porte une trace échantillonnée, elle sera toujours échantillonnée quel que soit le ratio. Les nouvelles traces démarrées par OxPHP sont échantillonnées au taux configuré. Si OTEL_TRACES_SAMPLER est réglé sur une valeur inconnue, OxPHP journalise un avertissement et revient à parentbased_traceidratio.

Voir aussi