Śledzenie rozproszone i APM

OxPHP obsługuje propagację W3C Trace Context, eksport OpenTelemetry (OTel) oraz wbudowany Application Performance Monitoring (APM). Przychodzące nagłówki traceparent są parsowane i kontynuowane, identyfikatory śledzenia są dostępne w PHP przez $_SERVER, logi dostępu zawierają pola śledzenia, a spany można eksportować do Jaegera, Grafany Tempo, Zipkina lub dowolnego zaplecza zgodnego z OTLP.

Wtyczka APM dodaje trzy warstwy śledzenia na fundamencie OTel:

  • Automatyczna instrumentacja — wewnętrzne funkcje PHP (PDO, mysqli, cURL, Redis, Memcached, operacje we/wy na plikach) są podpinane na poziomie silnika; każde wywołanie staje się spanem bez żadnych zmian w kodzie
  • Śledzenie oparte na atrybutach — oznacz dowolną funkcję lub metodę PHP atrybutem #[OxPHP\Apm\Trace], aby automatycznie tworzyć spany
  • PHP SDK — 10 funkcji oxphp_apm_*() do ręcznego tworzenia spanów, ustawiania atrybutów, zdarzeń i rejestrowania błędów

Jak to działa

  1. Przychodzące żądanie — OxPHP odczytuje nagłówki traceparent i tracestate zgodnie ze specyfikacją W3C Trace Context
  2. Nowy span — dla tego skoku generowany jest nowy identyfikator spana. Przychodzący identyfikator spana staje się rodzicem
  3. Propagacja do PHP — identyfikatory śledzenia są wstrzykiwane do $_SERVER['OXPHP_TRACE_ID'], $_SERVER['OXPHP_SPAN_ID'] oraz $_SERVER['OXPHP_PARENT_SPAN_ID']
  4. Log dostępu — ustrukturyzowane logi JSON zawierają pola trace_id i span_id na potrzeby korelacji logów
  5. Nagłówki odpowiedzi — zaktualizowany nagłówek traceparent (z identyfikatorem spana OxPHP) jest dodawany do odpowiedzi, dzięki czemu usługi w dalszej części łańcucha mogą kontynuować śledzenie
  6. Eksport OTel (opcjonalnie) — gdy wtyczka OTel jest włączona, każde żądanie staje się spanem eksportowanym przez OTLP z atrybutami konwencji semantycznej HTTP

Jeśli nagłówek traceparent nie jest obecny, OxPHP generuje nowy identyfikator śledzenia i identyfikator spana oraz rozpoczyna nowe śledzenie.

Konfiguracja

W3C Trace Context (wbudowany)

Zmienna Wartość domyślna Opis
TRACE_CONTEXT false Włącza propagację W3C Trace Context. Ustaw na true lub 1

Wtyczka OpenTelemetry

Wtyczka OTel to funkcja włączana podczas kompilacji (plugin-otel). Po włączeniu automatycznie aktywuje propagację trace-context (ten sam efekt co ustawienie TRACE_CONTEXT=true).

Zmienna Wartość domyślna Opis
OTEL_ENABLED false Włącza wtyczkę OpenTelemetry. Wartość logiczna — zobacz Wartości logiczne
OTEL_EXPORTER_OTLP_PROTOCOL grpc Protokół eksportu: grpc lub http/protobuf
OTEL_EXPORTER_OTLP_ENDPOINT http://localhost:4317 (gRPC) lub http://localhost:4318 (HTTP) Endpoint kolektora OTLP. Adres URL https:// jest eksportowany przez TLS na obu transportach, weryfikowany względem systemowego magazynu zaufania (obraz uruchomieniowy musi dostarczać pakiet CA, taki jak ca-certificates — oficjalny obraz go instaluje); niestandardowe pakiety CA oraz mTLS nie są jeszcze obsługiwane
OTEL_EXPORTER_OTLP_TIMEOUT 10000 Limit czasu eksportu w milisekundach
OTEL_EXPORTER_OTLP_HEADERS (nieustawione) Nagłówki uwierzytelniania: key=value,key2=value2
OTEL_SERVICE_NAME oxphp Nazwa usługi w eksportowanych spanach
OTEL_SERVICE_VERSION (nieustawione) Atrybut wersji usługi
OTEL_RESOURCE_ATTRIBUTES (nieustawione) Dodatkowe atrybuty zasobu: env=prod,region=us-east-1
OTEL_TRACES_SAMPLER parentbased_traceidratio Strategia próbkowania: always_on, always_off, traceidratio, parentbased_always_on, parentbased_always_off, parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG 1.0 Współczynnik próbkowania (0.0–1.0) dla samplerów opartych na współczynniku
Note

