open-telemetry/contrib-aws
AWS integrations for OpenTelemetry PHP: provides an AWS X-Ray propagator and trace ID generator so PHP services can join X-Ray traces and correlate telemetry across AWS services. Install via Composer to add AWS-aware context propagation to your instrumentation.
open-telemetry/contrib-aws is a thin auto-instrumentation layer for the AWS SDK for PHP. It wraps AWS client calls so each API operation (S3.PutObject, SQS.SendMessage, DynamoDB.GetItem, etc.) produces a span with AWS semantic attributes such as service name, operation, region, request ID, HTTP status, and error information. It does not ship a Laravel service provider, config file, or facade — you own the wiring.
Minimal install
composer require open-telemetry/contrib-aws open-telemetry/sdk open-telemetry/exporter-otlp
You also need the OpenTelemetry PHP API/SDK bootstrapped. Most Laravel apps do this in a dedicated service provider or a small bootstrap file. Configure the tracer provider, resource (service.name, deployment.environment), sampler, and OTLP exporter once, then reuse it.
What to read first
src/ — confirm the exact instrumentation class and whether registration is instrument($client), a static register(...), or an SDK middleware added to the handler list.tests/ — the fastest way to see a working client registration with the exact constructor signatures for the tagged version.Middleware/HandlerList docs — the instrumentation ultimately hooks there, and knowing that makes edge cases understandable.First use case
Trace an S3 upload from a queued job. Build the tracer provider, instantiate the instrumentation with it, apply the instrumentation to the S3Client, and dispatch the job. You should see a parent job/consumer span, an S3.PutObject span beneath it, and (if Guzzle is also instrumented) an HTTP client span beneath that. That three-level shape is the quickest way to confirm both the package and your context propagation are working.
A typical registration shape — verify the exact signature against your installed version:
use OpenTelemetry\Contrib\Aws\AwsSdkInstrumentation;
$instrumentation = new AwsSdkInstrumentation($tracerProvider);
$instrumentation->instrument($s3Client);
$s3Client->putObject([...]);
If the class or method name differs in your tag, adapt the call rather than assuming this snippet is stable. This package has effectively no downstream usage, so the source is the documentation.
Centralize and decorate client creation. Instrumentation applied ad hoc in controllers gets missed. Bind instrumented clients in the container and inject them:
$this->app->singleton(S3Client::class, function ($app) {
$client = new S3Client(config('filesystems.disks.s3'));
$app->make(AwsSdkInstrumentation::class)->instrument($client);
return $client;
});
Then use that binding everywhere, including repositories and jobs. Keep raw new S3Client(...) out of application code.
Laravel’s S3 filesystem disk needs an explicit hook. Storage::disk('s3') constructs its own Aws\S3\S3Client inside FilesystemManager, so container bindings alone won’t instrument it. Extend the driver or register a custom disk:
Storage::extend('s3', function ($app, $config) {
$client = $app->make(InstrumentedS3ClientFactory::class)->make($config);
$adapter = new \League\Flysystem\AwsS3V3\AwsS3V3Adapter(
$client, $config['bucket'], $config['root'] ?? '', null, null, $config['options'] ?? []
);
return new \Illuminate\Filesystem\FilesystemAdapter(
new \League\Flysystem\Filesystem($adapter), $adapter
);
});
This is the difference between “we trace uploads” and “we trace uploads from the storage layer but not from Storage::put.”
SQS queues: inject the client. Laravel’s SQS connector accepts a client key in the queue config in current versions. Build an instrumented SqsClient and pass it:
'sqs' => [
'driver' => 'sqs',
'key' => env('AWS_ACCESS_KEY_ID'),
// ...
'client' => app(InstrumentedSqsClient::class),
],
If your Laravel version constructs the client unconditionally, subclass SqsConnector and override the client factory method.
Propagate context across SQS. Instrumenting SendMessage gives you a producer span, but the consumer is a separate trace unless you carry the W3C traceparent. Two workable options:
traceparent in the SQS MessageAttributes by subclassing SqsQueue or decorating the producer call, then extract it in a JobProcessing listener and start the consumer span as a child.Queue::createPayloadUsing() to add trace context to the job payload, then extract it in a listener. Less standard than message attributes, but much easier to wire in a stock Laravel app.Pair either approach with a JobProcessing/JobProcessed/JobFailed listener that starts and ends a consumer span, so the trace is continuous across the queue boundary. Without this you get two disconnected traces.
Async clients. If you use invokeAsync or promise-based calls, confirm the package ends the span on resolution and records rejection. If it doesn’t, wrap the promise yourself and end the span in then/otherwise, with a finally guard against never-resolving promises.
Sampling. Use a parent-based sampler in the web/worker bootstrap. A root-only sampler will drop AWS spans in requests that were sampled upstream, or create orphaned traces. Set resource attributes once at bootstrap:
$resource = ResourceInfo::create(Attributes::create([
'service.name' => config('app.name'),
'deployment.environment' => app()->environment(),
]));
Testing. Use an in-memory exporter with a simple span processor in a test-only provider, instrument a client, invoke it, and assert span names and attributes. Disable instrumentation globally in phpunit.xml and enable it only in the tests that assert telemetry, otherwise every test pays the exporter cost.
AWS operation → HTTP request span pairs. That is usually correct, but it doubles span volume and can make latency dashboards confusing. Either accept it or filter HTTP spans whose host matches AWS endpoints.AssumeRole, and credential-refresh calls can generate spans that have nothing to do with your business transaction, especially on cold starts. Filter by operation name or endpoint in the exporter/processor.CreateMultipartUpload, many UploadPart, and CompleteMultipartUpload spans. Budget for it or sample those operations.ReceiveMessage with a 20-second wait time produces 20-second spans. Do not put queue-receive spans on the same latency panels as request spans; separate service or operation filters.OK while the payload says three messages failed. Inspect Failed/Errors and record relevant attributes or events yourself if that matters.JobProcessed if you buffer, watch memory, and queue:restart after deploys so workers pick up new instrumentation config.async-aws clients at all. If you use AsyncAws rather than aws/aws-sdk-php, verify support before designing around it.config/telemetry.php, but remember config:cache and read env only inside the config file.How can I help you explore Laravel packages today?