open-telemetry/api
OpenTelemetry PHP API package: vendor-neutral interfaces and context propagation for traces, metrics, and logs. Use it to instrument libraries/apps while staying decoupled from any specific SDK implementation. Documentation at opentelemetry.io.
Install the package:
composer require open-telemetry/api
(Note: This is a pure API package—you’ll need an SDK like open-telemetry/sdk for actual instrumentation.)
First use case: Instrument a route with tracing
use OpenTelemetry\API\Common\Instrumentation\SpanKind;
use OpenTelemetry\API\Trace\Span;
use OpenTelemetry\API\Trace\TracerInterface;
// In your Laravel service provider or route closure:
$tracer = \OpenTelemetry\API\GlobalTracer::getTracer('my-app');
$span = $tracer->spanBuilder('process-request')
->setSpanKind(SpanKind::SPAN_KIND_SERVER)
->startSpan();
try {
// Your route logic here
$span->addEvent('request-processed');
} finally {
$span->end();
}
Key entry points:
GlobalTracer::getTracer() → For trace instrumentation.GlobalMeter::getMeter() → For metrics.GlobalPropagator::getTextMapPropagator() → For context propagation.Where to look first:
src/API for core interfaces.examples in the monorepo for Laravel-specific patterns.Use the open-telemetry/auto-instrumentation package to auto-instrument HTTP requests, database queries, and queues:
// In `AppServiceProvider`:
\OpenTelemetry\AutoInstrumentation\AutoInstrumentation::register(
\OpenTelemetry\AutoInstrumentation\Instrumentation\LaravelInstrumentation::class
);
// Wrap business logic in spans:
$span = $tracer->spanBuilder('user-authentication')
->setAttribute('user.id', $userId)
->startSpan();
try {
$authService->authenticate($credentials);
$span->setStatus(Span::STATUS_OK);
} catch (Exception $e) {
$span->recordException($e);
$span->setStatus(Span::STATUS_ERROR, 'Authentication failed');
} finally {
$span->end();
}
Inject context into outgoing requests and extract from incoming:
// Incoming request (e.g., middleware):
$propagator = \OpenTelemetry\API\GlobalPropagator::getTextMapPropagator();
$context = $propagator->extract(
\OpenTelemetry\Context\Context::getCurrent(),
$_SERVER
);
// Outgoing request (e.g., HTTP client):
$propagator->inject($context, $headers);
$meter = \OpenTelemetry\API\GlobalMeter::getMeter('my-app');
$counter = $meter->getInt64Counter('api.requests');
$counter->add(1, ['method' => 'GET', 'endpoint' => '/users']);
$histogram = $meter->getHistogram('request.latency.ms');
$histogram->record($executionTimeMs, ['endpoint' => '/users']);
$meter->getObservableGauge('cache.hits')->addCallback(
function (callable $observer) {
$observer($this->getCacheHits());
}
);
// config/app.php
'providers' => [
// ...
OpenTelemetry\SDK\Laravel\OpenTelemetryServiceProvider::class,
];
// .env
OTEL_SERVICE_NAME=my-laravel-app
OTEL_TRACES_EXPORTER=otlp
OTEL_METRICS_EXPORTER=otlp
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
// app/Http/Middleware/TraceRequests.php
public function handle($request, Closure $next) {
$span = \OpenTelemetry\API\GlobalTracer::getTracer('laravel')
->spanBuilder('http.request')
->setAttribute('http.method', $request->method())
->setAttribute('http.url', $request->fullUrl())
->startSpan();
try {
return $next($request);
} finally {
$span->end();
}
}
// In your job class:
protected function handle(): void {
$span = \OpenTelemetry\API\GlobalTracer::getTracer('laravel-queue')
->spanBuilder('process-job')
->setAttribute('job.name', static::class)
->startSpan();
try {
// Job logic
} finally {
$span->end();
}
}
use OpenTelemetry\API\Common\Instrumentation\ResourceInfo;
$resource = ResourceInfo::create([
'service.name' => 'my-laravel-app',
'service.version' => '1.0.0',
'deployment.environment' => app()->environment(),
]);
\OpenTelemetry\API\GlobalResource::setResource($resource);
Use standardized attribute names (e.g., http.method, db.system, db.name) for compatibility with observability tools.
API vs. SDK Confusion
open-telemetry/sdk) to actually export data.composer require open-telemetry/api open-telemetry/sdk
Context Leaks
Span::end() and Context::restore() to prevent memory leaks.$globalSpan = $tracer->spanBuilder('bad')->startSpan(); // Leaks!
$span = $tracer->spanBuilder('good')->startSpan();
try { /* ... */ } finally { $span->end(); }
Deprecated Interfaces
InstrumentationInterface and ConfigurationResolver are deprecated (since v1.9.0). Use AutoInstrumentation instead.Attribute Validation
['key' => ['a', 'b']] is invalid). Use arrays of scalars:
$span->setAttribute('tags', ['tag1', 'tag2']); // ✅
$span->setAttribute('invalid', ['mixed' => ['a', 1]]); // ❌
Propagator Mismatches
TextMapPropagator) are consistent across services. Mixing B3 and W3C formats can break tracing.Enable Logging
Add to .env:
OTEL_LOG_LEVEL=debug
Logs will show SDK/API interactions (e.g., span creation, export failures).
Validate Spans
Use the Span::isRecording() check to verify spans are active:
if (!$span->isRecording()) {
throw new \RuntimeException('Span is not recording!');
}
Check Context Inspect the current context to debug propagation:
$context = \OpenTelemetry\Context\Context::getCurrent();
$span = \OpenTelemetry\Context\Span::getCurrentSpan($context);
dump($span ? $span->getName() : 'No active span');
OTLP Exporter Issues If traces/metrics don’t appear in your backend:
OTEL_EXPORTER_OTLP_ENDPOINT).curl:
curl -X POST http://otel-collector:4317/v1/traces -H "Content-Type: application/x-protobuf"
use OpenTelemetry\SDK\Trace\SpanProcessor\SpanProcessorInterface;
class MySpanProcessor implements SpanProcessorInterface {
public function onStart(\OpenTelemetry\SDK\Trace\SpanData $span):
How can I help you explore Laravel packages today?