Laravel applications, when instrumented with the Elastic APM PHP agent, gain deep visibility into their performance and behavior.

Here’s a typical transaction flowing through a Laravel app with APM enabled:

// In a Laravel controller
public function show(Post $post)
{
    // APM agent automatically captures this as a 'Controller' span
    $relatedPosts = Post::where('category_id', $post->category_id)
                        ->where('id', '!=', $post->id)
                        ->take(5)
                        ->get();

    // APM agent automatically captures this as a 'Database' span
    $comments = $post->comments()->orderBy('created_at', 'desc')->paginate(10);

    // APM agent automatically captures this as a 'View' span (if rendering a view)
    return view('posts.show', compact('post', 'relatedPosts', 'comments'));
}

When this controller action is requested, the Elastic APM agent, running within the PHP process, starts an APM transaction. It then automatically instruments common operations:

  • HTTP Requests: Captures incoming request details, route, and response status.
  • Database Queries: Records every SQL query executed, including the query itself and its execution time.
  • Cache Operations: Logs cache hits, misses, and writes.
  • External HTTP Calls: Tracks outgoing requests made via libraries like Guzzle.
  • Queue Jobs: Instruments jobs processed by Laravel’s queue worker.
  • View Rendering: If you’re rendering Blade templates, it can capture the time spent in the view layer.

The agent sends this data asynchronously to the Elastic APM Server, which then processes and stores it in Elasticsearch. Kibana provides dashboards to visualize this data, allowing you to see transaction traces, identify slow database queries, pinpoint errors, and understand external service dependencies.

The core problem APM solves is the "black box" nature of distributed systems and complex applications. Before APM, debugging performance issues often involved educated guesses, log diving, and manual timing. APM provides a unified, distributed trace of every request, showing exactly where time is spent and what errors occurred, across your entire application stack.

To get started, you’ll need to install the Elastic APM PHP agent. This is typically done via Composer:

composer require elastic/apm-agent-php

Then, you need to configure the agent. This is usually done in your php.ini or via an environment file. The critical settings are elastic_apm_service_name and elastic_apm_server_url.

; In php.ini or a conf.d file
extension=elastic_apm.so
elastic_apm_service_name=my-laravel-app
elastic_apm_server_url=http://localhost:8200
elastic_apm_environment=production

You also need to initialize the agent within your Laravel application. The App\Http\Kernel.php or bootstrap/app.php are common places for this, ensuring it’s loaded early in the request lifecycle.

// In App\Http\Kernel.php or bootstrap/app.php
// Ensure this is called *before* any other application logic runs.
// Typically, you'd use a Service Provider for cleaner integration.

// Example using a dedicated Service Provider (recommended)
// Create app/Providers/ApmServiceProvider.php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Elastic\Apm\ElasticApm;

class ApmServiceProvider extends ServiceProvider
{
    public function register()
    {
        // Check if the extension is loaded and configuration is present
        if (extension_loaded('elastic_apm') && get_cfg_var('elastic_apm_server_url')) {
            try {
                ElasticApm::start();
            } catch (\Exception $e) {
                // Log the error if APM fails to start, but don't crash the app
                error_log("Elastic APM failed to start: " . $e->getMessage());
            }
        }
    }
}

// Register this provider in config/app.php
// 'providers' => [
//     // ... other providers
//     App\Providers\ApmServiceProvider::class,
// ],

The elastic_apm_capture_span function is your manual control. While the agent auto-instruments many things, you’ll use this for custom logic or background tasks.

// In a custom service or job
use Elastic\Apm\ElasticApm;

public function processData()
{
    // Manually capture a specific piece of work
    ElasticApm::captureSpan('Custom Data Processing', 'processing', function () {
        // Simulate some intensive work
        sleep(2);
        // ... actual processing logic ...
    });
}

The elastic_apm_capture_exception function is crucial for logging unhandled exceptions and specific caught exceptions that you want to track in APM.

// In a try-catch block
use Elastic\Apm\ElasticApm;

try {
    // ... risky operation ...
} catch (\Throwable $e) {
    // Capture the exception in APM
    ElasticApm::captureException($e);
    // Optionally re-throw or handle the exception further
    throw $e;
}

The elastic_apm_set_user_context function allows you to associate user information with transactions, which is invaluable for debugging issues specific to certain users.

// In your authentication logic or middleware
use Elastic\Apm\ElasticApm;

$user = Auth::user();
if ($user) {
    ElasticApm::setUserInfo([
        'id' => $user->getAuthIdentifier(),
        'username' => $user->name,
        'email' => $user->email,
    ]);
}

When configuring elastic_apm_server_url, you can use multiple URLs separated by commas. The agent will then attempt to send data to each server in sequence, providing basic failover.

elastic_apm_server_url=http://apm-server-1:8200,http://apm-server-2:8200

The agent automatically infers the framework if it’s running within a known environment like Laravel, which allows it to provide framework-specific instrumentation (like route matching). If you’re not in a recognized framework, you might need to manually start the transaction with ElasticApm::beginCurrentTransaction().

The elastic_apm_capture_transaction function is rarely needed in a web context with Laravel because the agent hooks into the framework’s request lifecycle to start transactions automatically. However, for CLI commands or background workers where automatic transaction initiation isn’t present, you’d use this.

// For a custom CLI command
use Elastic\Apm\ElasticApm;

// Define the transaction name and type
ElasticApm::beginCurrentTransaction('MyCLICommand', 'cli');

try {
    // ... your command logic ...
    ElasticApm::getCurrentTransaction()->end(0); // End with success (0ms)
} catch (\Throwable $e) {
    ElasticApm::captureException($e);
    ElasticApm::getCurrentTransaction()->end(1); // End with error
    throw $e;
}

The most surprising thing about the Elastic APM PHP agent is how little manual intervention is often required for web applications. By simply enabling the extension and configuring the service name and server URL, the agent intelligently hooks into the Laravel framework, automatically capturing a wealth of performance data without you needing to write explicit instrumentation code for most common scenarios.

The elastic_apm_transaction_sample_rate setting is crucial for managing the volume of data sent to the APM server, especially in high-traffic environments. A value of 1.0 means every transaction is sent, while 0.1 means only 10% of transactions are sampled. This is a trade-off between visibility and performance/cost.

elastic_apm_transaction_sample_rate=0.5 ; Sample 50% of all transactions

You’ll know you’re done when transactions start appearing in Kibana’s APM UI, and you can see detailed traces for your Laravel requests.

The next error you’ll hit is likely related to misconfigured elastic_apm_server_url, causing transactions to fail to send and potentially leaving your APM data incomplete.

Want structured learning?

Take the full Elastic-apm course →