mgdsoft/stackdriver-bundle
A Symfony bundle for integrating Google Stackdriver services into your PHP application, helping route logs and errors to Google Cloud observability tooling with minimal configuration.
This bundle is a thin Symfony wrapper around Google Cloud (formerly Stackdriver) Logging and Error Reporting. Before installing anything, check the repo state: it is archived, has zero stars, and zero dependents, so treat it as a reference implementation rather than a dependency you build a product on.
If you still want to evaluate it in a scratch project:
composer require mgdsoft/stackdriver-bundle
Register the bundle in config/bundles.php (Symfony 4+ flexible structure) or app/AppKernel.php (Symfony 3):
// config/bundles.php
return [
// ...
MgdSoft\StackdriverBundle\MgdSoftStackdriverBundle::class => ['all' => true],
];
Provide credentials via the standard Google mechanism rather than in config — set GOOGLE_APPLICATION_CREDENTIALS to the path of a service-account JSON file, and give the service account roles/logging.logWriter and roles/errorreporting.writer.
Where to look first, in this order:
DependencyInjection/Configuration.php and DependencyInjection/*Extension.php — this is the authoritative list of options the bundle actually supports. Everything in the README should be cross-checked here.Resources/config/services.yml (or .xml) — reveals the service IDs and how the Monolog handler and error reporter are wired.google/cloud-logging vs. the older google/cloud monolog handler) because that determines your composer.json constraints.composer.json of the bundle — the Symfony and Monolog version bounds are your first compatibility gate.First use case — route one Monolog channel to Stackdriver and confirm logs appear in the Logging console under the correct resource.type (usually global or gce_instance):
# config/packages/monolog.yaml
monolog:
handlers:
stackdriver:
type: service
id: mgd_soft_stackdriver.monolog_handler
level: info
Then bin/console cache:clear and trigger a log line. If nothing shows up, the problem is almost always credentials, project ID, or the batch handler never flushing on a short-lived CLI process.
Channel-per-concern logging. Don't route all Monolog output to Stackdriver. Send app and security channels there and keep doctrine, event, and request local (or drop them). Google bills per GiB ingested, and verbose SQL logging is the classic way to blow up a logging budget in a week.
monolog:
channels: ['app', 'security']
handlers:
stackdriver:
type: service
id: mgd_soft_stackdriver.monolog_handler
channels: ['app', 'security']
level: warning
Structured context, not string interpolation. Stackdriver's whole value is jsonPayload querying. Log arrays, not concatenated strings:
$this->logger->warning('Payment declined', [
'orderId' => $order->getId(),
'gateway' => $gatewayName,
'reason' => $response->getDeclineReason(),
]);
These become queryable fields in Logging (jsonPayload.orderId="..."). If the bundle's formatter flattens context into a string, that's a red flag — check the formatter class before committing.
Error Reporting as a separate path from logging. Exception handling and log ingestion are distinct APIs with distinct IAM roles. A common working pattern is a kernel.exception listener that calls the error-reporting client for 5xx errors, plus normal log handlers for everything else. Do not double-report — if you send the same exception through both, you will see it twice in the console and pay twice.
// pseudo-wiring; adapt to whatever service ID the bundle exposes
public function onKernelException(ExceptionEvent $event): void
{
$e = $event->getThrowable();
if ($e instanceof HttpExceptionInterface && $e->getStatusCode() < 500) {
return; // 4xx is not an error worth paging on
}
$this->errorReporter->report($e);
}
Local/dev guard. Stackdriver calls are network calls. In dev and test, bypass the bundle entirely:
# config/packages/dev/monolog.yaml
monolog:
handlers:
stackdriver:
type: fingers_crossed
action_level: error
handler: null_handler
or simply don't register the bundle's handlers in non-prod environments. Nothing is more annoying than a dev environment that hangs on every log line because it's trying to reach logging.googleapis.com through a corporate proxy.
Correlate with request/trace IDs. Stackdriver will link logs to traces if you put logging.googleapis.com/trace in the payload. Add a Monolog processor that injects your request ID or GCP trace header (X-Cloud-Trace-Context) into every record's extra. This is the single highest-value integration — it turns disconnected log lines into a per-request timeline.
Batching on long-running workers. For Symfony Messenger consumers or queue workers, the Monolog handler needs an explicit flush on WorkerStoppedEvent. Batch handlers that never flush lose the tail of every run.
The archive status is the headline. No security patches, no Monolog 2/3 or Symfony 5/6/7 compatibility fixes, and no fixes for GCP API changes. Pin it only in a codebase you control end-to-end and expect to fork. Realistically the right move is to skip it and wire the official google/cloud-logging Monolog handler directly:
use Google\Cloud\Logging\LoggingClient;
$logging = new LoggingClient(['projectId' => $projectId]);
$logger = new Logger('app'); // Monolog\Logger
$logger->pushHandler(new \Google\Cloud\Logging\PsrLogger($logging->psrLogger('app')));
That's roughly what the bundle does, minus the bundle. Fifteen lines of service config replaces an unmaintained dependency.
Version bounds will bite first. The bundle's composer.json almost certainly constrains symfony/framework-bundle to ^3.x || ^4.x and monolog/monolog to ^1.x. On Symfony 6+ or Monolog 3, Composer will either refuse to install or you'll --ignore-platform-reqs and hit a fatal HandlerInterface::handle() signature mismatch at runtime. Check before you spend time debugging config.
Credentials failures are silent-ish. A missing GOOGLE_APPLICATION_CREDENTIALS produces a Google\Cloud\Core\Exception\ServiceException, but if the bundle swallows handler exceptions (Monolog does by default for logging failures), your logs just vanish. During setup, temporarily set the handler's bubble and error handling to fail loudly, and verify with gcloud auth application-default print-access-token against the same service account.
Project ID auto-detection is unreliable outside GCP. On GCE/GKE/GKE it resolves automatically; on a laptop or on AWS/Azure it does not. Always set projectId explicitly in config. Same for resource.type — the bundle may hardcode global, which loses you the instance metadata filtering you actually want on GCE.
Batch handler flush on shutdown. If the bundle uses BatchLogHandler, short-lived PHP processes (CLI commands, web requests) may never flush. Monolog flushes registered handlers on __destruct, but fatal errors and exit() skip destructors. For anything critical, add a register_shutdown_function flush or use the non-batch handler at the cost of one HTTP request per log record.
Severity mapping is off by one in some versions. Monolog's WARNING vs. Stackdriver's WARNING/ERROR mapping has been a recurring bug in Stackdriver handlers. Verify by logging one line per level and checking the console's severity column — don't assume.
Extension points, if you fork: the two places you'll want to modify are the formatter (to control jsonPayload shape) and the error-reporter call site (to filter 4xx and to attach user/tenant context via setUser() / setServiceContext()). Both are small classes; fork-and-vendor is a couple hours of work versus fighting an archived package for a week.
Migration path summary: for logging, replace with google/cloud-logging's PSR-3 adapter under a Monolog handler. For error reporting, use google/cloud-error-reporting directly in a kernel.exception listener. Both are first-party, maintained, and require no bundle — which, for something this small, is the correct shape anyway.
How can I help you explore Laravel packages today?