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

goksagun/elastic-apm-bundle

A PHP bundle that adds Elastic APM instrumentation to your application, capturing transactions, spans, errors, and performance metrics for request tracing and bottleneck diagnosis. Simple configuration and lightweight integration.

View on GitHub
Deep Wiki
Context7

Getting Started

The bundle is a thin Symfony integration around an Elastic APM PHP agent, so getting value out of it is really two jobs: get an APM Server + agent reachable, then let the bundle turn Symfony's request/console lifecycle into transactions.

  1. Install and register

    composer require goksagun/elastic-apm-bundle
    
    // config/bundles.php
    return [
        // ...
        Goksagun\ElasticApmBundle\ElasticApmBundle::class => ['all' => true],
    ];
    
  2. Point it at your APM Server (config/packages/elastic_apm.yaml). Keep secrets in env vars so they survive config caching:

    elastic_apm:
        enabled: '%env(bool:ELASTIC_APM_ENABLED)%'
        service_name: 'my-app'
        server_url: '%env(ELASTIC_APM_SERVER_URL)%'
        secret_token: '%env(ELASTIC_APM_SECRET_TOKEN)%'
        environment: '%kernel.environment%'
    
  3. Where to look first, in this order:

    • The README + the bundle's DependencyInjection/Configuration.php — that is the authoritative list of options for this version. With 1 star and 0 dependents, treat the README as the contract and the code as the fallback.
    • The DependencyInjection/ extension class: it tells you which services are registered and therefore what you can inject/decorate.
    • Whether the bundle expects the native elastic_apm PHP extension (elastic/apm-agent-php) or the HTTP intake-API library. This single fact changes everything about how you instrument (extension = mostly automatic; library = you call transaction APIs yourself). Confirm it before writing any code.
  4. First use case (15 minutes): instrument one controller, open Kibana → APM → Services → my-app, and confirm a transaction appears with the route as its name. If you see the service but no spans, the transaction is working and the deeper instrumentation isn't — that's a useful signal to separate the two concerns early.

Implementation Patterns

Let the lifecycle do the work, then name transactions deliberately. The most common pattern is: bundle/kernel subscriber opens a transaction per request, you control only the name. Route-based names (GET /orders/{id}) keep cardinality low; raw URIs or user IDs explode your transaction breakdown and make the UI useless. Most bundles expose a namer or a transaction object you can adjust — prefer GET /orders/{id} over GET /orders/12345.

Instrument the boundaries, not the internals. The high-value spans are: Doctrine queries, outbound HTTP calls, cache/Redis calls, and queue publishes. If the bundle ships Doctrine/HttpClient subscribers, enable them; otherwise wrap the boundary in your own adapter:

$span = $this->apm->beginSpan('external', 'GET payment-gateway');
try {
    $response = $this->httpClient->request(...);
} finally {
    $span?->end();
}

Keep spans around I/O, never around pure computation — you'll pay overhead for noise.

Capture exceptions where you already handle them. Don't hook every throwable; hook the place where you decide an error is "real". A kernel.exception listener or a try/catch in the top-level handler is the right seam:

try {
    $this->handler->handle($command);
} catch (\Throwable $e) {
    $this->apm->captureThrowable($e); // then rethrow
    throw $e;
}

If the extension is installed, uncaught exceptions are captured automatically — adding manual capture creates duplicates. Check which mode you're in before adding this.

Long-running processes need explicit transaction lifecycles. For Messenger workers and console consumers, a "boot transaction" model is wrong: one transaction per message, ended per message, and a flush at sensible intervals (or on WorkerStoppedEvent). This is the single most common place developers get value and the place they most often leak memory.

Console commands: separate transactions. If the bundle doesn't already trace commands, wrap execute() in a transaction named after the command (console app:sync-orders) so cron-driven work is visible next to web traffic. Without this, your slowest batch jobs are invisible in APM.

Distributed tracing across services. If service A calls B, propagate the trace header (traceparent) on the outbound HTTP client. Symfony's HttpClient supports decorating with a header-setting middleware; do it once in an event subscriber rather than per-call.

Environment and sampling discipline. Disable in test, lower transaction_sample_rate in staging, full sampling only in dev. In practice:

when@test:
    elastic_apm:
        enabled: false

This prevents test noise and keeps CI from failing on outbound network calls to the APM server.

Gotchas and Tips

  • Maturity risk is the headline gotcha. 1 star, 0 dependents, low opportunity score. Pin an exact version, read the source of the parts you rely on, and be prepared to fork or to swap to elastic/apm-agent-php directly. Don't build a critical observability path on an unmaintained integration without a fallback.
  • Extension vs. library is not interchangeable. If the bundle assumes elastic_apm_* functions from the native extension, your Docker image needs elastic_apm.so installed and ELASTIC_APM_ENABLED set before PHP starts. Calling function_exists('elastic_apm_begin_transaction') in a health check is a fast way to find out. Missing extension usually fails silently, not loudly.
  • Transactions that never end leak. In workers and daemons, an un-end()ed transaction accumulates spans until the process dies. Wrap message handling in try/finally with end() in the finally, and cap the worker's lifetime (--time-limit, --memory-limit).
  • Sampling makes "missing" data normal. At less than 100% sample rate, absent traces on a single request are expected. Don't debug observability by hitting one endpoint once — generate enough traffic, then look.
  • Labels are a cardinality/PII trap. Don't dump SQL parameters, auth tokens, or raw user IDs into labels. Prefer route templates, status codes, and enum-ish values. This bites in production reviews, not in dev.
  • Config caching and env resolution. Symfony caches the compiled container; changing ELASTIC_APM_SERVER_URL at the container level requires bin/console cache:clear. In Docker, set env vars before the app boots, not via a runtime override.
  • Overhead is real and uneven. The extension is comparatively cheap; userland span-per-function instrumentation is not. Benchmark with enabled: true for a day before enabling on your hottest endpoint.
  • Debugging connection problems: raise agent log verbosity (ELASTIC_APM_LOG_LEVEL=debug) and check the agent's log file / stderr first, then curl the APM Server's intake endpoint from the app container. The failure mode is usually network/secret token, not the bundle.
  • Extension points to reach for: decorate/override the transaction namer, add your own EventSubscriber for kernel.request/kernel.terminate, and if you need custom spans, wrap them in small services rather than sprinkling agent calls through domain code. That keeps the vendor dependency in one or two files — which is how you survive swapping this bundle out later.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource