PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

OpenTelemetry – distributed tracing, auto-instrumentation, Jaeger in DDEV

by Henryk Tews / Tuesday, 20 August 2024 / Published in Środowiska

OpenTelemetry is the emerging standard for distributed tracing, metrics, and logs. Where Blackfire shows you what is slow within a single PHP process, OpenTelemetry shows you what happens across multiple services – from the Magento web server through Redis, MySQL, and external APIs, to the queue consumer. I show auto-instrumentation setup, Jaeger in DDEV for viewing traces, and adding custom spans to your own code.

What OpenTelemetry adds over Blackfire

Aspect Blackfire OpenTelemetry
Scope Single PHP process Distributed across all services
Services PHP only PHP, MySQL, Redis, RabbitMQ, HTTP APIs
Activation On-demand (pull) Always on (push to collector)
Production On-demand profiling Continuous low-overhead tracing
Use case Optimising specific requests Understanding system behaviour

Setup: OpenTelemetry PHP SDK + Jaeger in DDEV

composer require \
    open-telemetry/sdk \
    open-telemetry/exporter-otlp \
    open-telemetry/opentelemetry-auto-psr3 \
    open-telemetry/opentelemetry-auto-pdo \
    open-telemetry/opentelemetry-auto-guzzle
# .ddev/docker-compose.jaeger.yaml
version: '3.6'
services:
  jaeger:
    image: jaegertracing/all-in-one:1.55
    environment:
      COLLECTOR_OTLP_ENABLED: "true"
    ports:
      - "16686:16686"  # Jaeger UI
      - "4317"         # OTLP gRPC
      - "4318"         # OTLP HTTP
    labels:
      com.ddev.site-name: ${DDEV_SITENAME}
# php.ini - enable auto-instrumentation
[opentelemetry]
extension=opentelemetry.so

OTEL_SERVICE_NAME=magento2
OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_TRACES_EXPORTER=otlp
OTEL_PHP_AUTOLOAD_ENABLED=true
OTEL_PROPAGATORS=baggage,tracecontext

Manual instrumentation – custom spans

<?php

declare(strict_types=1);

use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\API\Trace\StatusCode;

class ProductImportService
{
    private \OpenTelemetry\API\Trace\TracerInterface $tracer;

    public function __construct(
        private \Vendor\Module\Model\Api\ExternalApiClient $apiClient,
        private \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
    ) {
        $this->tracer = Globals::tracerProvider()->getTracer('vendor.import');
    }

    public function importBatch(array $skus): array
    {
        $span = $this->tracer->spanBuilder('import.batch')
            ->setAttribute('import.sku_count', count($skus))
            ->setSpanKind(SpanKind::KIND_INTERNAL)
            ->startSpan();

        $scope = $span->activate();
        $results = ['success' => 0, 'failed' => 0];

        try {
            foreach ($skus as $sku) {
                $this->importSingle($sku, $results);
            }

            $span->setAttribute('import.success_count', $results['success']);
            $span->setAttribute('import.failed_count', $results['failed']);
            $span->setStatus(StatusCode::STATUS_OK);

        } catch (\Exception $e) {
            $span->recordException($e);
            $span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());
            throw $e;
        } finally {
            $scope->detach();
            $span->end();
        }

        return $results;
    }

    private function importSingle(string $sku, array &$results): void
    {
        $span = $this->tracer->spanBuilder('import.single')
            ->setAttribute('product.sku', $sku)
            ->startSpan();
        $scope = $span->activate();

        try {
            // Fetch from external API - this span will appear nested in Jaeger
            $data = $this->apiClient->getProduct($sku);

            if ($data === null) {
                $span->setAttribute('product.found', false);
                $results['failed']++;
                return;
            }

            // Save to Magento - SQL queries will appear as child spans automatically
            $this->saveProduct($data);
            $span->setAttribute('product.found', true);
            $results['success']++;

        } catch (\Exception $e) {
            $span->recordException($e);
            $results['failed']++;
        } finally {
            $scope->detach();
            $span->end();
        }
    }
}

