open-telemetry/exporter-otlp
OpenTelemetry OTLP exporter for PHP. Send traces to an OpenTelemetry Collector via HTTP (JSON/protobuf) or gRPC (with transport-grpc). Requires a protobuf runtime; for production, install the protobuf PECL extension for best performance.
Installation Add the package via Composer:
composer require open-telemetry/exporter-otlp
Require the autoloader in your Laravel app (handled automatically by Composer).
First Use Case: Basic Tracing
Initialize the OTLP exporter in your bootstrap/app.php or a service provider:
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
use OpenTelemetry\SDK\Resource\ResourceInfo;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationLibrary;
use OpenTelemetry\Exporter\OTLP\OtlpHttp\OtlpHttpSpanExporter;
$exporter = new OtlpHttpSpanExporter(
'http://your-collector:4318/v1/traces', // OTLP endpoint
OtlpHttpSpanExporter::SECURE // or OtlpHttpSpanExporter::INSECURE for local testing
);
$provider = new TracerProvider();
$provider->addSpanProcessor(new BatchSpanProcessor($exporter));
$provider->register();
Verify with a Test Span Create a simple route to test:
use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Trace\TracerInterface;
Route::get('/test-trace', function () {
$tracer = Globals::tracerProvider()->getTracer('laravel.test');
$span = $tracer->spanBuilder('test-span')->startSpan();
$span->end();
return 'Trace sent!';
});
Check Collector Verify traces appear in your OTLP collector (e.g., Jaeger, Zipkin, or OTLP-compatible backend).
Use middleware to automatically trace incoming requests:
namespace App\Http\Middleware;
use Closure;
use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Trace\TracerInterface;
class TraceMiddleware
{
public function handle($request, Closure $next)
{
$tracer = Globals::tracerProvider()->getTracer('laravel.http');
$span = $tracer->spanBuilder('HTTP ' . $request->method())
->setAttribute('http.url', $request->fullUrl())
->startSpan();
try {
$response = $next($request);
$span->setAttribute('http.status_code', $response->getStatusCode());
return $response;
} finally {
$span->end();
}
}
}
Register in app/Http/Kernel.php:
protected $middleware = [
\App\Http\Middleware\TraceMiddleware::class,
];
Wrap database queries with spans:
use OpenTelemetry\API\Globals;
use Illuminate\Support\Facades\DB;
DB::listen(function ($query) {
$tracer = Globals::tracerProvider()->getTracer('laravel.db');
$span = $tracer->spanBuilder('SQL Query')
->setAttribute('db.type', 'mysql')
->setAttribute('db.query', $query->sql)
->startSpan();
try {
$result = $query->run();
$span->setAttribute('db.duration_ms', $query->time * 1000);
return $result;
} finally {
$span->end();
}
});
Propagate spans across service boundaries (e.g., HTTP clients):
use OpenTelemetry\API\Globals;
use Illuminate\Support\Facades\Http;
$response = Http::withOptions([
'headers' => [
'traceparent' => Globals::propagation()->getBaggageHeader(),
],
])->get('https://external-service.com/api');
Set static resource attributes (e.g., service name, version) in bootstrap/app.php:
$resource = ResourceInfo::create([
'service.name' => 'laravel-app',
'service.version' => '1.0.0',
'deployment.environment' => app()->environment(),
]);
$provider = new TracerProvider();
$provider->setResource($resource);
Service Container Binding Bind the tracer provider to Laravel’s container for easy access:
$app->singleton(TracerProvider::class, function () {
return new TracerProvider();
});
Then inject TracerProvider into controllers/services:
use OpenTelemetry\SDK\Trace\TracerProvider;
public function __construct(private TracerProvider $tracerProvider) {}
Queue Job Tracing
Trace async jobs by wrapping handle():
use OpenTelemetry\API\Globals;
public function handle()
{
$tracer = Globals::tracerProvider()->getTracer('laravel.queue');
$span = $tracer->spanBuilder('Job: ' . static::class)->startSpan();
try {
// Job logic
} finally {
$span->end();
}
}
Artisan Commands Trace CLI commands:
use OpenTelemetry\API\Globals;
use Symfony\Component\Console\Command\Command;
protected function execute(InputInterface $input, OutputInterface $output)
{
$tracer = Globals::tracerProvider()->getTracer('laravel.artisan');
$span = $tracer->spanBuilder($this->getName())->startSpan();
try {
parent::execute($input, $output);
} finally {
$span->end();
}
}
Exporter Configuration
$exporter = new OtlpHttpSpanExporter(
'http://collector:4318',
OtlpHttpSpanExporter::SECURE,
30 // timeout in seconds
);
use OpenTelemetry\Exporter\OTLP\OtlpHttp\OtlpHttpSpanExporter;
use GuzzleHttp\Client;
$client = new Client(['timeout' => 5]);
$exporter = new OtlpHttpSpanExporter('http://collector:4318', OtlpHttpSpanExporter::SECURE, 0, $client);
Span Limits
$processor = new BatchSpanProcessor($exporter, 500, 5); // Max 500 spans, flush every 5 seconds
Context Leaks
Span::end() in finally blocks.OTLP Collector Compatibility
v1 vs. v0.17.0). Mismatches may cause silent failures.Enable Logging Configure the exporter to log errors:
$exporter = new OtlpHttpSpanExporter(
'http://collector:4318',
OtlpHttpSpanExporter::SECURE,
0,
null,
['logger' => new \Monolog\Logger('otlp')]
);
Validate Traces Locally
Use testcontainers or Docker to spin up a temporary OTLP collector (e.g., Jaeger) for testing:
docker run -d --name jaeger \
-e COLLECTOR_OTLP_ENABLED=true \
-p 16686:16686 -p 4317:4317 -p 4318:4318 jaegertracing/all-in-one:latest
Check Headers
Verify traceparent headers are propagated in HTTP requests:
dd(Http::get('https://example.com')->headers());
SpanProcessor to filter or modify spans:
use OpenTelemetry\SDK\Trace\SpanProcessor\SpanProcessorInterface;
class FilterSpanProcessor implements SpanProcessorInterface
{
public function onStart(\OpenTelemetry\SDK\Trace\Span $span) {}
public function onEnd(\OpenTele
How can I help you explore Laravel packages today?