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
- Requête entrante — OxPHP lit les en-têtes
traceparentettracestateconformément à la spécification W3C Trace Context - Nouveau span — un nouvel ID de span est généré pour ce saut. L'ID de span entrant devient le parent
- 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'] - Journal d'accès — les journaux JSON structurés incluent les champs
trace_idetspan_idpour la corrélation des logs - En-têtes de réponse — l'en-tête
traceparentmis à jour (avec l'ID de span d'OxPHP) est ajouté à la réponse, afin que les services en aval puissent poursuivre la trace - 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 |
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
$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
$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 :
{
"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 :
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01Si 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 :
- 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
- 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
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
// 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
$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
$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
// 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 :
services:
app:
image: ghcr.io/oxphp/oxphp:0.10.0
ports:
- "80:80"
environment:
- TRACE_CONTEXT=true
- INTERNAL_ADDR=0.0.0.0:9090Stack d'observabilité complète avec Jaeger comme backend de traçage :
services:
app:
image: ghcr.io/oxphp/oxphp:0.10.0
ports:
- "80:80"
environment:
- OTEL_ENABLED=true
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317
- OTEL_SERVICE_NAME=my-app
- OTEL_SERVICE_VERSION=1.0.0
- OTEL_RESOURCE_ATTRIBUTES=env=production
- INTERNAL_ADDR=0.0.0.0:9090
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686" # Jaeger UI
- "4317:4317" # OTLP gRPCservices:
app:
image: ghcr.io/oxphp/oxphp:0.10.0
ports:
- "80:80"
environment:
- OTEL_ENABLED=true
- OTEL_EXPORTER_OTLP_ENDPOINT=http://tempo:4317
- OTEL_SERVICE_NAME=my-app
tempo:
image: grafana/tempo:latest
ports:
- "4317:4317"
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"Observabilité complète avec instrumentation automatique des requêtes de base de données, des appels HTTP, des opérations de cache et des E/S de fichiers :
services:
app:
image: ghcr.io/oxphp/oxphp:0.10.0
ports:
- "80:80"
environment:
- OTEL_ENABLED=true
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317
- OTEL_SERVICE_NAME=my-app
- OTEL_APM_ENABLED=true
- OTEL_APM_SLOW_QUERY_MS=50
- INTERNAL_ADDR=0.0.0.0:9090
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686" # Jaeger UI
- "4317:4317" # OTLP gRPC
environment:
- COLLECTOR_OTLP_ENABLED=trueLa 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
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 :
docker compose exec app curl -v http://jaeger:4317Vérification : Assurez-vous que le plugin est activé :
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 :
OTEL_TRACES_SAMPLER=parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG=0.1 # Sample 10% of tracesL'é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
- Fonctions PHP — référence des fonctions
oxphp_apm_*() - Décorateurs — interception de fonctions par attribut, dont
#[Trace] - Journalisation des accès — journaux JSON structurés avec champs de trace
- IDs de requête — comment les IDs de requête interagissent avec le contexte de trace
- Métriques — référence des métriques Prometheus
- Contrôles de santé — endpoint
/configaffichant l'état du contexte de trace - Référence de configuration — toutes les variables d'environnement