Expert guidance for configuring and deploying the OpenTelemetry Collector. Use when setting up a Collector pipeline, configuring receivers, exporters, or processors, deploying a Collector to Kubernetes or Docker, or forwarding telemetry to Dash0. Triggers on requests involving collector, pipeline, OTLP receiver, exporter, or Dash0 collector setup.
100
100%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Advisory
Suggest reviewing before use
Instrument PHP applications to generate traces, logs, and metrics for deep insights into behavior and performance.
composer require \
open-telemetry/sdk \
open-telemetry/opentelemetry-auto-slim \
open-telemetry/exporter-otlp \
open-telemetry/opentelemetry-auto-psr18The opentelemetry PHP extension requires gcc, make, and autoconf to compile.
Linux (APT):
sudo apt-get install gcc make autoconf
pecl install opentelemetrymacOS:
brew install gcc make autoconf
pecl install opentelemetryAfter installing, add the extension to your php.ini:
extension=opentelemetry.soSet the OTEL_PHP_AUTOLOAD_ENABLED environment variable to true so the SDK auto-loads instrumentation at runtime.
Note: Installing the packages and extension alone is insufficient—you must enable auto-loading AND configure exporters.
All environment variables that control the SDK behavior:
| Variable | Required | Default | Description |
|---|---|---|---|
OTEL_PHP_AUTOLOAD_ENABLED | Yes | false | Enables auto-loading of the OpenTelemetry SDK |
OTEL_SERVICE_NAME | Yes | unknown_service | Identifies your service in telemetry data |
OTEL_TRACES_EXPORTER | Yes | none | Must set to otlp to export traces |
OTEL_METRICS_EXPORTER | No | none | Set to otlp to export metrics |
OTEL_LOGS_EXPORTER | No | none | Set to otlp to export logs |
OTEL_EXPORTER_OTLP_ENDPOINT | Yes | http://localhost:4318 | OTLP collector endpoint |
OTEL_EXPORTER_OTLP_HEADERS | No | - | Headers for authentication (e.g., Authorization=Bearer TOKEN) |
OTEL_EXPORTER_OTLP_PROTOCOL | No | http/protobuf | Protocol: grpc, http/protobuf, or http/json |
OTEL_RESOURCE_ATTRIBUTES | No | - | Additional resource attributes (e.g., deployment.environment=production) |
Critical: Without OTEL_TRACES_EXPORTER=otlp, the SDK defaults to none and no telemetry is exported.
https://<region>.your-platform.comorder-api, checkout-service)The SDK activates through the OTEL_PHP_AUTOLOAD_ENABLED environment variable:
export OTEL_PHP_AUTOLOAD_ENABLED=trueexport OTEL_SERVICE_NAME="my-service"This step is required - without it, no telemetry is sent:
# Required for traces
export OTEL_TRACES_EXPORTER="otlp"
# Optional: also export metrics and logs
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_LOGS_EXPORTER="otlp"export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://<OTLP_ENDPOINT>"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer YOUR_AUTH_TOKEN"export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer YOUR_AUTH_TOKEN,Dash0-Dataset=my-dataset"# Enable auto-loading
export OTEL_PHP_AUTOLOAD_ENABLED=true
# Service identification
export OTEL_SERVICE_NAME="my-service"
# Enable exporters (required!)
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_LOGS_EXPORTER="otlp"
# Configure endpoint
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://<OTLP_ENDPOINT>"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer YOUR_AUTH_TOKEN"
php -S localhost:8000 -t publicMany PHP frameworks (e.g., Laravel, Symfony) support .env files natively.
.env:
OTEL_PHP_AUTOLOAD_ENABLED=true
OTEL_SERVICE_NAME=my-service
OTEL_TRACES_EXPORTER=otlp
OTEL_METRICS_EXPORTER=otlp
OTEL_LOGS_EXPORTER=otlp
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_ENDPOINT=https://<OTLP_ENDPOINT>
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer YOUR_AUTH_TOKENNote: For the built-in PHP development server, you must export environment variables directly or use a wrapper script, as .env files are not loaded automatically outside a framework context.
OTEL_PHP_AUTOLOAD_ENABLED=true \
OTEL_SERVICE_NAME="my-service" \
OTEL_TRACES_EXPORTER="otlp" \
OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf" \
OTEL_EXPORTER_OTLP_ENDPOINT="https://<OTLP_ENDPOINT>" \
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer YOUR_AUTH_TOKEN" \
php -S localhost:8000 -t publicFor development without a collector, use the console exporter to see telemetry in your terminal:
export OTEL_PHP_AUTOLOAD_ENABLED=true
export OTEL_SERVICE_NAME="my-service"
export OTEL_TRACES_EXPORTER="console"
export OTEL_METRICS_EXPORTER="console"
export OTEL_LOGS_EXPORTER="console"
php -S localhost:8000 -t publicThis prints spans, metrics, and logs directly to stdout—useful for verifying instrumentation works before configuring a remote backend.
If you set OTEL_TRACES_EXPORTER=otlp but have no collector running, you will see connection errors.
This is expected behavior.
Options:
console exporter during development (recommended for quick testing)Set service.name, service.version, and deployment.environment.name for every deployment.
See resource attributes for the full list of required and recommended attributes.
See Kubernetes deployment for pod metadata injection, resource attributes, and Dash0 Kubernetes Operator guidance.
The auto-instrumentation packages automatically instrument:
| Category | Libraries |
|---|---|
| HTTP | cURL, Guzzle, PSR-18 |
| Framework | Laravel, Symfony, Slim |
| Database | PDO |
Refer to OpenTelemetry documentation for the complete list.
Add business context to auto-instrumented traces:
use OpenTelemetry\API\Globals;
$tracer = Globals::tracerProvider()->getTracer('my-service');
function processOrder(array $order): mixed
{
global $tracer;
$span = $tracer->spanBuilder('order.process')->startSpan();
$scope = $span->activate();
try {
$span->setAttribute('order.id', $order['id']);
$span->setAttribute('order.total', $order['total']);
$result = saveOrder($order);
return $result;
} catch (\Throwable $e) {
$span->setStatus(\OpenTelemetry\API\Trace\StatusCode::STATUS_ERROR, $e->getMessage());
$ctx = $span->getContext();
$logger->error('order.process.failed', [
'trace_id' => $ctx->getTraceId(),
'span_id' => $ctx->getSpanId(),
'exception.type' => get_class($e),
'exception.message' => $e->getMessage(),
'exception.stacktrace' => $e->getTraceAsString(),
]);
throw $e;
} finally {
$scope->detach();
$span->end();
}
}Auto-instrumentation creates spans you do not control directly (e.g., the SERVER span for an HTTP request).
To enrich these spans with business context or set their status, retrieve the active span from the current context.
See adding attributes to auto-instrumented spans for when to use this pattern.
use OpenTelemetry\API\Trace\Span;
$span = Span::getCurrent();
$span->setAttribute('order.id', $order['id']);
$span->setAttribute('tenant.id', $request->getHeaderLine('X-Tenant-Id'));Span::getCurrent() returns a non-recording span if no span is active.
Calling setAttribute or setStatus on a non-recording span is a no-op, so no null check is needed.
See span status code for the full rules. This section shows how to apply them in PHP.
ERRORThe second argument to setStatus is the status message.
It must contain the error type and a short explanation — enough to understand the failure without opening the full trace.
use OpenTelemetry\API\Trace\StatusCode;
// BAD: no status message
$span->setStatus(StatusCode::STATUS_ERROR);
// BAD: generic message with no diagnostic value
$span->setStatus(StatusCode::STATUS_ERROR, 'something went wrong');
// GOOD: specific message with error type and context
$span->setStatus(StatusCode::STATUS_ERROR, get_class($e) . ': ' . $e->getMessage());Do not include stack traces in the status message.
Record those in a log record with exception.stacktrace instead.
// BAD: stack trace in the status message
$span->setStatus(StatusCode::STATUS_ERROR, $e->getTraceAsString());
// GOOD: short message only
$span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());OK only for confirmed successSet status to OK when application logic has explicitly verified the operation succeeded.
Leave status UNSET if the code simply did not encounter an error.
// GOOD: explicit confirmation from downstream
$response = $client->request('GET', $url);
if ($response->getStatusCode() === 200) {
$span->setStatus(StatusCode::STATUS_OK);
}
// BAD: setting OK speculatively
$span->setStatus(StatusCode::STATUS_OK);
return someFunction(); // might still fail after this pointConfigure your logging framework to serialize exceptions into a single structured field so that stack traces do not break the one-line-per-record contract. See logs for general guidance on structured logging and exception stack traces.
Monolog is the standard logging library for Laravel and Symfony.
Use the JsonFormatter to produce single-line JSON output.
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\JsonFormatter;
$handler = new StreamHandler('php://stdout', Logger::INFO);
$handler->setFormatter(new JsonFormatter());
$logger = new Logger('app');
$logger->pushHandler($handler);
try {
processOrder($order);
} catch (\Throwable $e) {
$logger->error('order.failed', [
'exception' => $e,
'order_id' => $order['id'],
]);
}Monolog's JsonFormatter serializes exceptions (including the stack trace) into a structured context.exception field as a single-line JSON entry.
PHP's traditional execution model is request-scoped: each HTTP request starts a new process (or reuses one from a pool), and the SDK flushes telemetry when the request ends. No explicit shutdown code is needed for standard web requests.
For long-running PHP processes (queue workers, daemons), register a shutdown function to flush providers before exit:
register_shutdown_function(function () {
\OpenTelemetry\API\Globals::tracerProvider()->shutdown();
});shutdown() flushes pending batches and releases resources.
Check exporters are enabled:
echo $OTEL_TRACES_EXPORTER # Should be "otlp" or "console", not emptyThe SDK defaults OTEL_TRACES_EXPORTER to none, which silently discards all telemetry.
Verify auto-loading is enabled:
echo $OTEL_PHP_AUTOLOAD_ENABLED # Should be "true"Verify the extension is installed:
php -m | grep opentelemetry # Should output "opentelemetry"This means the SDK is working but cannot reach the collector:
OTEL_TRACES_EXPORTER=consoleOTEL_EXPORTER_OTLP_ENDPOINT is correcthttp/protobuf on port 4318Symptom: No instrumentation happens despite correct environment variables.
Fix: Ensure the opentelemetry extension is listed in your php.ini:
extension=opentelemetry.soVerify with:
php -m | grep opentelemetryIf the extension does not appear, check that gcc, make, and autoconf were available during the pecl install step, and reinstall if necessary.
Symptom: Errors about missing classes or undefined namespaces.
Fix: Ensure all required packages are installed:
composer require \
open-telemetry/sdk \
open-telemetry/opentelemetry-auto-slim \
open-telemetry/exporter-otlp \
open-telemetry/opentelemetry-auto-psr18