Laravel Vapor: How to track which routes are timing out

November 11, 2023

If you’re using Laravel Vapor, you might encounter the “Task timed out after x seconds” error, indicating that the Lambda function running your code exceeded the configured timeout.

The log message is not very helpful, as it doesn’t tell you which route is timing out. This is because the Lambda function itself is not aware of the ins and outs of Laravel and how it handles requests.

To track this, we can make use of the PHP pcntl functions and a simple Laravel middleware. Let’s get our hands dirty and see how this works.

First, we need to create a new HTTP middleware. Let’s call it TrackLambdaTimeoutsMiddleware.

php artisan make:middleware TrackLambdaTimeoutsMiddleware

Do not forget to register the middleware in your app/Http/Kernel.php file.

protected $middleware = [
        VaporTimeoutLogger::class,
        // ...
];
class TrackLambdaTimeoutsMiddleware
{
    public function handle(Request $request, Closure $next)
    {
        if (! isset($_ENV['AWS_REQUEST_ID'])) {
            return $next($request);
        }

        pcntl_signal(SIGALRM, function () use ($request) {
            logger()->warning('[Timeout] Vapor function timed out', [
                'request' => $request->all(),
                'url' => $request->url(),
                'aws_request_id' => $_ENV['AWS_REQUEST_ID'] ?? null,
            ]);
        });

        pcntl_async_signals(true);
        pcntl_alarm(29); // Make sure to update this to your function's timeout, minus 1 second

        return $next($request);
    }
}

This middleware will start this “timer” in the background, and if the function is still running after 29 seconds, it will log a warning message with the request data so you can know what route is failing. You should add more contextual information here as it makes sense to your application.

Important: Make sure to update the pcntl_alarm value to match your function’s timeout, minus 1 second. We need to make sure the function triggers and logs the message before the Lambda function times out and the whole process gets killed. This value should be taken from your vapor.yml configuration file, under the timeout key.

If you don’t specify any value, Vapor’s default timeout is 10 seconds.

environments:
    staging:
        timeout: 30