Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Elastic Apm Bundle Laravel Package

spacespell/elastic-apm-bundle

Symfony bundle for integrating Elastic APM into PHP apps, with request tracing, transactions, spans, error capture, and performance monitoring. Sends APM data to Elasticsearch/Kibana with minimal configuration for better observability.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: spacespell/elastic-apm-bundle is a Symfony bundle, not a Laravel package. There is no service provider, no config file in config/ you can publish, and no facade. If you live in Laravel, the honest first step is deciding whether you're:

  1. Instrumenting a Symfony service that coexists with a Laravel app — then this bundle is usable as-is on that side.
  2. Trying to APM a Laravel app — in which case you should reach for elastic/apm-agent-php (the official, framework-agnostic PHP agent) plus a small middleware, and treat this bundle as a reference for wiring conventions rather than a runtime dependency.

Assuming you're on the Symfony side or prototyping the patterns:

  • Install: composer require spacespell/elastic-apm-bundle
  • Register the bundle in config/bundles.php (or AppKernel) — there is no auto-discovery-era guarantee here given the 2020 release date; verify the class name from the README rather than trusting Flex recipes.
  • Minimum viable config — point it at a running APM Server:
    # config/packages/elastic_apm.yaml
    elastic_apm:
        enabled: true
        app_name: "%env(APP_NAME)%"
        server_url: "%env(APM_SERVER_URL)%"   # e.g. http://apm:8200
        environment: "%kernel.environment%"
        transactions:
            sample_rate: 0.1
    
  • Run a local stack: docker run -p 8200:8200 -p 5601:5601 docker.elastic.co/apm/apm-server:7.x alongside Kibana, or use the Elastic APM integration in a 8.x stack.
  • First use case: hit a single route, confirm one transaction document lands in the apm-* index in Kibana's APM UI with a sane name (e.g. GET /orders/{id}), then confirm a Doctrine query and a Twig render appear as child spans under it.

Implementation Patterns

Rely on the built-in subscribers first. The bundle's value is that it hooks the kernel and common libraries via event subscribers/listeners, so you get HTTP transactions, console command transactions, Doctrine DBAL query spans, Twig render spans, and Guzzle calls without writing code. Verify each by clicking into a transaction in Kibana — if the Doctrine tab is empty, the DBAL listener isn't matching your DBAL version (see Gotchas).

Name transactions deliberately. Default naming tends to either use the route/route name or fall back to the raw path. Raw paths with IDs (/orders/48213) explode your transaction cardinality and make the UI useless. Add a small kernel listener with high priority that renames the current transaction to CONTROLLER::action or the route name. For a Laravel port, this is the equivalent of a TerminateMiddleware that calls \Apm\ElasticApm::getCurrentTransaction()->setName(...).

Wrap business-critical work in custom spans. Inject the tracer/client service and wrap anything you want to see broken out — external API calls the bundle doesn't auto-instrument, heavy report generation, third-party SDK calls:

$span = $this->tracer->beginSpan('report.generate', 'custom');
try {
    $report = $this->generate();
} finally {
    $span->end(); // always end, even on throw
}

The pattern you'd copy into Laravel is roughly a startSpan/endSpan helper plus a middleware that opens a transaction per request and closes it in terminate().

Add context and labels, not just names. Attach user ID, tenant ID, and feature flags as labels on the transaction. Keep label values low-cardinality — tenant_id is fine if you have tens of tenants, not thousands. High-cardinality values belong as free-form context, not labels.

Sample per environment. sample_rate: 1.0 locally, 0.1 or lower in prod. If you're doing queue workers or high-QPS endpoints, sample below the global rate — you want traces, not bills. Sample rates are per-transaction in this style of bundle, so workers can be dialed down independently.

Instrument async work explicitly. HTTP transactions get closed automatically at kernel terminate; queue/messenger handlers do not. Wrap handlers in their own transaction (or span if you propagate a parent trace ID) and make sure the transaction ends before the worker goes back to sleep(). This is the single biggest source of "we have no APM data from our queue" tickets.

Propagate traceparent downstream. If the bundle's Guzzle middleware is active, it injects the traceparent/tracestate headers for you. If you use a non-Guzzle HTTP client (Symfony HttpClient, cURL), add the header yourself from the current transaction — otherwise every downstream service starts a fresh trace and the service map is broken.

For a Laravel app, mirror the subscriber model: a ServiceProvider that binds a singleton tracer, a global middleware starting/ending transactions, an Event::listen(QueryExecuted::class) for query spans, and JobProcessing/JobProcessed for queue spans. The bundle's structure is a decent blueprint; its code isn't portable.

Gotchas and Tips

  • Unmaintained since Jan 2020. There has been no release in 5+ years, and it targets pre-8 Symfony and older DBAL. Expect friction on Symfony 6/7 and PHP 8.2+. Pin it, run the test suite, and budget for a fork or replacement rather than assuming it "just works." The official elastic/apm-agent-php is the strategic answer for any new work.
  • enabled: false still costs you. Compile-time wiring and service instantiation often happen regardless. Disable the bundle entirely in config/bundles.php for local/test when you don't need it, rather than relying on the config flag to make it free.
  • sample_rate: 0 silently ships nothing. It's a valid value and produces no error — you'll stare at an empty Kibana for an hour. Check this first when "APM isn't working" in a new environment.
  • Body and header capture are expensive and a PII risk. capture_body and capture_headers can serialize request payloads, cookies, and auth headers into APM docs. Turn both off in production unless you have a redaction story; they also cost CPU on every request.
  • Backtraces cost real CPU. Every span can carry a stack trace. Cap the stack trace limit (or disable for DB spans, which are the noisiest), or your p95 latency will be visibly worse after instrumenting.
  • Orphaned transactions/spans poison the trace view. Any custom span you begin must be ended in a finally block. Any custom transaction you open (CLI, worker) must be end()ed before shutdown; if the process is SIGKILLed the transaction is lost entirely — no partial data.
  • DBAL version mismatch is the classic failure. The Doctrine listener hooks a specific DBAL API. On DBAL 3+ queries often appear as un-instrumented; verify by counting query spans for a known N+1 endpoint. If zero, the listener is dead weight — you'll need a custom SQLLogger/middleware.
  • Flush on long-running processes. The APM client buffers spans and flushes on shutdown. In workers and daemons, "shutdown" may be hours away, so call the flush method periodically (e.g. every N jobs) or you'll lose everything on a deploy restart.
  • Debugging checklist, in order: curl $APM_SERVER_URL (is the server up?), check APM Server logs for intake rejections (usually a version or secret-token mismatch), then verify the agent's own log level is bumped (logging.level: debug or equivalent) to see what it's actually sending.
  • Config key triage. Three auth-ish keys get confused: server_url (where to ship), secret_token (older APM Server 6.x/7.x auth), and api_key (APM Server 7.6+ / Elastic Cloud). Use one, not both; a 401 at intake is almost always this.
  • Extension points worth knowing: the transaction-naming kernel listener (highest leverage), a custom event subscriber for your domain events, and the Guzzle middleware if you want to customize header injection. These are the seams to copy into a Laravel provider if you port.
  • Don't chase the star count. 125 stars and zero dependents means nearly no community debugging. Treat the README as documentation of an older Symfony era and test against your versions before committing.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums