softspring/google-cloud-trace-bundle
A Symfony bundle that integrates Google Cloud Trace, adding distributed tracing to your PHP applications. It captures request spans and exports timing data to Google Cloud, helping you monitor latency, diagnose bottlenecks, and follow requests across services.
composer require it. softspring/google-cloud-trace-bundle is a Symfony bundle. It registers through config/bundles.php, wires itself with DI compiler passes, and decorates Symfony's HttpKernel and HttpClient. Laravel has no equivalent extension point. Composer will resolve and install it, php artisan package:discover will find nothing, and you'll have added a dependency (and its google/cloud-trace transitive client) that does nothing on its own. Treat it as a reference implementation, not a runtime dependency.DependencyInjection/Configuration.php — the authoritative list of every knob (project id, sampler, enable flags, HTTP-client instrumentation). The README is a Flex quickstart; the config tree is the spec.src/ for the request listener (root-span lifecycle) and the HTTP-client decorator (outbound header injection). Those two files are what you're actually porting.Tests/ fixtures — they contain concrete header strings and span-attribute assertions, which is faster than reading the Cloud Trace docs to reverse-engineer the format.google/cloud-trace directly, and copy this bundle's conventions for (a) parsing the inbound X-Cloud-Trace-Context header (trace id, span id, sampling bit), (b) injecting that header into downstream calls, and (c) the span-attribute naming the GCP Trace UI expects. That's roughly 200 lines of work and it's the entire value of the package.TraceServiceProvider — binds Google\Cloud\Trace\Tracer as a singleton, only when GOOGLE_CLOUD_PROJECT is set and APP_ENV is not local/testing. Register a no-op tracer otherwise.X-Cloud-Trace-Context, opens a root span named for the route, tags it, closes it in finally.terminate() callback — flushes the exporter after the response is sent.Guard the service provider hard. Credential resolution against the GCP metadata server adds latency and log noise in CI and local dev. The pattern:
$this->app->singleton(Tracer::class, function ($app) {
if (! config('trace.enabled')) {
return new NullTracer(); // implement the same interface, do nothing
}
return new Tracer([...]);
});
Bind an interface, not the concrete Tracer, so NullTracer is a drop-in when tracing is off. Everything downstream then works unconditionally.
Inbound: one root span per request, closed in terminate(). Open the span at the top of the middleware, stash it on the container, and close + flush in terminate(). Do not close it in a finally block inside handle() — Laravel's terminate() runs after the response is flushed to the client, which is the only place a synchronous exporter flush doesn't sit on the critical path. Tag route name, controller action, HTTP method, and status code (read the status in terminate(), after the response is built — that's where you'll actually see 5xx).
Outbound: the propagation middleware is the highest-value piece. Without it, every service shows up as an isolated trace and Cloud Trace is nearly useless. Register a Guzzle middleware (or Http::globalMiddleware() on Laravel 10+) that reads the current span id from the container and writes it into the outbound X-Cloud-Trace-Context header. This is the one thing worth getting right on day one — cross-service traces are what people actually open the console to look at.
Mirror the bundle's sampler, behind env. Always / never / probability, driven by a config value that defaults to a low probability (e.g. 0.05) in production and 0 in tests. Never inherit a 1.0 default; it's a latency and billing surprise waiting for your first traffic spike.
Queues are your problem, not the bundle's. The bundle does not instrument async work. Add a job middleware that serializes the trace header into the payload and re-establishes the parent span when the job is popped. Without this, every queued job starts a new trace and your async half is invisible.
Console commands and scheduled tasks. Wrap Artisan::starting() (or a base command) to open a span per command invocation. Lower priority than HTTP, but scheduled jobs are exactly where you want latency data and where it's hardest to get from logs.
Reuse the bundle's span-attribute schema verbatim. The /http/url, /http/status_code, /http/method style attributes are what the GCP Trace UI and any GCP-native services in your estate already understand. Renaming them "for Laravel" breaks rendering and cross-service correlation for no benefit.
Octane / long-running workers need explicit cleanup. A root span that isn't reset between requests accumulates in memory and corrupts parent-child relationships across requests. If you run Octane, flush and null the tracer state in a per-request hook, not just in terminate().
config/*.php to publish. Budget for a port, not an install.GOOGLE_APPLICATION_CREDENTIALS locally. When the project id or credentials are wrong you get missing traces, not an exception. Wire the exporter's error handler into your logs on day one, and alert on the absence of spans, not just on errors. Quiet telemetry loss is the worst failure mode because it looks exactly like "no traffic."terminate(); accept that under some SAPIs you'll still lose the tail.X-Cloud-Trace-Context is untrusted input. It arrives from the public internet. Malformed, oversized, or adversarial values must not throw. Validate the format (traceid/spanid;o=1) and fall back to generating a fresh trace id.DependencyInjection/Configuration.php and composer.json constraints between v5.5.10 and v5.6.0 yourself — a config-tree rename is the most likely breaking change in a point release of a bundle like this, and it won't show up until your YAML (or your ported config) silently stops applying.google/cloud-trace shares transitive google/cloud-* clients with Pub/Sub, Storage, and Logging. Letting Composer resolve them independently is how you get a deploy-time conflict in an unrelated integration. Test the full GCP stack in one process.How can I help you explore Laravel packages today?