오류 처리
번역일: 2026년 6월 20일
오류 처리
소개
Laravel 프로젝트를 새로 생성하면 오류 및 예외 처리가 이미 기본으로 설정되어 있습니다. 애플리케이션에서 발생하는 모든 예외는 App\Exceptions\Handler 클래스를 통해 로깅되고 사용자에게 응답으로 변환됩니다. 이 문서에서는 이 클래스를 자세히 살펴봅니다.
설정
config/app.php의 debug 옵션은 오류 정보를 사용자에게 얼마나 자세히 표시할지를 결정합니다. 기본값은 .env 파일의 APP_DEBUG 환경 변수를 따릅니다.
로컬 개발 환경에서는 APP_DEBUG=true로 설정해야 합니다. 운영 환경에서는 반드시 false로 설정하세요. true로 남겨두면 민감한 설정 값이 사용자에게 노출될 수 있습니다.
예외 핸들러
예외 리포팅
모든 예외는 App\Exceptions\Handler 클래스에서 처리됩니다. 이 클래스의 register 메서드에서 커스텀 예외 리포팅 및 렌더링 콜백을 등록할 수 있습니다.
예외 리포팅은 예외를 로그에 기록하거나 Flare, Bugsnag, Sentry 같은 외부 서비스로 전송하는 데 사용됩니다. 별도 설정이 없으면 로깅 설정에 따라 기록됩니다.
예외 종류별로 다른 방식으로 리포팅하려면 reportable 메서드로 클로저를 등록하세요. Laravel은 클로저의 타입 힌트를 보고 어떤 예외에 적용할지 자동으로 판단합니다.
use App\Exceptions\InvalidOrderException;
/**
* 애플리케이션의 예외 처리 콜백을 등록합니다.
*/
public function register(): void
{
$this->reportable(function (InvalidOrderException $e) {
// ...
});
}reportable로 커스텀 콜백을 등록해도 Laravel은 기본 로깅 설정에 따라 예외를 계속 기록합니다. 기본 로깅 스택으로의 전파를 막으려면 stop 메서드를 사용하거나 콜백에서 false를 반환하세요.
$this->reportable(function (InvalidOrderException $e) {
// ...
})->stop();
$this->reportable(function (InvalidOrderException $e) {
return false;
});NOTE
특정 예외 클래스에 리포팅 동작을 직접 정의하려면 리포터블 예외를 활용할 수도 있습니다.
전역 로그 컨텍스트
Laravel은 로그 메시지에 현재 로그인한 사용자의 ID를 자동으로 컨텍스트 데이터로 추가합니다. 추가적인 전역 컨텍스트 데이터가 필요하다면 App\Exceptions\Handler 클래스에 context 메서드를 정의하세요. 여기서 반환하는 데이터는 모든 예외 로그 메시지에 포함됩니다.
/**
* 로깅에 사용할 기본 컨텍스트 변수를 반환합니다.
*
* @return array<string, mixed>
*/
protected function context(): array
{
return array_merge(parent::context(), [
'foo' => 'bar',
]);
}예외별 로그 컨텍스트
전역 컨텍스트와 달리, 특정 예외에만 해당하는 추가 정보를 로그에 남기고 싶을 때가 있습니다. 예외 클래스에 context 메서드를 정의하면 해당 예외가 로깅될 때 이 데이터가 함께 기록됩니다.
<?php
namespace App\Exceptions;
use Exception;
class InvalidOrderException extends Exception
{
// ...
/**
* 예외의 컨텍스트 정보를 반환합니다.
*
* @return array<string, mixed>
*/
public function context(): array
{
return ['order_id' => $this->orderId];
}
}`report` 헬퍼
예외를 리포팅하면서도 현재 요청 처리를 계속 이어가야 할 때는 report 헬퍼를 사용하세요. 오류 페이지를 렌더링하지 않고 예외를 핸들러에 전달해 기록할 수 있습니다.
public function isValid(string $value): bool
{
try {
// 값을 검증합니다...
} catch (Throwable $e) {
report($e);
return false;
}
}중복 예외 리포팅 방지
애플리케이션 곳곳에서 report 함수를 사용하다 보면 동일한 예외가 여러 번 리포팅되어 로그에 중복 항목이 생길 수 있습니다.
동일한 예외 인스턴스를 한 번만 리포팅하도록 하려면 App\Exceptions\Handler 클래스에서 $withoutDuplicates 프로퍼티를 true로 설정하세요.
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
/**
* 동일한 예외 인스턴스는 한 번만 리포팅되도록 합니다.
*
* @var bool
*/
protected $withoutDuplicates = true;
// ...
}이제 report 헬퍼에 동일한 예외 인스턴스를 전달하면 첫 번째 호출만 리포팅됩니다.
$original = new RuntimeException('오류 발생!');
report($original); // 리포팅됨
try {
throw $original;
} catch (Throwable $caught) {
report($caught); // 무시됨 (동일 인스턴스)
}
report($original); // 무시됨
report($caught); // 무시됨예외 로그 레벨
로그에 메시지를 기록할 때는 해당 메시지의 심각도를 나타내는 로그 레벨이 함께 지정됩니다. 로그 레벨에 따라 메시지가 기록되는 채널이 달라질 수 있으므로, 특정 예외의 로그 레벨을 직접 지정하고 싶을 때가 있습니다.
예외 핸들러의 $levels 프로퍼티에 예외 타입과 로그 레벨을 배열로 정의하면 됩니다.
use PDOException;
use Psr\Log\LogLevel;
/**
* 예외 타입별 커스텀 로그 레벨 목록.
*
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
*/
protected $levels = [
PDOException::class => LogLevel::CRITICAL,
];예외 타입 무시하기
특정 예외는 리포팅할 필요가 없을 수 있습니다. 예외 핸들러의 $dontReport 프로퍼티에 해당 예외 클래스를 추가하면 리포팅에서 제외됩니다. 단, 렌더링 로직은 그대로 동작합니다.
use App\Exceptions\InvalidOrderException;
/**
* 리포팅하지 않을 예외 타입 목록.
*
* @var array<int, class-string<\Throwable>>
*/
protected $dontReport = [
InvalidOrderException::class,
];Laravel은 내부적으로 404 HTTP 오류나 유효하지 않은 CSRF 토큰으로 인한 419 응답 등 일부 예외를 기본으로 무시합니다. 이처럼 기본 무시 대상인 예외를 다시 리포팅 대상에 포함시키려면 register 메서드에서 stopIgnoring을 호출하세요.
use Symfony\Component\HttpKernel\Exception\HttpException;
/**
* 애플리케이션의 예외 처리 콜백을 등록합니다.
*/
public function register(): void
{
$this->stopIgnoring(HttpException::class);
// ...
}예외 렌더링
Laravel 예외 핸들러는 기본적으로 예외를 HTTP 응답으로 변환합니다. 특정 예외에 대해 커스텀 렌더링 방식을 지정하려면 renderable 메서드로 클로저를 등록하세요.
클로저는 Illuminate\Http\Response 인스턴스를 반환해야 하며, Laravel은 클로저의 타입 힌트를 통해 어떤 예외에 적용할지 판단합니다.
use App\Exceptions\InvalidOrderException;
use Illuminate\Http\Request;
/**
* 애플리케이션의 예외 처리 콜백을 등록합니다.
*/
public function register(): void
{
$this->renderable(function (InvalidOrderException $e, Request $request) {
return response()->view('errors.invalid-order', [], 500);
});
}renderable 메서드로 NotFoundHttpException 같은 Laravel 또는 Symfony 내장 예외의 렌더링 동작을 재정의할 수도 있습니다. 클로저가 아무 값도 반환하지 않으면 Laravel의 기본 렌더링 방식이 사용됩니다.
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* 애플리케이션의 예외 처리 콜백을 등록합니다.
*/
public function register(): void
{
$this->renderable(function (NotFoundHttpException $e, Request $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => '요청한 리소스를 찾을 수 없습니다.'
], 404);
}
});
}리포터블 & 렌더러블 예외
Handler의 register 메서드에서 콜백을 등록하는 대신, 예외 클래스에 직접 report와 render 메서드를 정의할 수도 있습니다. 이 메서드들이 존재하면 프레임워크가 자동으로 호출합니다.
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class InvalidOrderException extends Exception
{
/**
* 예외를 리포팅합니다.
*/
public function report(): void
{
// ...
}
/**
* 예외를 HTTP 응답으로 렌더링합니다.
*/
public function render(Request $request): Response
{
return response(/* ... */);
}
}이미 렌더링이 구현된 Laravel 또는 Symfony 내장 예외를 상속하는 경우, render 메서드에서 false를 반환하면 해당 예외의 기본 HTTP 응답을 그대로 사용합니다.
/**
* 예외를 HTTP 응답으로 렌더링합니다.
*/
public function render(Request $request): Response|bool
{
if (/** 커스텀 렌더링이 필요한 경우 */) {
return response(/* ... */);
}
return false;
}특정 조건에서만 커스텀 리포팅이 필요하고 나머지는 기본 처리에 맡기고 싶다면 report 메서드에서 false를 반환하세요.
/**
* 예외를 리포팅합니다.
*/
public function report(): bool
{
if (/** 커스텀 리포팅이 필요한 경우 */) {
// ...
return true;
}
return false;
}NOTE
report 메서드에 필요한 의존성을 타입 힌트로 선언하면 Laravel의 서비스 컨테이너가 자동으로 주입합니다.
리포팅 예외 스로틀링
애플리케이션에서 예외가 대량으로 발생하면 로그 또는 외부 오류 추적 서비스로 전송되는 예외 수를 제한하고 싶을 수 있습니다.
무작위 샘플링 방식으로 예외를 처리하려면 예외 핸들러의 throttle 메서드에서 Lottery 인스턴스를 반환하세요. App\Exceptions\Handler 클래스에 이 메서드가 없다면 직접 추가하면 됩니다.
use Illuminate\Support\Lottery;
use Throwable;
/**
* 수신된 예외를 스로틀링합니다.
*/
protected function throttle(Throwable $e): mixed
{
return Lottery::odds(1, 1000);
}예외 타입에 따라 조건부로 샘플링할 수도 있습니다. 특정 예외 클래스에만 Lottery를 적용하려면 다음과 같이 작성하세요.
use App\Exceptions\ApiMonitoringException;
use Illuminate\Support\Lottery;
use Throwable;
/**
* 수신된 예외를 스로틀링합니다.
*/
protected function throttle(Throwable $e): mixed
{
if ($e instanceof ApiMonitoringException) {
return Lottery::odds(1, 1000);
}
}Lottery 대신 Limit 인스턴스를 반환하면 시간당 리포팅 횟수를 제한할 수 있습니다. 예를 들어 외부 서비스 장애로 예외가 급증할 때 로그가 폭주하는 것을 방지할 수 있습니다.
use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Throwable;
/**
* 수신된 예외를 스로틀링합니다.
*/
protected function throttle(Throwable $e): mixed
{
if ($e instanceof BroadcastException) {
return Limit::perMinute(300);
}
}기본적으로 제한 키는 예외 클래스명이 사용됩니다. Limit의 by 메서드로 키를 직접 지정할 수도 있습니다.
use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Throwable;
/**
* 수신된 예외를 스로틀링합니다.
*/
protected function throttle(Throwable $e): mixed
{
if ($e instanceof BroadcastException) {
return Limit::perMinute(300)->by($e->getMessage());
}
}Lottery와 Limit을 예외 종류에 따라 혼합해서 사용할 수도 있습니다.
use App\Exceptions\ApiMonitoringException;
use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Lottery;
use Throwable;
/**
* 수신된 예외를 스로틀링합니다.
*/
protected function throttle(Throwable $e): mixed
{
return match (true) {
$e instanceof BroadcastException => Limit::perMinute(300),
$e instanceof ApiMonitoringException => Lottery::odds(1, 1000),
default => Limit::none(),
};
}HTTP 예외
일부 예외는 서버의 HTTP 오류 코드를 나타냅니다. 예를 들어 "페이지를 찾을 수 없음"(404), "인증 오류"(401), 개발자가 직접 발생시키는 500 오류 등이 있습니다. 애플리케이션 어디서든 abort 헬퍼를 사용해 이러한 응답을 반환할 수 있습니다.
abort(404);
커스텀 HTTP 오류 페이지
Laravel에서는 HTTP 상태 코드별로 커스텀 오류 페이지를 쉽게 만들 수 있습니다. 예를 들어 404 오류 페이지를 커스터마이징하려면 resources/views/errors/404.blade.php 뷰 파일을 생성하세요. 파일명은 HTTP 상태 코드와 일치해야 합니다. abort 함수가 발생시킨 Symfony\Component\HttpKernel\Exception\HttpException 인스턴스가 $exception 변수로 뷰에 전달됩니다.
<h2>{{ $exception->getMessage() }}</h2>
Laravel의 기본 오류 페이지 템플릿을 게시하려면 다음 Artisan 명령을 사용하세요. 게시 후 원하는 대로 수정할 수 있습니다.
php artisan vendor:publish --tag=laravel-errors폴백 HTTP 오류 페이지
특정 HTTP 상태 코드에 해당하는 뷰 파일이 없을 때 사용할 폴백 오류 페이지를 정의할 수도 있습니다. resources/views/errors 디렉터리에 4xx.blade.php와 5xx.blade.php 템플릿을 만들어두면, 대응하는 개별 파일이 없는 상태 코드의 오류가 발생했을 때 해당 템플릿이 사용됩니다.