Nieprawidłowe lub wykraczające poza zakres wartości OTEL_TRACES_SAMPLER_ARG są przycinane do [0.0, 1.0] i logowane na poziomie warn. Nieznane wartości OTEL_TRACES_SAMPLER są zastępowane wartością parentbased_traceidratio i logowane.

Wtyczka APM

Wtyczka APM to funkcja włączana podczas kompilacji (plugin-apm), która zależy od wtyczki OTel. Dodaje automatyczną instrumentację, dekorator #[OxPHP\Apm\Trace] oraz PHP-owy SDK do śledzenia.

Zmienna Wartość domyślna Opis
OTEL_APM_ENABLED false Włącza APM: automatyczną instrumentację, przechwytywanie błędów, PHP SDK. Wymaga OTEL_ENABLED=true. Wartość logiczna — zobacz Wartości logiczne
OTEL_APM_SLOW_QUERY_MS 100 Próg wolnego zapytania w milisekundach. Zapytania powyżej tego progu otrzymują oxphp.db.slow=true na swoich spanach
OTEL_APM_DB_CAPTURE_PARAMS_ENABLED false Rejestruje parametry powiązania w atrybucie spana db.params. Wartość logiczna — zobacz Wartości logiczne
OTEL_APM_STACKTRACE_MAX_BYTES 8192 Maksymalny rozmiar w bajtach atrybutu exception.stacktrace. Po przekroczeniu limitu ślad stosu jest przycinany od końca (ramka główna jest zachowywana) ze znacznikiem …(truncated). 0 wyłącza przycinanie
OTEL_APM_MESSAGE_MAX_BYTES 4096 Maksymalny rozmiar w bajtach atrybutu exception.message (wartość domyślna odpowiada limitowi wartości na atrybut w New Relic). Po przekroczeniu limitu komunikat jest przycinany od końca ze znacznikiem …(truncated). 0 wyłącza przycinanie

Kontekst śledzenia w PHP

Gdy TRACE_CONTEXT=true, w skryptach PHP dostępne są trzy zmienne $_SERVER:

Zmienna Opis Przykład
OXPHP_TRACE_ID Identyfikator śledzenia W3C (32 znaki hex) 4bf92f3577b34da6a3ce929d0e0e4736
OXPHP_SPAN_ID Identyfikator spana OxPHP dla tego żądania (16 znaków hex) 00f067aa0ba902b7
OXPHP_PARENT_SPAN_ID Przychodzący identyfikator spana nadrzędnego (16 znaków hex, pusty przy nowym śledzeniu) a3ce929d0e0e4736

Użyj ich, aby propagować kontekst śledzenia do usług w dalszej części łańcucha:

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", ], ]) ); }

Z 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", ], ]);

Korelacja logów dostępu

Gdy kontekst śledzenia jest włączony, ustrukturyzowane logi dostępu w formacie JSON zawierają pola trace_id i 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" } }

Możesz następnie wyszukiwać logi według identyfikatora śledzenia w systemach agregacji logów (Loki, Elasticsearch, Splunk, CloudWatch), aby znaleźć każdy wpis logu dla śledzenia rozproszonego.

Nagłówki odpowiedzi

OxPHP dodaje nagłówek traceparent do każdej odpowiedzi, z własnym identyfikatorem spana OxPHP:

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

Jeśli przychodzące żądanie zawierało nagłówek tracestate, jest on również przekazywany w odpowiedzi.

Integracja z OpenTelemetry

Gdy wtyczka OTel jest włączona, każde żądanie HTTP staje się spanem eksportowanym do Twojego zaplecza śledzenia przez OTLP.

Atrybuty spana

Eksportowane spany zawierają standardowe atrybuty konwencji semantycznej HTTP:

Atrybut Opis
http.request.method Metoda HTTP (GET, POST itd.)
url.path Ścieżka żądania
http.response.status_code Kod statusu odpowiedzi
client.address Adres IP klienta
server.address Adres nasłuchiwania serwera
oxphp.request_id ID żądania OxPHP
http.request.body.size Rozmiar ciała żądania w bajtach (jeśli różny od zera)
http.response.body.size Rozmiar ciała odpowiedzi w bajtach (jeśli różny od zera)

Odpowiedzi 5xx są oznaczane jako spany błędów.

Zdarzenia spana

Spany potomne przenoszą również zdarzenia spana — opatrzone znacznikiem czasu adnotacje eksportowane jako zdarzenia OpenTelemetry i renderowane natywnie przez Jaegera, Grafanę Tempo oraz inne zaplecza OTLP. Atrybut oxphp.event.kind w każdym zdarzeniu identyfikuje jego typ:

