scoutapp/scout-apm-symfony-bundle
Symfony bundle for integrating Scout APM application performance monitoring into Symfony apps. Track slow requests, database queries, and errors to identify bottlenecks and improve performance with minimal configuration and low overhead.
This is a Symfony bundle, so as a Laravel developer your first move is to check whether you actually need it: the day-to-day API is ScoutApm\Agent from the underlying scoutapp/scout-apm-php package, and the Laravel equivalent (scoutapp/scout-apm-laravel) wraps the same agent. If you're evaluating this repo because you're on Laravel, stop and pull the Laravel package instead — the bundle is thin glue that registers middleware, a Doctrine subscriber, and config into Symfony's container, none of which exist in your app. The value you'll get from reading it is understanding what the agent does and how instrumentation is wired.
If you're genuinely on Symfony (or a Symfony-kernel app), the minimal path is:
composer require scoutapp/scout-apm-symfony-bundle:^1.2 and register it in config/bundles.php.SCOUT_KEY, SCOUT_NAME, SCOUT_MONITOR=true in your environment.config/packages/scout_apm.yaml echoing those env vars plus a runtime-derived name like <app>-<env>.SCOUT_LOG_LOCATION (default scout_apm.log / var/log) until you see the handshake. The dashboard lags by a minute or two.First substantive use case: instrument a non-HTTP path — a queued job or console command — with Agent::instrument('Job', ClassName::class, fn () => ...) and an explicit stop(). That teaches you the transaction lifecycle, which is the mental model for context, naming, and ignore rules.
Read DependencyInjection/Configuration.php first. It's the canonical list of every knob (key, name, monitor flag, auto-instrument toggles, log location, revision SHA) and the fastest way to understand what the agent does. Everything else in the repo is plumbing around this config tree.
Trust auto-instrumentation, hand-instrument the gaps. The bundle wires Doctrine queries, Twig rendering, and HTTP client calls when auto-instrumentation is on. Treat that as baseline and add manual instrumentation only for non-standard surfaces: custom console commands, queue consumers, and third-party SDK calls. Over-instrumenting noisy traces raises p50 overhead.
Wrap non-HTTP work deterministically. Background workers don't get a web transaction and either vanish or get folded into a parent. Wrap the unit of work with Agent::instrument() inside try/finally so exceptions still close the transaction. This is the highest-value pattern in production and the one most teams skip.
Context over spans. addContext() is a key/value payload, not a new span — use it liberally for user id, tenant, job id, feature flags. It's what makes a slow trace diagnosable in the Scout UI. Reserve distinct transaction names for genuinely distinct operations, not per-variant.
Ignore-list discipline. Health checks, readiness probes, asset routes, metrics scrapers, and high-frequency polling inflate trace volume and skew averages. Wire ignore rules next to route definitions so they get reviewed when routes change.
Environment-as-config. Keep SCOUT_KEY/SCOUT_NAME per environment, derive app name from runtime (<app>-<env>), and inject SCOUT_REVISION_SHA from CI. Cross-service traces are only as useful as the metadata discipline behind them — if you run Laravel and Symfony services side by side, standardize these conventions.
The core agent is a downloaded native binary, not a Composer package. In containers this is the #1 silent failure: read-only filesystems, no runtime network, or an ephemeral download path all mean the agent never attaches. Bake it into the image at build time and point the path at a writable location. If traces are missing and nothing throws, check this before anything else.
Silent no-ops are the default. A missing key, SCOUT_MONITOR=false, or an unreachable agent all degrade gracefully — the app keeps running and you get zero telemetry. Verify via the agent log (SCOUT_LOG_LOCATION), never by the absence of errors in the dashboard. Add a staging smoke test that asserts a transaction was recorded.
Build matrix ≠ Composer constraint. 1.2.0 adds PHP 8.3 to CI, but that's a signal the suite is green on 8.3, not proof the package's composer.json php constraint permits it. Check the constraint before bumping your own platform.php, and apply the same caution to any transitive Symfony-version bump.
Long-running processes leak transaction state. Under Octane, FrankenPHP, or long-lived workers, an unstopped transaction can bleed into the next request or job, producing traces attributed to the wrong unit of work. Make stop() non-optional (finally), and be wary of per-request state cached in singletons.
Exceptions don't auto-close what you opened. If instrument()'s callback throws and you don't stop the transaction, you get "missing" traces rather than errors. That's the most common homegrown-instrumentation bug.
Overhead scales with span count, not request count. Instrumenting a hot loop or an ORM accessor called thousands of times per request shows up in your p50s. Prefer service decorators and middleware — wrap at boundaries, not at leaves.
Upgrade cadence is cheap here. Both PRs in 1.2.0 are tooling only (PHP 8.3 in the matrix, release automation). No functional, config, or API changes and no deprecations — a drop-in minor bump. Pin to ^1.2 and let minors flow through staging first, mostly to catch agent-binary compatibility drift rather than PHP API breakage. The release-automation PR is a useful signal that future releases should land more predictably.
How can I help you explore Laravel packages today?