Error Handling

Error handling

Introduction

Cuando inicias un nuevo proyecto Laravel, el manejo de errores y excepciones ya está configurado para ti; sin embargo, en cualquier momento, puedes usar el método withExceptions en bootstrap/app.php de tu aplicación para manejar cómo las excepciones son reportadas y renderizadas por tu aplicación.

El objeto $exceptions proporcionado al closure withExceptions es una instancia de Illuminate\Foundation\Configuration\Exceptions y es responsable de gestionar el manejo de excepciones en su aplicación.

Configuration

La opción debug en su archivo de configuración config/app.php determina cuánta información sobre un error se muestra realmente al usuario. Por defecto, esta opción está configurada para respetar el valor de la variable de entorno APP_DEBUG, que se almacena en su archivo .env.

Durante el desarrollo local, debe establecer la variable de entorno APP_DEBUG en true. En su entorno de producción, este valor debe ser siempre false. Si el valor se establece en true en producción, corres el riesgo de exponer valores de configuración sensibles a los usuarios finales de tu aplicación.

Handling Exceptions

Reporting Exceptions

En Laravel, el reporte de excepciones se utiliza para registrar excepciones o enviarlas a un servicio externo Sentry o Flare. Por defecto, las excepciones se registrarán en función de tu configuración de logging. No obstante, puede registrar(log) las excepciones como desee.

Si necesitas reportar diferentes tipos de excepciones de diferentes maneras, puedes usar el método report exception en bootstrap/app.php de tu aplicación para registrar un closure que debe ser ejecutado cuando se necesite reportar una excepción de un tipo dado. Laravel determinará qué tipo de excepción reporta el closure examinando el type-hint del closure:

->withExceptions(function (Exceptions $exceptions) {
    $exceptions->report(function (InvalidOrderException $e) {
        // ...
    });
})

Cuando registras un callback personalizado de reporte de excepciones utilizando el método report, Laravel seguirá registrando la excepción utilizando la configuración de logging por defecto para la aplicación. Si deseas detener la propagación de la excepción al logging stack por defecto, puedes utilizar el método stop al definir tu callback o devolver false desde el callback:

->withExceptions(function (Exceptions $exceptions) {
    $exceptions->report(function (InvalidOrderException $e) {
        // ...
    })->stop();
 
    $exceptions->report(function (InvalidOrderException $e) {
        return false;
    });
})

ℹ️ Para personalizar la notificación de una excepción determinada, también puede utilizar reportable exceptions.

Global Log Context

Si está disponible, Laravel añade automáticamente el ID del usuario actual al mensaje de registro de cada excepción como datos contextuales. Puedes definir tus propios datos contextuales globales usando el método context exception en el fichero bootstrap/app.php de tu aplicación. Esta información se incluirá en cada mensaje de registro de excepción escrito por tu aplicación:

->withExceptions(function (Exceptions $exceptions) {
    $exceptions->context(fn () => [
        'foo' => 'bar',
    ]);
})

Exception Log Context

Aunque añadir contexto a cada mensaje de registro (log message) puede ser útil, a veces una excepción en particular puede tener un contexto único que te gustaría incluir en tus logs. Al definir un método de context en una de las excepciones de su aplicación, puede especificar cualquier dato relevante para esa excepción que deba añadirse a la entrada de registro de la excepción:

<?php
 
namespace App\Exceptions;
 
use Exception;
 
class InvalidOrderException extends Exception
{
    // ...
 
    /**
     * Get the exception's context information.
     *
     * @return array<string, mixed>
     */
    public function context(): array
    {
        return ['order_id' => $this->orderId];
    }
}

The report Helper

A veces puede ser necesario informar de una excepción pero continuar gestionando la petición actual. La función helper reportpermite notificar rápidamente una excepción sin mostrar una página de error al usuario:

public function isValid(string $value): bool
{
    try {
        // Validate the value...
    } catch (Throwable $e) {
        report($e);
 
        return false;
    }
}

Deduplicating Reported Exceptions