oxphp.event.kind Źródło Atrybuty zdarzenia
exception Funkcja #[OxPHP\Apm\Trace], która rzuciła wyjątek, lub oxphp_apm_error() exception.type, exception.message, exception.stacktrace
custom oxphp_apm_event() dostarczone przez użytkownika
mark adnotacja profilera #[Mark] dostarczone przez użytkownika
slow przekroczenie profilera #[SlowThreshold] threshold_ms, elapsed_ms
memory_spike przekroczenie profilera #[MemoryThreshold] threshold_kb, delta_bytes

Atrybut oxphp.event.kind może dodatkowo przyjmować wartości sql, http lub alloc w zdarzeniach generowanych przez instrumentację APM.

ID żądania w połączeniu z OTel

Gdy wtyczka OTel jest aktywna, identyfikatory żądań są wyprowadzane z kontekstu śledzenia: pierwsze 16 znaków identyfikatora śledzenia oraz pierwsze 8 znaków identyfikatora spana, oddzielone myślnikiem. Pojawia się to w logach, nagłówku odpowiedzi X-Request-ID oraz w oxphp_request_id() w PHP.

APM: automatyczna instrumentacja

Gdy wtyczka APM jest włączona, OxPHP automatycznie podpina 33 wewnętrzne funkcje PHP na poziomie silnika. Każde wywołanie podpiętej funkcji tworzy span potomny pod spanem głównym bieżącego żądania — bez konieczności wprowadzania jakichkolwiek zmian w kodzie.

Podpięte funkcje

Kategoria Funkcje
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
We/wy plików fopen, fread, fwrite, file_get_contents, file_put_contents

Podpięcia (hooks) są instalowane tylko dla rozszerzeń, które są faktycznie załadowane. Jeśli Twoja kompilacja nie zawiera rozszerzenia Redis, podpięcia Redis są po cichu pomijane.

Instalacja podpięć

Instalacja podpięć wykorzystuje dwufazowy projekt zapewniający bezpieczeństwo wątkowe w PHP ZTS:

  1. Faza 1 (MINIT) — podczas inicjalizacji modułu OxPHP weryfikuje każdą docelową funkcję względem załadowanych rozszerzeń i przechwytuje oryginalne wskaźniki do handlerów na tylko-do-odczytu listę zatwierdzonych
  2. Faza 2 (RINIT) — przy pierwszym żądaniu na każdym wątku worker zatwierdzone podpięcia są instalowane w tablicach funkcji tego wątku

Zapewnia to, że każdy wątek worker ZTS ma spójne modyfikacje tablicy funkcji oraz stan lokalny wątku.

APM: śledzenie oparte na atrybutach

Atrybut #[OxPHP\Apm\Trace] automatycznie tworzy spany wokół udekorowanych funkcji i metod. W przeciwieństwie do podpięć automatycznej instrumentacji (które celują w wewnętrzne funkcje C), działa on na kodzie PHP zdefiniowanym przez użytkownika.

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

Atrybut #[Trace] obejmuje zarówno funkcje, jak i metody. Nie jest potrzebne żadne wywołanie rejestrujące — wtyczka APM rejestruje dekorator automatycznie podczas inicjalizacji.

Jeśli udekorowana funkcja rzuci wyjątek, status spana jest ustawiany na error, a zdarzenie exception jest rejestrowane z pełnymi danymi konwencji semantycznej OpenTelemetry: exception.type (klasa), exception.message (komunikat) oraz exception.stacktrace (stos wywołań z getTraceAsString()). Komunikat jest przycinany do OTEL_APM_MESSAGE_MAX_BYTES bajtów (domyślnie 4096), a ślad stosu do OTEL_APM_STACKTRACE_MAX_BYTES bajtów (domyślnie 8192); 0 wyłącza dowolny z tych limitów. Przechwytywanie argumentów wewnątrz ramek jest zgodne z ustawieniem zend.exception_ignore_args w samym PHP.

APM: PHP-owy SDK do śledzenia

Wtyczka APM rejestruje 10 funkcji oxphp_apm_*() do ręcznego zarządzania spanami. Wszystkie funkcje są bezpiecznymi no-op, gdy APM jest wyłączony, więc Twój kod działa bez modyfikacji w dowolnym środowisku.

Tworzenie spanów

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);

Dodawanie atrybutów i zdarzeń

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);

Rejestrowanie błędów

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); }

Propagowanie kontekstu śledzenia

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", ], ]) );

Referencja funkcji

