open-telemetry/api
OpenTelemetry PHP API package: vendor-neutral interfaces for tracing, metrics, and context propagation used by instrumented libraries and apps. Pair with an SDK/exporters to emit telemetry. Docs: opentelemetry.io/docs/instrumentation/php
Install the package:
composer require open-telemetry/api
Initialize OpenTelemetry (requires a compatible SDK like open-telemetry/sdk):
use OpenTelemetry\API\Common\Instrumentation\Configuration;
use OpenTelemetry\API\Common\Instrumentation\ConfigurationProvider;
use OpenTelemetry\API\Common\Instrumentation\HookManager;
// Initialize configuration (e.g., via env or config file)
$config = Configuration::fromEnv();
ConfigurationProvider::setGlobalConfiguration($config);
// Register hooks (e.g., for auto-instrumentation)
HookManager::registerHooks();
First Use Case: Tracing a Request
use OpenTelemetry\API\Trace\TracerInterface;
use OpenTelemetry\API\Trace\SpanKind;
$tracer = \OpenTelemetry\API\GlobalTracer::getTracer('my-app');
$span = $tracer->spanBuilder('process-request')
->setSpanKind(SpanKind::SERVER)
->startSpan();
try {
// Business logic here
$span->addEvent('request-processed');
} finally {
$span->end();
}
GlobalTracer::getTracer(), SpanBuilderGlobalMeter::getMeter(), Counter, HistogramContext, ContextStorageLogger, LogRecordBuilderManual Spans:
$tracer = \OpenTelemetry\API\GlobalTracer::getTracer('my-service');
$span = $tracer->spanBuilder('user-auth')
->setAttribute('user.id', $userId)
->startSpan();
try {
// Critical section
$span->addEvent('auth-success');
} catch (\Exception $e) {
$span->recordException($e);
$span->setStatus(\OpenTelemetry\API\Trace\Status::error($e->getMessage()));
} finally {
$span->end();
}
Context Propagation (e.g., HTTP requests):
use OpenTelemetry\API\Common\Context\Context;
use OpenTelemetry\API\Common\Context\ContextStorage;
// Inject context into HTTP headers (e.g., for downstream services)
$context = Context::getCurrent();
$propagator = \OpenTelemetry\API\Common\Propagation\Propagator::getTextMapPropagator();
$headers = $propagator->extract($context, new ArrayAccessHeaderCarrier($httpRequest->headers));
// Later, extract context from incoming request
$incomingContext = $propagator->inject(Context::getCurrent(), new ArrayAccessHeaderCarrier($httpResponse->headers));
ContextStorage::store($incomingContext);
Auto-Instrumentation Hooks:
HookManager::registerHooks([
'http.client.request' => function ($request, $options) {
$tracer = \OpenTelemetry\API\GlobalTracer::getTracer('http-client');
$span = $tracer->spanBuilder('http.request')
->setAttribute('http.url', $request->getUri())
->startSpan();
return fn() => $span->end();
}
]);
Counters:
$meter = \OpenTelemetry\API\GlobalMeter::getMeter('my-service');
$counter = $meter->getInt64Counter('api.requests');
$counter->add(1, ['method' => 'GET', 'endpoint' => '/users']);
Histograms (for latency):
$histogram = $meter->getHistogram('api.latency');
$histogram->record(150, ['endpoint' => '/users'], 1);
Observers (custom metrics):
$meter->bindObservableGauge(
'cache.hits',
fn() => Cache::hits(),
['cache' => 'redis']
);
Structured Logging:
$logger = \OpenTelemetry\API\Logger::getLogger('my-service');
$logger->logRecordBuilder()
->setSeverityText('info')
->setBody(['user_id' => $userId, 'action' => 'login'])
->emit();
Correlated Logs:
$context = Context::getCurrent();
$logger->logRecordBuilder()
->setContext($context)
->setSeverityText('error')
->setBody(['error' => $e->getMessage()])
->emit();
Laravel Integration:
Use spatie/laravel-ignition or spatie/laravel-telemetry for seamless integration with Laravel’s service container and middleware.
Example middleware:
public function handle($request, Closure $next) {
$tracer = \OpenTelemetry\API\GlobalTracer::getTracer('laravel');
$span = $tracer->spanBuilder('http.request')
->setAttribute('http.method', $request->method())
->startSpan();
try {
return $next($request);
} finally {
$span->end();
}
}
Queue Workers: Wrap queue jobs in spans:
$tracer = \OpenTelemetry\API\GlobalTracer::getTracer('queue');
$span = $tracer->spanBuilder('process-job')
->setAttribute('job.name', $job->job)
->startSpan();
try {
$job->handle();
} finally {
$span->end();
}
Database Queries:
Use doctrine/dbal or illuminate/database listeners to trace queries:
DB::listen(function ($query) {
$tracer = \OpenTelemetry\API\GlobalTracer::getTracer('database');
$span = $tracer->spanBuilder('sql.query')
->setAttribute('db.statement', $query->sql)
->startSpan();
// ... end span after query executes
});
Context Leaks:
ContextStorage::store($context) and ContextStorage::restore():
$context = Context::getCurrent();
ContextStorage::store($context);
// Async work...
ContextStorage::restore($context);
Span Lifecycle:
finally blocks) can cause missing data.Span::end() in finally blocks and avoid early termination.Attribute Limits:
Span::setStatus() for critical metadata.Deprecated APIs:
InstrumentationInterface and ConfigurationResolver are deprecated (v1.9.0+).ConfigurationProvider and HookManager instead.Metric Naming:
snake_case and validate names:
$meter->getInt64Counter('valid_metric_name');
Enable Debug Logging:
Set the OTEL_LOG_LEVEL environment variable:
export OTEL_LOG_LEVEL=debug
Logs will appear in stderr or your configured logger.
Validate Spans:
Use the Span::isRecording() method to check if a span is active:
if ($span->isRecording()) {
$span->addEvent('debug-event');
}
Inspect Context: Dump the current context to verify propagation:
\OpenTelemetry\API\Common\Context\Context::getCurrent()->debugDump();
Check for Auto-Instrumentation Conflicts: If spans are missing, ensure hooks are registered before the target code runs:
HookManager::registerHooks(); // Early in bootstrap
PropagatorInterface for non-standard context formats (e.g., gRPC metadata):
class CustomPropagator
How can I help you explore Laravel packages today?