open-telemetry/opentelemetry
OpenTelemetry PHP metapackage bundling the API and SDK plus common HTTP exporters (OTLP, Zipkin), a PSR-7 factory (nyholm/psr7), and Symfony HTTP client. Great for trying OpenTelemetry; for production, require needed packages directly.
Installation Add the metapackage to your Laravel project via Composer:
composer require open-telemetry/opentelemetry
For production, replace with explicit package versions (e.g., open-telemetry/sdk, open-telemetry/exporter-otlp).
First Use Case: Basic Tracing
Initialize a tracer in bootstrap/app.php or a service provider:
use OpenTelemetry\API\Trace\TracerInterface;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
$tracerProvider = new TracerProvider();
$tracerProvider->addSpanProcessor(new SimpleSpanProcessor());
$tracer = $tracerProvider->getTracer('laravel-app');
Start a root span in a controller or middleware:
$span = $tracer->spanBuilder('user-request')->startSpan();
try {
// Your logic here
} finally {
$span->end();
}
Exporter Configuration
Configure an OTLP exporter (e.g., for Jaeger or Honeycomb) in a config file (config/opentelemetry.php):
return [
'exporter' => [
'otlp' => [
'endpoint' => env('OPENTELEMETRY_ENDPOINT', 'http://localhost:4318'),
'headers' => [
'Authorization' => 'Bearer ' . env('OPENTELEMETRY_API_KEY'),
],
],
],
];
Attach it to the provider:
$exporter = new \OpenTelemetry\Exporter\OTlp\OtlpHttpExporter(
new \Nyholm\Psr7\Factory\Psr17Factory(),
new \Symfony\Contracts\HttpClient\HttpClientInterface(),
$config['exporter']['otlp']
);
$tracerProvider->addSpanProcessor(new \OpenTelemetry\SDK\Trace\SpanProcessor\BatchingSpanProcessor($exporter));
Middleware-Based Tracing Create a middleware to auto-instrument HTTP requests:
namespace App\Http\Middleware;
use Closure;
use OpenTelemetry\API\Trace\TracerInterface;
class TelemetryMiddleware
{
public function __construct(protected TracerInterface $tracer) {}
public function handle($request, Closure $next)
{
$span = $this->tracer->spanBuilder('http-request')
->setAttribute('http.method', $request->method())
->setAttribute('http.url', $request->url())
->startSpan();
try {
return $next($request)->withSpan($span);
} finally {
$span->end();
}
}
}
Register it in app/Http/Kernel.php:
protected $middleware = [
\App\Http\Middleware\TelemetryMiddleware::class,
];
Database Query Tracing Use a query listener to trace Eloquent queries:
use OpenTelemetry\API\Trace\TracerInterface;
use Illuminate\Database\Events\QueryExecuted;
public function boot()
{
\DB::listen(function (QueryExecuted $query) {
$span = app(TracerInterface::class)->spanBuilder('db-query')
->setAttribute('db.system', 'mysql')
->setAttribute('db.statement', $query->sql)
->startSpan();
try {
// Simulate query execution time
} finally {
$span->end();
}
});
}
Context Propagation Propagate spans across service boundaries (e.g., HTTP clients):
use OpenTelemetry\Context\Context;
use OpenTelemetry\Context\Propagation\TextMapPropagator;
$propagator = new TextMapPropagator();
$context = Context::withPropagator($propagator);
$httpClient = \Symfony\Contracts\HttpClient\HttpClient::create();
$response = $httpClient->request('GET', 'https://api.example.com', [
'headers' => $propagator->inject(
['traceparent' => ''],
Context::current()
),
]);
TracerInterface and MeterInterface to the container in a service provider:
$this->app->singleton(TracerInterface::class, function ($app) {
$provider = new TracerProvider();
$provider->addSpanProcessor(new BatchingSpanProcessor(
new OtlpHttpExporter(/* ... */)
));
return $provider->getTracer('laravel-app');
});
handle():
public function handle()
{
$span = app(TracerInterface::class)->spanBuilder('job-' . $this->job->id)->startSpan();
try {
// Job logic
} finally {
$span->end();
}
}
HttpClient for automatic context propagation:
$client = \OpenTelemetry\Contrib\Http\Client::create();
$response = $client->request('GET', 'https://api.example.com');
Span Leaks
try-finally blocks) can leak memory.try-finally or a SpanProcessor with auto-flushing (e.g., BatchingSpanProcessor).tracerProvider->forceFlush().Context Propagation Failures
traceparent) are injected/carried:
$propagator = new TextMapPropagator();
$carrier = [];
$propagator->inject($carrier, Context::current());
assert(isset($carrier['traceparent']));
Exporter Timeouts
$exporter = new OtlpHttpExporter(/* ... */, [
'timeout' => 2.0,
'retry' => [
'max_attempts' => 3,
'delay' => 100,
],
]);
Attribute Limits
SimpleSpanProcessor with a custom logger:
$processor = new SimpleSpanProcessor(function ($span) {
\Log::debug('Span', [
'name' => $span->getName(),
'attributes' => $span->getAttributes(),
]);
});
\Log::debug('Current Context', [
'trace_id' => Context::current()->getValue('trace_id'),
]);
Custom Span Processors
Extend SpanProcessorInterface to filter or modify spans:
class FilterSpanProcessor implements SpanProcessorInterface
{
public function onStart(Span $span, ?Span $parentSpan = null) {}
public function onEnd(Span $span) {
if ($span->getName() === 'ignored-span') {
$span->setStatus(new Status(StatusCode::ERROR_UNKNOWN, 'Filtered'));
}
}
}
Resource Attributes
Add static metadata (e.g., service version) via Resource:
$resource = new Resource([
'service.name' => 'laravel-app',
'service.version' => '1.0.0',
]);
$tracerProvider = new TracerProvider([], [$resource]);
Instrumentation Libraries Use community packages like:
opentelemetry/opentelemetry-php-instrumentation for auto-instrumentation.spatie/laravel-activitylog + OTLP exporter for activity tracing..env for exporter endpoints:
How can I help you explore Laravel packages today?