Funkcja Zwraca Opis
oxphp_apm_trace(name, callback, ?attributes) void Wykonuje callback wewnątrz spana (zarezerwowane do przyszłego użytku)
oxphp_apm_start(name, ?attributes) int Otwiera span i zwraca jego lokalny ID. 0, gdy APM jest wyłączony
oxphp_apm_end(span_id) void Zamyka span o podanym lokalnym ID
oxphp_apm_attribute(key, value, ?span_id) void Ustawia atrybut na bieżącym lub wskazanym spanie
oxphp_apm_event(name, ?attributes, ?span_id) void Rejestruje zdarzenie ze znacznikiem czasu na bieżącym lub wskazanym spanie
oxphp_apm_error(exception, ?span_id) void Oznacza bieżący lub wskazany span jako error i rejestruje zdarzenie exception. Obiekt Throwable dostarcza exception.type, exception.message oraz exception.stacktrace; sam argument tekstowy jest rejestrowany jako exception.message z ogólnym exception.type równym Error (dzięki czemu zdarzenie pozostaje widoczne w zapleczach grupujących według typu)
oxphp_apm_status(code, ?description, ?span_id) void Ustawia status spana: 0 = Unset, 1 = Ok, 2 = Error
oxphp_apm_trace_id() string Bieżący identyfikator śledzenia (32 znaki hex). Pusty, gdy APM jest wyłączony
oxphp_apm_span_id() string Bieżący identyfikator spana (16 znaków hex). Pusty, gdy brak aktywnego spana
oxphp_apm_header() string Wartość nagłówka W3C traceparent dla bieżącego kontekstu spana

Pełną referencję sygnatur funkcji znajdziesz w Funkcje PHP.

Przykład Docker

Gotowe do uruchomienia warianty compose.yaml:

Włącz propagację śledzenia W3C bez zewnętrznego zaplecza:

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

Funkcja Cargo plugin-apm musi być włączona podczas budowania. Oficjalny obraz OxPHP zawiera ją domyślnie.

Stos obserwowalności

OxPHP zapewnia trzy filary obserwowalności, które współpracują ze sobą:

Filar Funkcja Korelacja
Metryki Liczniki i histogramy Prometheusa na /metrics Zagregowane dane o wydajności
Logowanie Ustrukturyzowane logi dostępu JSON z ACCESS_LOG Szczegóły dla każdego żądania, przeszukiwalne po trace_id
Śledzenie W3C Trace Context + eksport OTLP Kompleksowy (end-to-end) rozproszony przepływ żądań

Wszystkie trzy współdzielą ten sam trace_id i request_id, dzięki czemu możesz przejść od alertu na pulpicie Grafany do śledzenia w Tempo, a następnie do linii logów w Loki dla pojedynczego żądania.

Rozwiązywanie problemów

Nagłówki śledzenia nie pojawiają się w odpowiedziach

TRACE_CONTEXT nie jest włączony.

Rozwiązanie: Ustaw TRACE_CONTEXT=true lub włącz wtyczkę OTel przez OTEL_ENABLED=true (co automatycznie włącza kontekst śledzenia).

Zmienne śledzenia w $_SERVER są puste

Kontekst śledzenia jest wyłączony lub zmienne są sprawdzane poza OxPHP.

Sprawdź: Zmienne OXPHP_TRACE_ID, OXPHP_SPAN_ID oraz OXPHP_PARENT_SPAN_ID istnieją tylko wtedy, gdy TRACE_CONTEXT=true i żądanie jest obsługiwane przez OxPHP. Przetestuj za pomocą:

php
<?php echo $_SERVER['OXPHP_TRACE_ID'] ?? 'trace context not enabled';
Spany nie pojawiają się w Jaegerze/Tempo

Sprawdź: Zweryfikuj, czy endpoint OTLP jest osiągalny z kontenera OxPHP:

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

Sprawdź: Zweryfikuj, czy wtyczka jest włączona:

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

Rozwiązanie: Upewnij się, że OTEL_ENABLED=true, a OTEL_EXPORTER_OTLP_ENDPOINT wskazuje na poprawny adres kolektora.

Duża liczba próbkowanych spanów w produkcji

Eksportowanie każdego spana jest kosztowne przy dużym natężeniu ruchu.

Rozwiązanie: Zmniejsz współczynnik próbkowania:

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

Próbkowanie oparte na rodzicu (parent-based) oznacza, że jeśli przychodzące żądanie niesie próbkowane śledzenie, zostanie ono zawsze próbkowane niezależnie od współczynnika. Nowe śledzenia rozpoczynane w OxPHP są próbkowane ze skonfigurowaną częstotliwością. Jeśli OTEL_TRACES_SAMPLER jest ustawiony na nieznaną wartość, OxPHP loguje ostrzeżenie i wraca do parentbased_traceidratio.

Zobacz też