Oussama GHAIEB

Tips, tricks, and code snippets for developers

Laravel Performance Monitoring: Track Slow Requests & Memory Leaks | Complete Guide

Are slow Laravel requests hurting your user experience? This hands-on guide shows you how to identify and fix performance bottlenecks using custom middleware that tracks execution time and memory usage. Learn how to implement production-ready monitoring with minimal overhead – complete with tested code examples.

Jump to section:

Why Performance Monitoring Matters for Laravel Apps

Performance issues don't just frustrate developers – they directly impact your business metrics:

  • User experience deterioration: 53% of mobile users abandon sites that take more than 3 seconds to load
  • Lower conversion rates: Every 1-second delay reduces conversions by approximately 7%
  • Increased infrastructure costs: Inefficient code requires more server resources, leading to higher hosting bills

Without proper monitoring, performance problems can silently degrade your application, often going undetected until users complain.

The Complete Laravel Performance Middleware Solution

Here's the production-ready middleware that identifies both slow requests and memory spikes in your Laravel application:

public function terminate($request, $response)
{
    // Calculate execution time in milliseconds using Laravel's start constant
    $executionTime = round((microtime(true) - LARAVEL_START) * 1000);
    
    // Get threshold from config with 1000ms default
    $threshold = config('app.execution_time_threshold', 1000);

    if ($executionTime > $threshold) {
        // Gather detailed request information
        $url = $request->path();
        $method = $request->method();
        $route = $request->route();
        $action = $route ? str_replace('App\\Http\\Controllers\\', '', $route->getActionName()) : 'N/A';
        
        // Calculate peak memory usage in MB
        $memory = round(memory_get_peak_usage(true) / 1024 / 1024, 2);
        
        // Log structured performance data for easy parsing
        Log::channel('performance')->warning(
            "Slow Request - {$method} {$url} ({$action}) took {$executionTime}ms "
            . "(threshold: {$threshold}ms, memory: {$memory}MB)"
        );
    }
}

Key Performance Tracking Features

Precise timing measurement using Laravel's built-in LARAVEL_START constant
Memory leak detection with PHP's memory_get_peak_usage() function
Structured logging format for easy parsing and analysis
Environment-specific thresholds via Laravel configuration
Minimal overhead since logging occurs after response is sent

Step-by-Step Laravel Middleware Implementation

1. Create the Performance Middleware

Create a new file at app/Http/Middleware/LogLongExecutionTime.php:

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Log;

class LogLongExecutionTime
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        // No need to set start time; LARAVEL_START is already defined in index.php
        return $next($request);
    }

    /**
     * Perform tasks after the response has been sent to the client.
     * This ensures our monitoring doesn't delay the user experience
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Illuminate\Http\Response  $response
     * @return void
     */
    public function terminate($request, $response)
    {
        $executionTime = round((microtime(true) - LARAVEL_START) * 1000);
        $threshold = config('app.execution_time_threshold', 1000);
    
        if ($executionTime > $threshold) {
            $url = $request->path();
            $method = $request->method();
            $route = $request->route();
            $action = $route ? str_replace('App\\Http\\Controllers\\', '', $route->getActionName()) : 'N/A';
            $memory = round(memory_get_peak_usage(true) / 1024 / 1024, 2); // MB
            
            Log::channel('performance')->warning(
                "Slow Request - {$method} {$url} ({$action}) took {$executionTime}ms "
                . "(threshold: {$threshold}ms, memory: {$memory}MB)"
            );
        }
    }
}

2. Register the Laravel Performance Middleware

Add it to your global middleware stack in app/Http/Kernel.php:

protected $middleware = [
    // Other middleware...
    \App\Http\Middleware\LogLongExecutionTime::class,
];

3. Configure a Dedicated Performance Logging Channel

Set up a dedicated logging channel in config/logging.php:

'channels' => [
    // Other channels...
    'performance' => [
        'driver' => 'daily',
        'path' => storage_path('logs/performance.log'),
        'level' => 'warning',
        'days' => 14,  // Keep two weeks of history
    ],
],

4. Add Performance Threshold Configuration

In your .env file:

APP_EXECUTION_TIME_THRESHOLD=1000

And in config/app.php:

'execution_time_threshold' => env('APP_EXECUTION_TIME_THRESHOLD', 1000),