Si utiliza la función report en toda su aplicación, es posible que en ocasiones notifique la misma excepción varias veces, creando entradas duplicadas en sus registros.

Si desea asegurarse de que una única instancia de una excepción sólo se notifique una vez, puede invocar el método de excepción dontReportDuplicates en el archivo bootstrap/app.php de su aplicación:

->withExceptions(function (Exceptions $exceptions) {
    $exceptions->dontReportDuplicates();
})

Ahora, cuando se llame al helper report con la misma instancia de una excepción, sólo se informará de la primera llamada:

$original = new RuntimeException('Whoops!');
 
report($original); // reported
 
try {
    throw $original;
} catch (Throwable $caught) {
    report($caught); // ignored
}
 
report($original); // ignored
report($caught); // ignored

Exception Log Levels

Cuando se escriben mensajes en los logs su aplicación , los mensajes se escriben en un log level, que indica la gravedad o la importancia del mensaje que se está registrando.

Como se ha indicado anteriormente, incluso cuando se registra un callback personalizadp para el reporte de excepciones utilizando el método report, Laravel seguirá registrando la excepción utilizando la configuración por defecto de logging; sin embargo, dado que el nivel de registro a veces puede influir en los canales en los que se registra un mensaje, es posible que desees configurar el log level en el que se registran ciertas excepciones.

Para ello, puede utilizar el método de excepción de level en el archivo bootstrap/app.php Este método recibe el tipo de excepción como primer argumento y el log level como segundo argumento:

use PDOException;
use Psr\Log\LogLevel;
 
->withExceptions(function (Exceptions $exceptions) {
    $exceptions->level(PDOException::class, LogLevel::CRITICAL);
})

Ignoring Exceptions by Type

Cuando construyas tu aplicación, habrá algunos tipos de excepciones que nunca querrás reportar. Para ignorar estas excepciones, puede utilizar el método dontReport exception en el archivo bootstrap/app.php de su aplicación. Cualquier clase proporcionada a este método nunca será reportada; sin embargo, aún pueden tener lógica de renderizado personalizada:

use App\Exceptions\InvalidOrderException;
 
->withExceptions(function (Exceptions $exceptions) {
    $exceptions->dontReport([
        InvalidOrderException::class,
    ]);
})

Alternativamente, puede simplemente “marcar” una clase de excepción con la interfaz Illuminate\Contracts\Debug\ShouldntReport. Cuando una excepción está marcada con esta interfaz, nunca será reportada por el manejador de excepciones de Laravel:

<?php
 
namespace App\Exceptions;
 
use Exception;
use Illuminate\Contracts\Debug\ShouldntReport;
 
class PodcastProcessingException extends Exception implements ShouldntReport
{
    //
}

Internamente, Laravel ya ignora algunos tipos de errores por ti, como las excepciones resultantes de errores HTTP 404 o respuestas HTTP 419 generadas por tokens CSRF inválidos. Si quieres indicar a Laravel que deje de ignorar un determinado tipo de excepción, puedes utilizar el método stopIgnoring exception en el fichero bootstrap/app.php de tu aplicación:

use Symfony\Component\HttpKernel\Exception\HttpException;
 
->withExceptions(function (Exceptions $exceptions) {
    $exceptions->stopIgnoring(HttpException::class);
})

Rendering Exceptions

Por defecto, el manejador de excepciones de Laravel convertirá las excepciones en una respuesta HTTP por ti. Sin embargo, puedes registrar un closure custom de renderizado para las excepciones de un tipo determinado. Para ello, puede utilizar el método render exception en el archivo bootstrap/app.php de su aplicación.

El closure pasado al método render debe devolver una instancia de Illuminate\Http\Response, que puede ser generada mediante el helper response. Laravel determinará qué tipo de excepción devuelve el closure examinando el type-hint del closure:

use App\Exceptions\InvalidOrderException;
use Illuminate\Http\Request;
 
->withExceptions(function (Exceptions $exceptions) {
    $exceptions->render(function (InvalidOrderException $e, Request $request) {
        return response()->view('errors.invalid-order', status: 500);
    });
})

También puedes usar el método render para sobreescribir el comportamiento de renderizado para excepciones incorporadas en Laravel o Symfony como NotFoundHttpException. Si el closure dado al método render no devuelve un valor, se utilizará el renderizado de excepciones por defecto de Laravel:

use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
->withExceptions(function (Exceptions $exceptions) {
    $exceptions->render(function (NotFoundHttpException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json([
                'message' => 'Record not found.'
            ], 404);
        }
    });
})

Rendering Exceptions as JSON

Al renderizar una excepción, Laravel determinará automáticamente si la excepción debe ser renderizada como una respuesta HTML o JSON basándose en el header Accept de la petición. Si quieres personalizar como Laravel determina si renderizar las excepción como HTML o JSON, puedes utilizar el método shouldRenderJsonWhen:

use Illuminate\Http\Request;
use Throwable;
 
->withExceptions(function (Exceptions $exceptions) {
    $exceptions->shouldRenderJsonWhen(function (Request $request, Throwable $e) {
        if ($request->is('admin/*')) {
            return true;
        }
 
        return $request->expectsJson();
    });
})

Customizing the Exception Response

En raras ocasiones, puede que necesites personalizar toda la respuesta HTTP renderizada por el manejador de excepciones de Laravel (Laravel exception handler). Para ello, puedes registrar una respuesta customizada usando closure en el método respond

use Symfony\Component\HttpFoundation\Response;
 
->withExceptions(function (Exceptions $exceptions) {
    $exceptions->respond(function (Response $response) {
        if ($response->getStatusCode() === 419) {
            return back()->with([
                'message' => 'The page expired, please try again.',
            ]);
        }
 
        return $response;
    });
})

Reportable and Renderable Exceptions

En lugar de definir custom reports y comportamientos de rendering en el archivo bootstrap/app.php de tu aplicación, puedes definir métodos report y renderdirectamente en las excepciones de tu aplicación. Cuando estos métodos existan, serán llamados automáticamente por el framework:

<?php
 
namespace App\Exceptions;
 
use Exception;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
 
class InvalidOrderException extends Exception
{
    /**
     * Report the exception.
     */
    public function report(): void
    {
        // ...
    }
 
    /**
     * Render the exception into an HTTP response.
     */
    public function render(Request $request): Response
    {
        return response(/* ... */);
    }
}

Si tu excepción extiende una excepción que ya es renderable, como una excepción integrada de Laravel o Symfony, puedes devolver false desde el método render de la excepción para renderizar la respuesta HTTP por defecto de la excepción:

/**
 * Render the exception into an HTTP response.
 */
public function render(Request $request): Response|bool
{
    if (/** Determine if the exception needs custom rendering */) {
 
        return response(/* ... */);
    }
 
    return false;
}

Si su excepción contiene lógica de reporte personalizada que sólo es necesaria cuando se cumplen ciertas condiciones, puede que necesite instruir a Laravel para que a veces reporte la excepción utilizando la configuración de manejo de excepciones por defecto. Para ello, puede devolver false desde el método report de la excepción:

/**
 * Report the exception.
 */
public function report(): bool
{
    if (/** Determine if the exception needs custom reporting */) {
 
        // ...
 
        return true;
    }
 
    return false;
}

ℹ️ Puedes usar type-hint para cualquier dependencia en el método report y serán automáticamente inyectadas usado  Laravel service container.

Throttling Reported Exceptions

Si su aplicación reporta un gran número de excepciones, es posible que desee controlar cuántas excepciones se registran realmente o se envían al servicio externo de seguimiento de errores de su aplicación.

Para tomar una tasa de muestra aleatoria de excepciones, puede utilizar el método throttle exception en el archivo bootstrap/app.php de su aplicación. El método throttle recibe un closure que debe devolver una instancia de Lottery:

use Illuminate\Support\Lottery;
use Throwable;
 
->withExceptions(function (Exceptions $exceptions) {
    $exceptions->throttle(function (Throwable $e) {
        return Lottery::odds(1, 1000);
    });
})

