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. Avec http/protobuf, c'est l'URL de base du collecteur, et les spans sont envoyés à /v1/traces sous celle-ci, comme le prescrit la spécification OTLP : http://collector:4318 envoie vers http://collector:4318/v1/traces, n'ajoutez donc pas /v1/traces à la valeur. 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. En gRPC, il borne aussi la connexion au collecteur, poignée de main TLS comprise ; gardez-le donc au-dessus du temps que prend une connexion : une tentative qui doit d'abord se connecter peut durer jusqu'à deux fois ce délai. En gRPC sous Linux, il fixe aussi au bout de combien de temps est fermée une connexion qui n'accepte plus de données — voir Erreurs d'export de spans dans le journal |
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",
"user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0",
"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 |
user_agent.original |
L'en-tête User-Agent de la requête (le premier, si elle en a envoyé plusieurs), coupé à 512 octets (marqueur …(truncated) compris) s'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 |
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) |
oxphp.shed_reason |
Pourquoi l'admission a refusé la requête, sur une requête qu'elle a refusée : queue_full, wait_timeout, waiting_full ou waiting_bytes sur un 529, shutting_down sur un 503, pool_unavailable sur un 500 — les valeurs du label reason de oxphp_admission_refused_total. Absent sinon, y compris sur un 529 renvoyé par l'application elle-même |
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é oxphp_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 le pointeur de handler que chaque cible a à ce moment-là 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.
La phase 1 s'exécute après les remplacements de handlers propres à OxPHP. Avec RUNTIME_HOOKS activant streams, le handler capturé pour une méthode PDO, mysqli ou Redis que cette catégorie intercepte aussi est donc le handler protégé d'OxPHP : pour une requête ou une commande, il réserve la connexion à la Fiber appelante, et pour PDO::__construct, il sérialise les constructeurs persistants vers la même source de données (voir Hooks d'exécution). Le span enveloppe cette protection. Dans les Fibers de requête du mode worker et les Fibers de tâches asynchrones, où la protection peut attendre, la durée du span — et pour une requête, l'indicateur de requête lente — inclut toute attente d'une connexion qu'une autre requête ou tâche utilise.
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. Elles sont toutes enregistrées, que l'APM soit activé ou non, de sorte que votre code fonctionne sans modification dans n'importe quel environnement. Lorsque l'APM est désactivé, les fonctions de span deviennent des no-ops qui retournent des valeurs sentinelles (0 pour les ID, "" pour les chaînes).
La seule exception à la règle du no-op est oxphp_apm_trace() : lorsque l'APM est désactivé, il exécute quand même le callback qui lui a été passé et retourne quand même sa valeur. La logique métier à l'intérieur du callback continue donc de s'exécuter lorsque le traçage est désactivé.
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) |
mixed |
Exécute un callback à l'intérieur d'un span et retourne sa valeur. Le callback reçoit l'ID local du span ; une exception levée passe le span en error avec un événement exception (type et message, sans stacktrace) puis se propage |
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.13.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.13.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.13.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.13.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 — avec http/protobuf, son URL de base sans /v1/traces. Si le journal montre des exports en échec, Erreurs d'export de spans dans le journal explique ce que signifie chacun.
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.
Erreurs d'export de spans dans le journal
OxPHP envoie les spans au collecteur par lots, et journalise un export en échec sur ses propres lignes, avec plugin défini à otel :
WARNOTLP span export failed, retrying once— la première tentative a échoué rapidement, sans réponse du collecteur, et le lot est renvoyé. Champs :spans(taille du lot),error.ERROROTLP span export failed, the batch is dropped— l'export a définitivement échoué, et ses spans ne sont pas renvoyés. Champs :spans,retried,error.
Une ligne WARN qui n'est suivie d'aucune ligne ERROR signifie que la nouvelle tentative a livré le lot. Les erreurs renvoyées par le collecteur lui-même ne sont pas retentées : elles apparaissent sous la forme d'une seule ligne ERROR avec retried à false.
Chaque ligne ERROR s'accompagne d'une seconde ligne pour le même export, écrite par la bibliothèque OpenTelemetry. Son message est vide, et la cause se trouve dans error :
{"level":"ERROR","fields":{"message":"","name":"BatchSpanProcessor.ExportError","error":"Operation failed: code: 'Unknown error', message: \"transport error\", source: ..."}}{"level":"ERROR","fields":{"message":"","name":"BatchSpanProcessor.ExportError","error":"Operation failed: reqwest::Error { kind: Request, url: \"http://collector:4318/v1/traces\", source: ... }"}}Ce que désigne le champ error :
Dans error |
Ce qui s'est passé |
|---|---|
code: 'The request does not have valid authentication credentials' (gRPC), kind: Status(401, None) (http/protobuf) |
Le collecteur a rejeté la clé d'API |
ConnectionReset (l'un ou l'autre protocole), connection closed (gRPC), IncompleteMessage (http/protobuf) |
Un intermédiaire entre OxPHP et le collecteur, comme un répartiteur de charge ou un NAT, a coupé la connexion pendant qu'elle était inactive |
kind: TimedOut, error: Elapsed (gRPC) |
L'export devait d'abord se connecter, et la connexion — résolution de nom, TCP et poignée de main TLS d'un endpoint https:// — ne s'est pas achevée dans le délai OTEL_EXPORTER_OTLP_TIMEOUT. Lorsque le nom se résout en plusieurs adresses, le délai est réparti entre elles ; cela peut donc aussi signifier qu'une adresse n'a pas répondu dans sa part |
Un export sur une connexion dont l'autre extrémité a disparu sans réinitialisation, comme une connexion qu'un NAT ou un répartiteur de charge a abandonnée silencieusement, se heurte à OTEL_EXPORTER_OTLP_TIMEOUT comme tout export auquel le collecteur ne répond pas à temps. En gRPC sous Linux, la connexion est alors fermée, dès que ce qui y a été envoyé est resté sans accusé de réception pendant cette durée, et les exports suivants se reconnectent. La connexion gRPC porte aussi un keepalive TCP — la première sonde après 30 secondes sans trafic, puis une toutes les 10 secondes — qui la maintient dans les tables des NAT et des répartiteurs de charge entre les exports et, avec le délai par défaut, ferme une connexion inactive morte en une minute environ.
Métriques d'export OpenTelemetry compte les exports en échec, leurs spans et les nouvelles tentatives, et explique quand un export est retenté.
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.
Exporte les traces d'OxPHP vers mon backend de traçage. Lis la page `features/distributed-tracing` via le serveur MCP de la documentation OxPHP.
Détermine à partir de mes fichiers Compose ou Kubernetes où se trouve le collecteur, donne-moi le bloc de variables d'environnement et un taux d'échantillonnage que mon trafic peut se permettre, et montre-moi où les appels sortants de ce dépôt doivent propager le contexte de trace.Mes spans OxPHP n'arrivent pas jusqu'à mon backend de traçage. Lis la page `features/distributed-tracing` via le serveur MCP de la documentation OxPHP.
Vérifie les variables d'activation des plugins, l'endpoint et le protocole OTLP, et l'échantillonneur que définit mon déploiement, et dis-moi à quel saut les traces se perdent.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