nipwaayoni/elastic-apm-php-agent
Laravel-friendly Elastic APM PHP agent for instrumenting apps and sending performance data, errors, and transactions to an Elastic APM Server. Helps monitor response times, slow queries, and exceptions with simple setup and configurable reporting.
Installation Add the package via Composer:
composer require nipwaayoni/elastic-apm-php-agent
Require the autoloader in your Laravel app (handled automatically if using Composer).
Configuration Publish the config file:
php artisan vendor:publish --provider="Nipwaayoni\ElasticApm\ElasticApmServiceProvider" --tag="config"
Update .env with your Elastic APM server URL and service name:
ELASTIC_APM_SERVER_URL=http://your-apm-server:8200
ELASTIC_APM_SERVICE_NAME=your-laravel-app
ELASTIC_APM_SECRET_TOKEN=your-secret-token # Optional but recommended
First Use Case
Enable the agent in config/elastic-apm.php:
'enabled' => env('APP_ENV') !== 'local',
Test by triggering an error or logging a transaction:
// In a route/controller
\Nipwaayoni\ElasticApm\ElasticApm::transaction('test_transaction', function() {
// Your code here
throw new \Exception("Test error for APM");
});
Transaction Tracking Wrap critical paths (routes, jobs, commands) in transactions:
// In routes/web.php
Route::get('/critical-path', function() {
ElasticApm::transaction('user_dashboard_load', function() {
$user = User::with('orders')->find(1);
return view('dashboard', compact('user'));
});
});
Middleware Integration Auto-capture HTTP requests via middleware:
namespace App\Http\Middleware;
use Nipwaayoni\ElasticApm\ElasticApm;
use Closure;
class APMMiddleware
{
public function handle($request, Closure $next)
{
ElasticApm::transaction('http_request_' . $request->path(), function() use ($request, $next) {
return $next($request);
});
}
}
Register in app/Http/Kernel.php:
protected $middleware = [
// ...
\App\Http\Middleware\APMMiddleware::class,
];
Error Monitoring Capture exceptions globally:
// In App\Exceptions\Handler
public function report(Throwable $exception)
{
ElasticApm::captureException($exception);
parent::report($exception);
}
Custom Spans Instrument database queries or external calls:
ElasticApm::span('database_query', function() {
DB::table('users')->where('id', 1)->get();
});
Queue Job Tracking Wrap jobs in transactions:
namespace App\Jobs;
use Nipwaayoni\ElasticApm\ElasticApm;
use Illuminate\Bus\Queueable;
class ProcessOrder implements Queueable
{
public function handle()
{
ElasticApm::transaction('process_order_job', function() {
// Job logic
});
}
}
barryvdh/laravel-debugbar for local debugging without APM noise.local env to avoid overhead:
'enabled' => app()->environment(['staging', 'production']),
ElasticApm::setContext(['user_id' => auth()->id(), 'request_id' => $request->header('X-Request-ID')]);
php artisan queue:work --queue=high --sleep=3 --tries=3 --daemon
Performance Overhead
local env and avoid over-instrumenting. Use ignore_urls in config to exclude health checks or static assets:
'ignore_urls' => [
'health-check',
'favicon.ico',
],
Transaction Naming Collisions
http_request_home) can obscure performance issues.ElasticApm::transaction('user_profile_' . $user->id, ...);
Queue Job Visibility
finally blocks or Laravel’s handle() with try-catch:
public function handle()
{
try {
ElasticApm::transaction('long_job', fn() => $this->process());
} catch (\Throwable $e) {
ElasticApm::captureException($e);
throw $e;
}
}
Database Query Spans
ElasticApm::span('fetch_user_orders', function() {
return Order::where('user_id', 1)->get();
});
Configuration Caching
config/elastic-apm.php may not reflect without clearing config cache.php artisan config:clear
Or restart your PHP process (e.g., php-fpm or queue:restart).Log Level Enable debug logging to troubleshoot:
'log_level' => 'debug',
Check logs at storage/logs/laravel.log.
Agent Status Verify the agent is active:
if (ElasticApm::isEnabled()) {
// Agent is running
}
Network Issues
ELASTIC_APM_SERVER_URL is correct. Test connectivity:
curl -v http://your-apm-server:8200
Span Hierarchy
ElasticApm::transaction('parent', function() {
ElasticApm::span('child', function() {
// This span will be nested under 'parent'
});
});
Custom Instrumentation Extend the agent by creating a facade wrapper:
namespace App\Services;
use Nipwaayoni\ElasticApm\ElasticApm as BaseApm;
class APMService
{
public function trackApiCall(string $endpoint, callable $callback)
{
BaseApm::transaction("api_{$endpoint}", $callback);
}
}
Event Listeners Hook into Laravel events to auto-instrument:
// In EventServiceProvider
protected $listen = [
'Illuminate\Http\Kernel::terminate' => [
\App\Listeners\InstrumentHttpRequest::class,
],
];
Queue Failed Jobs Capture failed jobs with context:
// In FailedJob class
public function failed(Connection $connection, $exception)
{
ElasticApm::captureException($exception, [
'job' => $this->job,
'queue' => $this->queue,
]);
}
Custom Metrics Report custom metrics (e.g., cache hits/misses):
ElasticApm::setCustomMetric('cache_hit_ratio', 0.95);
How can I help you explore Laravel packages today?