open-telemetry/sdk-configuration
Configuration layer for the OpenTelemetry PHP SDK, resolving settings from environment variables, php.ini, and declarative config files so instrumentation and exporters can be configured consistently without code changes.
open-telemetry/sdk-configuration is the configuration layer of the OpenTelemetry PHP SDK: an immutable SdkConfiguration value object plus the env-var / php.ini resolution that fills it, following the OpenTelemetry SDK configuration spec. It contains no exporters, no HTTP client, no auto-instrumentation. Its entire job is to answer "what did the operator ask for?" and hand back constructed (lazily) providers.
Two things to internalise before touching it:
open-telemetry/opentelemetry-php. That's why it has ~0 stars. Never file issues or PRs here — they go to the monorepo. Don't composer require it directly either; it arrives transitively via open-telemetry/sdk (or any exporter/instrumentation package).Where to look first, in order:
SdkConfiguration — its property list is the supported env-var matrix. Skim it once and you know every knob (OTEL_SERVICE_NAME, OTEL_TRACES_SAMPLER, OTEL_TRACES_EXPORTER, OTEL_EXPORTER_OTLP_*, OTEL_PROPAGATORS, OTEL_SDK_DISABLED, ...).SdkConfigurationFactory::create() — the normal entry point.Environment/EnvReader and Environment/Resolver — precedence and parsing rules (this is where most surprise lives).Minimal first use case in a Laravel app: you're adding open-telemetry/sdk + open-telemetry/exporter-otlp and want .env-driven tracing.
composer require open-telemetry/sdk open-telemetry/exporter-otlp
use OpenTelemetry\SDK\Configuration\SdkConfigurationFactory;
$config = SdkConfigurationFactory::create(); // reads OTEL_* from the environment
$tracer = $config->getTracerProvider()->getTracer('laravel');
$span = $tracer->spanBuilder('handle-request')->startSpan();
// ... work ...
$span->end();
If that produces a working span, the package is doing its job and you can stop reading. Everything below is about the 20% of cases where it isn't.
1. Env-var-first (let ops own it). The zero-code path: put OTEL_* variables in .env, docker-compose.yml, or your k8s deployment and never write PHP. This is the intended usage and the cheapest to maintain.
OTEL_SERVICE_NAME=checkout-api
OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_TRACES_SAMPLER=parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG=0.05
OTEL_PROPAGATORS=tracecontext,baggage
2. Laravel config/ as source of truth, translated into SdkConfiguration. Use this when you need config:cache, per-environment files, package config merging, or secret indirection. Resolve once in a service provider, build the object, and register it so every downstream SDK call (instrumentations, exporters, middleware) sees your values instead of raw env.
// config/opentelemetry.php
return [
'service_name' => env('OTEL_SERVICE_NAME', env('APP_NAME', 'laravel')),
'endpoint' => env('OTEL_EXPORTER_OTLP_ENDPOINT'),
'protocol' => env('OTEL_EXPORTER_OTLP_PROTOCOL', 'http/protobuf'),
'headers' => env('OTEL_EXPORTER_OTLP_HEADERS', ''),
'sampler' => env('OTEL_TRACES_SAMPLER', 'parentbased_always_on'),
'disabled' => (bool) env('OTEL_SDK_DISABLED', false),
];
// AppServiceProvider::register()
public function register(): void
{
$c = config('opentelemetry');
$config = new \OpenTelemetry\SDK\Configuration\SdkConfiguration(
serviceName: $c['service_name'],
// ... rest of the named args, derived from $c
);
\OpenTelemetry\SDK\Configuration\ConfigurationRegistry::set($config);
}
The payoff: one place (config/opentelemetry.php) that is cacheable, greppable, and testable, instead of env vars leaking in from a dozen deployment layers.
3. Explicit construction in tests. Build a SdkConfiguration with named arguments (or a small helper/factory in tests/Support) rather than mutating $_ENV. It's a plain value object — that's the most underrated feature of the package. Pair it with ConfigurationRegistry::reset() in tearDown.
4. Resource attributes as the first-class citizen. service.name defaults to unknown_service:php, which makes traces unfindable. Always set OTEL_SERVICE_NAME / OTEL_RESOURCE_ATTRIBUTES (deployment.environment, service.version, team) — or pass a ResourceInfo and let it merge. Do this at boot, not per request.
5. Octane / queue workers. Resolve config once per worker at boot; the provider is process-scoped and safe to share. Your per-request responsibility is only span->end() and context propagation across jobs via OTEL_PROPAGATORS.
6. You're already using it. If you run keepsuit/laravel-opentelemetry, spatie/laravel-open-telemetry, or the OTel PHP extension, this package is under them. Reading its property list is the fastest way to understand their config files, and gives you the vocabulary to debug collector connection failures.
sdk-configuration and open-telemetry/sdk share the Context/ConfigProperties interfaces. Mismatched majors produce confusing interface errors. Let Composer resolve; never pin this one manually.config:cache and env() — the trap that doesn't apply. env() returns null outside config files once cached, but this package does not use env(); it reads $_SERVER / $_ENV (with a getenv() fallback). So OTEL_* in .env keeps working even with cached config — which contradicts every Laravel reflex and confuses people both ways. Verify with:
php -r 'var_dump($_ENV["OTEL_SERVICE_NAME"] ?? $_SERVER["OTEL_SERVICE_NAME"] ?? null);'
OTEL_TRACES_EXPORTER=garbage, malformed header lists) throw InvalidArgumentException when SdkConfiguration is constructed — i.e. inside a service provider's register(), producing a stack trace that points at framework bootstrap rather than your typo. Validate in config/opentelemetry.php or wrap the bootstrap in try/catch + log.Logging API, not thrown. Set OTEL_LOG_LEVEL=debug and wire a PSR-3 logger, or you'll chase 401s from the collector with zero local evidence.OTEL_EXPORTER_OTLP_HEADERS parsing. Comma-separated key=value, values are URL-decoded, and a comma inside a value must be percent-encoded. Same shape for OTEL_RESOURCE_ATTRIBUTES. Getting this subtly wrong drops the header and looks like a collector outage.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT overrides OTEL_EXPORTER_OTLP_ENDPOINT. Setting the wrong one is a classic "why is nothing exported" bug. Likewise 4317 (gRPC) vs 4318 (HTTP) — default ports differ by protocol, and http/json is not fully supported.OTEL_TRACES_SAMPLER requires a worker restart; it is not re-read per request. Default is parentbased_always_on — fine for staging, expensive in production.OTEL_PHP_* is not yours. OTEL_PHP_AUTOLOAD_ENABLED, OTEL_PHP_EXCLUDED_URLS belong to the native extension / auto-instrumentation. SdkConfiguration ignores them — don't expect this package to honour them, and don't assume their presence means tracing is on.SdkConfiguration::__construct has ~40 named parameters and gains/renames them across minors. Prefer SdkConfigurationFactory or the registry, and add one test asserting your hand-built config still round-trips after upgrades.ConfigurationRegistry::set() / get() / reset() is convenient but dangerous: mutating it per-request under Octane leaks config between requests, and forgetting reset() leaks between tests. Treat it as write-once-at-boot.ResourceInfo and set OTEL_RESOURCE_ATTRIBUTES, they merge and later values win with no warning. Decide on one source of truth per attribute.OTEL_LOG_LEVEL=debug → dump the resolved SdkConfiguration → confirm getTracerProvider() is not a no-op (OTEL_SDK_DISABLED=true silently gives you one — useful as an emergency kill switch, terrible when set by accident) → confirm sampler → only then suspect the network.How can I help you explore Laravel packages today?