5. Test with a Slow Endpoint

Create a test route to verify your monitoring setup:

Route::get('/test-performance', function() {
    // Simulate a database operation
    sleep(2); // 2-second delay
    return response()->json(['message' => 'Performance test completed']);
});

Real-World Laravel Performance Issues

Our middleware can help identify common Laravel performance problems:

1. Identifying N+1 Query Problems

Log entry:

[2024-05-03] Slow Request - GET /api/products (ProductController@index) took 3200ms (memory: 128MB)

Root cause: Each product loading related data with separate queries

Solution:

// Before: Inefficient queries
$products = Product::all();  // N+1 problem when accessing $product->category

// After: With eager loading
$products = Product::with('category', 'tags')->get();  // Reduces queries by 90%

Results: Request time reduced from 3200ms to 320ms (90% improvement)

2. Detecting Memory Leaks in Report Generation

Log entry:

[2024-05-03] Slow Request - POST /reports/monthly (ReportController@generate) took 5500ms (memory: 512MB)

Root cause: Loading all records into memory at once

Solution:

// Before: Memory-intensive approach
$allRecords = Transaction::whereMonth('created_at', $month)->get();
// Process everything in memory

// After: Chunked processing
Transaction::whereMonth('created_at', $month)
    ->chunk(1000, function ($transactions) use (&$report) {
        // Process each chunk with controlled memory usage
    });

Results: Memory usage reduced by 75% (from 512MB to 128MB)

3. Identifying Slow API Integrations

Log entry:

[2024-05-03] Slow Request - GET /dashboard (DashboardController@index) took 4300ms (memory: 96MB)

Root cause: Synchronous external API calls

Solution: Implemented request caching and asynchronous loading:

// Before: Synchronous calls
$weatherData = $weatherApi->getCurrentWeather($location);
$stockData = $stockApi->getLatestPrices($symbols);

// After: Cached responses with reasonable TTL
$weatherData = Cache::remember('weather.'.$location, 3600, function () use ($weatherApi, $location) {
    return $weatherApi->getCurrentWeather($location);
});

Results: Dashboard load time improved by 65% (from 4300ms to 1500ms)

Advanced Laravel Performance Monitoring Techniques

Take your monitoring to the next level with these enhancements:

1. Request Tracing Across Microservices

Add correlation IDs to track requests across your entire infrastructure:

public function handle($request, Closure $next)
{
    // Generate or retrieve request ID
    $requestId = $request->header('X-Request-ID') ?? (string) Str::uuid();
    
    // Make it available throughout the application
    app()->instance('request-id', $requestId);
    
    // Add to response headers
    $response = $next($request);
    $response->header('X-Request-ID', $requestId);
    
    return $response;
}

public function terminate($request, $response)
{
    // Use request ID in performance logs
    $requestId = app('request-id');
    
    // Rest of the terminate method with [$requestId] added to log
    Log::channel('performance')->warning("[$requestId] Slow Request...");
}

2. Real-time Alert Integration

Notify your team about critical performance issues immediately:

// In the terminate method
if ($executionTime > 5000) {  // Critical threshold: 5 seconds
    // Send Slack notification for immediate attention
    Notification::route('slack', config('notifications.slack_webhook'))
        ->notify(new SlowRequestDetected($url, $executionTime, $memory));
        
    // Or use Laravel's HTTP client for other notification services
    Http::post('https://api.pagerduty.com/incidents', [
        'incident' => [
            'title' => "Critical performance issue: {$url}",
            'service' => ['id' => config('services.pagerduty.service_id')],
            'urgency' => 'high',
            'body' => "Request took {$executionTime}ms with {$memory}MB memory"
        ]
    ]);
}

3. Database Query Monitoring Integration

Capture slow queries alongside request performance:

public function handle($request, Closure $next)
{
    // Enable query logging for this request if it's in debug mode
    if (config('app.debug')) {
        DB::enableQueryLog();
    }
    
    return $next($request);
}