Viewing traces in Jaeger

# After ddev restart, Jaeger UI is at:
# http://localhost:16686

# Run a Magento request to generate traces
curl https://magento2-dev.ddev.site/catalog/product/view/id/42

# In Jaeger UI:
# 1. Select service: magento2
# 2. Click "Find Traces"
# 3. Click on a trace to see the waterfall

# Typical trace for a product page:
# - magento2: request (total time)
#   |- magento2: database.query (SELECT catalog_product_entity...)
#   |- magento2: database.query (SELECT catalog_product_index_price...)
#   |- magento2: redis.get (cache lookup)
#   |- magento2: redis.set (cache write)
#   |- magento2: database.query (x18 more queries...)

Trace propagation across services

<?php

// When Magento calls an external service (ERP, PIM, payment gateway),
// propagate the trace context so you can see the full distributed trace

use GuzzleHttp\Client;
use OpenTelemetry\API\Globals;
use OpenTelemetry\Context\Propagation\TraceContextPropagator;

class ErpClient
{
    public function __construct(private Client $httpClient) {}

    public function createOrder(array $orderData): array
    {
        $headers = [];

        // Inject trace context into outgoing request headers
        // The ERP service (if also instrumented) will continue the same trace
        TraceContextPropagator::getInstance()->inject($headers);

        $response = $this->httpClient->post('/api/orders', [
            'headers' => $headers, // includes traceparent header
            'json'    => $orderData,
        ]);

        return json_decode($response->getBody(), true);
    }
}

Summary

OpenTelemetry provides the “big picture” view that Blackfire cannot – distributed traces across PHP, databases, caches, and external services in a single waterfall diagram. Auto-instrumentation with the PHP SDK instruments PDO, HTTP clients, and PSR-3 loggers with zero code changes. Custom spans add business-level visibility to your own code. Jaeger in DDEV takes 10 minutes to set up and immediately answers “why is this request slow” across service boundaries.

About Henryk Tews

What you can read next

DDEV advanced – mutagen, custom services, hooks, shared team configuration
DDEV – local Magento 2 in 10 minutes, comparison with XAMPP
Blackfire – DDEV setup, HTTP and CLI profiling, CI/CD assertions

© 2026 Created by

TOP
Zarządzaj zgodą
Aby zapewnić jak najlepsze wrażenia, korzystamy z technologii, takich jak pliki cookie, do przechowywania i/lub uzyskiwania dostępu do informacji o urządzeniu. Zgoda na te technologie pozwoli nam przetwarzać dane, takie jak zachowanie podczas przeglądania lub unikalne identyfikatory na tej stronie. Brak wyrażenia zgody lub wycofanie zgody może niekorzystnie wpłynąć na niektóre cechy i funkcje.
Funkcjonalne Always active
Przechowywanie lub dostęp do danych technicznych jest ściśle konieczny do uzasadnionego celu umożliwienia korzystania z konkretnej usługi wyraźnie żądanej przez subskrybenta lub użytkownika, lub wyłącznie w celu przeprowadzenia transmisji komunikatu przez sieć łączności elektronicznej.
Preferencje
Przechowywanie lub dostęp techniczny jest niezbędny do uzasadnionego celu przechowywania preferencji, o które nie prosi subskrybent lub użytkownik.
Statystyka
Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do celów statystycznych. Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do anonimowych celów statystycznych. Bez wezwania do sądu, dobrowolnego podporządkowania się dostawcy usług internetowych lub dodatkowych zapisów od strony trzeciej, informacje przechowywane lub pobierane wyłącznie w tym celu zwykle nie mogą być wykorzystywane do identyfikacji użytkownika.
Marketing
Przechowywanie lub dostęp techniczny jest wymagany do tworzenia profili użytkowników w celu wysyłania reklam lub śledzenia użytkownika na stronie internetowej lub na kilku stronach internetowych w podobnych celach marketingowych.
  • Manage options
  • Manage services
  • Manage {vendor_count} vendors
  • Read more about these purposes
Zobacz preferencje
  • {title}
  • {title}
  • {title}