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. Les valeurs sont enregistrées brutes — contrairement à db.statement, elles ne sont pas obfusquées, ce qui peut faire entrer des PII (e-mails, jetons, etc.) dans vos traces. À n'activer que là où c'est acceptable. 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, oxphp_apm_error(), ou une exception non gérée / erreur fatale sur le span racine de la requête (voir Capture automatique des exceptions sur le span racine) |
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.
Capture automatique des exceptions sur le span racine
Lorsqu'une requête échoue sur une exception non gérée ou une erreur fatale et répond 5xx, OxPHP attache automatiquement un événement exception au span racine de la requête — aucun attribut #[OxPHP\Apm\Trace] ni appel à oxphp_apm_error() requis. Un 500 devient auto-descriptif dans la trace, et les backends qui regroupent les erreurs par événement d'exception (l'Errors Inbox de New Relic, par exemple) s'allument sans le moindre code applicatif.
L'événement porte les attributs standard exception.type, exception.message et exception.stacktrace, plus deux extensions OxPHP, exception.file et exception.line, qui pointent sur le site du throw (ou l'emplacement de l'erreur fatale). Pour une erreur fatale sans classe — un trigger_error(…, E_USER_ERROR), un abandon pour dépassement de mémoire, un délai d'exécution écoulé — exception.type est un nom synthétique (la constante d'erreur PHP, par exemple E_USER_ERROR) et aucune trace d'appels n'est présente. Appeler une fonction non définie n'est pas une erreur fatale sans classe en PHP 8 : cela lève une Error ordinaire, qui porte une trace d'appels complète comme toute autre exception. Le message et la trace d'appels obéissent aux mêmes plafonds OTEL_APM_MESSAGE_MAX_BYTES / OTEL_APM_STACKTRACE_MAX_BYTES que les autres événements d'exception.
Cela fonctionne pour le PHP brut, pour les applications sans gestionnaire d'exceptions personnalisé et pour les gestionnaires en mode worker.
Limite : les frameworks qui avalent les exceptions (chemin de requête traditionnel). Dans les modes Traditionnel / Framework / SPA, une application qui installe set_exception_handler() et rend sa propre page d'erreur (Laravel, Symfony, WordPress, …) a géré l'exception du point de vue du moteur. Elle ne se propage jamais sans être rattrapée, si bien qu'OxPHP ne voit que le statut 500 et ne peut pas récupérer le Throwable. La capture automatique ne se déclenche pas pour ces requêtes ; enregistrez l'exception explicitement depuis le rapporteur d'erreurs de votre framework avec oxphp_apm_error($e).
Le mode worker ne passe pas par set_exception_handler(). Le runtime worker rattrape directement une exception qui s'échappe de votre closure oxphp_worker(), sans invoquer le gestionnaire d'exceptions utilisateur du moteur. Pour les gestionnaires worker, la capture automatique se déclenche donc dès qu'une exception quitte la closure, même si le code a enregistré son propre set_exception_handler() — ce gestionnaire ne s'applique alors qu'aux exceptions que la closure rattrape elle-même, pas à celles qui s'en échappent.
Réponses en streaming. Pour une réponse diffusée en flux (SSE, ou toute boucle oxphp_stream_flush()), le statut HTTP est figé dès que les en-têtes sont sur le réseau, et la requête est considérée comme terminée à ce moment-là. Une erreur fatale levée après que le statut a été engagé — sur une réponse en streaming, ou sur une réponse ayant appelé finish_request() — est seulement journalisée ; elle n'est pas ajoutée au span racine (la trace montre toujours le span, sans l'événement exception). C'est une limite documentée, qui vaut autant pour un 5xx engagé que pour un 2xx engagé.
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 34 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::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.
Attributs de span des bases de données
Les hooks base de données (PDO, mysqli) décorent leurs spans avec les attributs des conventions sémantiques OpenTelemetry :
| Attribut | Source | Exemple |
|---|---|---|
db.statement |
Texte de la requête avec les valeurs littérales remplacées par ?, si bien que les PII (e-mails, jetons) n'atteignent jamais le backend de traçage |
SELECT * FROM users WHERE email = ? |
db.operation |
Mot-clé SQL de tête | SELECT |
db.system |
Déduit du DSN PDO / du constructeur mysqli | mysql, postgresql, sqlite |
server.address, server.port |
Hôte/port de la connexion (omis pour un socket unix ou un fichier SQLite) | db.internal, 5432 |
db.name |
Nom de la base, ou le chemin du fichier pour SQLite | shop |
oxphp.db.slow |
true lorsque le temps réel de l'appel atteint ou dépasse OTEL_APM_SLOW_QUERY_MS |
true |
db.params |
Paramètres liés, enregistrés bruts (non obfusqués) — ils peuvent donc contenir des PII ; uniquement lorsque OTEL_APM_DB_CAPTURE_PARAMS_ENABLED=true |
[1, active] |
db.statement est lu depuis les arguments propres à chaque appel query / exec / prepare, il apparaît donc sur ce span. Sur un span PDOStatement::execute, il est aussi présent, lu depuis la propriété queryString de l'objet statement lui-même, si bien qu'il ne peut jamais être le SQL d'un autre statement. Un span mysqli_stmt::execute n'a pas de db.statement (mysqli n'expose pas une telle propriété), mais le SQL figure sur le span mysqli::prepare qui le précède. Chaque span execute porte le chronométrage, et avec lui l'indicateur de requête lente, et pour PDO également db.params. Les hooks de cache (Redis, Memcached), de client HTTP (cURL) et d'E/S de fichiers émettent un simple span de chronométrage.
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.11.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.11.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.11.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.11.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