public function terminate($request, $response)
{
    $executionTime = round((microtime(true) - LARAVEL_START) * 1000);
    $threshold = config('app.execution_time_threshold', 1000);
    
    if ($executionTime > $threshold) {
        // Existing code...
        
        // Add query logging for slow requests
        if (config('app.debug') && isset(DB::getQueryLog()[0])) {
            $queries = DB::getQueryLog();
            $totalQueryTime = array_sum(array_column($queries, 'time'));
            $slowestQuery = max(array_column($queries, 'time'));
            
            Log::channel('performance')->warning(
                "Query stats for slow request {$url}: " .
                "total queries: " . count($queries) . ", " .
                "total query time: {$totalQueryTime}ms, " .
                "slowest query: {$slowestQuery}ms"
            );
        }
    }
}

FAQ: Laravel Performance Monitoring

Q: How does this middleware compare to Laravel Telescope?
A: This middleware provides lightweight, production-ready logging focused specifically on performance metrics. Laravel Telescope offers broader debugging capabilities but with higher overhead, making it better suited for local development. Our middleware can safely run in production with negligible impact.

Q: What's an appropriate threshold value for different environments?
A: We recommend:

  • API endpoints: 300-500ms for critical endpoints, 1000ms for standard endpoints
  • Web routes: 1000-1500ms for complex pages
  • Admin panels: 1500-2000ms for data-heavy operations Adjust based on your specific SLAs and user expectations.

Q: Will this middleware affect my application's performance?
A: The performance impact is minimal (typically <1ms per request) since the logging happens in the terminate method, which runs after the response has been sent to the user. The overhead is negligible compared to the insights gained.

Q: How do I handle different thresholds for different route groups?
A: For route-specific thresholds, implement route middleware:

// In your route definition
Route::middleware(['api.performance:200'])->group(function () {
    Route::get('/api/critical-endpoint', 'ApiController@critical');
});

// Create a parameterized middleware
class ApiPerformanceMonitor
{
    public function handle($request, Closure $next, $threshold = 1000)
    {
        $request->attributes->set('execution_time_threshold', $threshold);
        return $next($request);
    }
    
    public function terminate($request, $response)
    {
        // Use route-specific threshold
        $threshold = $request->attributes->get('execution_time_threshold', 
            config('app.execution_time_threshold', 1000));
            
        // Rest of termination logic using this threshold
    }
}

Next Steps for Laravel Performance Optimization

Once you've implemented performance monitoring, take these actions:

  1. Create a performance dashboard using tools like Grafana, Datadog or ELK stack to visualize:

    • Average response times by endpoint
    • Memory usage trends over time
    • Daily/weekly performance comparisons
  2. Implement automated performance testing in your CI/CD pipeline:

    • Set up load tests with tools like k6 or JMeter
    • Fail builds that don't meet performance thresholds
    • Run regression tests after major changes
  3. Optimize identified bottlenecks with techniques like:

    • Strategic query caching with Redis
    • Database query optimization (indexes, query rewriting)
    • Queue long-running operations using Laravel Jobs
    • Implement horizontal scaling for high-traffic routes
  4. Monitor user-perceived performance by adding frontend metrics:

    // Add to your blade templates
    <script>
      window.addEventListener('load', function() {
        navigator.sendBeacon('/api/performance-metrics', JSON.stringify({
          ttfb: performance.timing.responseStart - performance.timing.requestStart,
          domComplete: performance.timing.domComplete - performance.timing.domLoading,
          route: "posts.show",
        }));
      });
    </script>
    

Monitoring Implementation Benchmark Results

Scenario Before Optimization After Optimization Improvement
Product listing page 2.3s response time 450ms response time 80% faster
Report generation 512MB memory 128MB memory 75% less memory
Dashboard rendering 4.3s response time 1.5s response time 65% faster

Conclusion

By implementing this Laravel performance monitoring middleware, you gain valuable insights into your application's bottlenecks without impacting user experience. The minimal overhead makes it suitable for production environments, while the detailed logging helps you prioritize optimization efforts for maximum impact.

This proactive approach to performance monitoring will help you identify issues before users notice them, reduce infrastructure costs, and create a more responsive Laravel application that keeps users engaged and satisfied.

Tags: #laravel #performance
Oussama GHAIEB - Laravel Certified Developer in Paris

Oussama GHAIEB

Laravel Certified Developer | Full-Stack Web Developer in Paris

14+ years experience 20+ projects
Read more about me →

Comments (0)

No comments yet. Be the first to comment!


Leave a Comment

More Posts :