También es posible muestrear condicionalmente en función del tipo de excepción. Si sólo desea muestrear instancias de una clase de excepción específica, puede devolver una instancia de Lottery sólo para esa clase:

use App\Exceptions\ApiMonitoringException;
use Illuminate\Support\Lottery;
use Throwable;
 
->withExceptions(function (Exceptions $exceptions) {
    $exceptions->throttle(function (Throwable $e) {
        if ($e instanceof ApiMonitoringException) {
            return Lottery::odds(1, 1000);
        }
    });
})

También puedes limitar las excepciones registradas o enviadas a un servicio externo de seguimiento de errores devolviendo una instancia de Limit en lugar de Lottery. Esto es útil si desea protegerse contra ráfagas repentinas de excepciones que inundan sus registros, por ejemplo, cuando un servicio de terceros utilizado por su aplicación está fuera de servicio:

use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Throwable;
 
->withExceptions(function (Exceptions $exceptions) {
    $exceptions->throttle(function (Throwable $e) {
        if ($e instanceof BroadcastException) {
            return Limit::perMinute(300);
        }
    });
})

Por defecto, los límites utilizarán la clase de la excepción como key para el rate limit. Puede personalizar esto especificando su propia clave utilizando el método by en la función Limit:

use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Throwable;
 
->withExceptions(function (Exceptions $exceptions) {
    $exceptions->throttle(function (Throwable $e) {
        if ($e instanceof BroadcastException) {
            return Limit::perMinute(300)->by($e->getMessage());
        }
    });
})

Por supuesto, puede devolver una mezcla de instancias de Lottery y Limit para diferentes excepciones:

use App\Exceptions\ApiMonitoringException;
use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Lottery;
use Throwable;
 
->withExceptions(function (Exceptions $exceptions) {
    $exceptions->throttle(function (Throwable $e) {
        return match (true) {
            $e instanceof BroadcastException => Limit::perMinute(300),
            $e instanceof ApiMonitoringException => Lottery::odds(1, 1000),
            default => Limit::none(),
        };
    });
})

HTTP Exceptions

Algunas excepciones describen códigos de error HTTP del servidor. Por ejemplo, puede tratarse de un error de “página no encontrada” (404), un “error no autorizado” (401), o incluso un error 500 generado por el desarrollador. Para generar una respuesta de este tipo desde cualquier parte de tu aplicación, puedes utilizar el helper abort:

abort(404);

Custom HTTP Error Pages

Laravel facilita la visualización de páginas de error personalizadas para varios códigos de estado HTTP. Por ejemplo, para personalizar la página de error para códigos de estado HTTP 404, crea una plantilla de vista **resources/views/errors/404.blade.php**. Esta vista se mostrará para todos los errores 404 generados por su aplicación. Las vistas dentro de este directorio deben ser nombradas para que coincidan con el código de estado HTTP al que corresponden. La instancia Symfony\Component\HttpKernel\Exception\HttpException levantada por la función abort se pasará a la vista como la variable $exception:

**<h2>{{ $exception->getMessage() }}</h2>**

Puedes publicar las plantillas de páginas de error por defecto de Laravel usando el comando vendor:publish de Artisan. Una vez publicadas las plantillas, puedes personalizarlas a tu gusto:

**php artisan vendor:publish --tag=laravel-errors**

Fallback HTTP Error Pages

También puede definir una página de error “fallback” para una serie determinada de códigos de estado HTTP. Esta página se mostrará si no existe una página correspondiente para el código de estado HTTP específico que se ha producido. Para ello, defina una plantilla 4xx.blade.php y una plantilla 5xx.blade.php en el directorio resources/views/errors de su aplicación.

Cuando se definen páginas de error fallback, las páginas fallback no afectarán a las respuestas de error 404, 500, y 503 ya que Laravel tiene páginas internas dedicadas para estos códigos de estado. Para personalizar las páginas renderizadas para estos códigos de estado, debes definir una página de error personalizada para cada uno de ellos individualmente.