오류 처리
업데이트됨번역일: 2026년 7월 15일
이 페이지는 원문이 업데이트되어 번역이 갱신되었습니다.
- 원문 수정
- 2026년 7월 15일
- 번역 갱신
- 2026년 7월 15일
오류 처리
소개
새 Laravel 프로젝트를 시작하면 오류 및 예외 처리는 이미 기본으로 구성되어 있습니다. 필요에 따라 bootstrap/app.php의 withExceptions 메서드를 통해 예외를 어떻게 리포팅하고 렌더링할지 커스터마이즈할 수 있습니다.
withExceptions 클로저에 전달되는 $exceptions 객체는 Illuminate\Foundation\Configuration\Exceptions의 인스턴스로, 애플리케이션의 예외 처리를 총괄합니다. 이 문서에서 이 객체를 활용하는 다양한 방법을 살펴봅니다.
설정
config/app.php의 debug 옵션은 오류 발생 시 사용자에게 얼마나 많은 정보를 보여줄지를 결정합니다. 기본적으로 이 옵션은 .env 파일의 APP_DEBUG 환경 변수 값을 따릅니다.
로컬 개발 환경에서는 APP_DEBUG를 true로 설정해야 디버그 정보를 확인할 수 있습니다.
WARNING
프로덕션 환경에서는 APP_DEBUG를 반드시 false로 설정해야 합니다. true로 두면 민감한 설정 값이 최종 사용자에게 노출될 위험이 있습니다.
예외 처리
예외 리포팅
예외 리포팅은 예외를 로그에 기록하거나 Laravel Nightwatch, Sentry, Flare 같은 외부 서비스로 전송하는 데 사용됩니다. 기본적으로는 로깅 설정에 따라 예외가 기록됩니다.
특정 예외 타입에 맞는 리포팅 로직이 필요하다면 bootstrap/app.php에서 report 메서드로 클로저를 등록하면 됩니다. Laravel은 클로저의 타입 힌트를 보고 어떤 예외에 해당 클로저를 적용할지 판단합니다.
use App\Exceptions\InvalidOrderException;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->report(function (InvalidOrderException $e) {
// ...
});
})커스텀 리포팅 콜백을 등록하더라도, Laravel은 기본 로깅 설정에 따라 예외를 추가로 기록합니다. 기본 로그 스택으로의 전파를 막으려면 stop 메서드를 체이닝하거나 콜백에서 false를 반환하면 됩니다.
use App\Exceptions\InvalidOrderException;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->report(function (InvalidOrderException $e) {
// ...
})->stop();
$exceptions->report(function (InvalidOrderException $e) {
return false;
});
})NOTE
특정 예외의 리포팅을 커스터마이즈하려면 리포터블 예외를 활용하는 방법도 있습니다.
전역 로그 컨텍스트
Laravel은 기본적으로 현재 로그인한 사용자의 ID를 모든 예외 로그 메시지에 자동으로 포함시킵니다. 여기에 추가적인 전역 컨텍스트 데이터를 붙이고 싶다면 bootstrap/app.php에서 context 메서드를 사용하세요. 이 데이터는 애플리케이션이 기록하는 모든 예외 로그에 함께 포함됩니다.
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->context(fn () => [
'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 함수를 사용하다 보면 같은 예외가 여러 번 리포팅되어 로그에 중복 항목이 생길 수 있습니다.
동일한 예외 인스턴스가 한 번만 리포팅되도록 하려면 bootstrap/app.php에서 dontReportDuplicates 메서드를 호출하면 됩니다.
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->dontReportDuplicates();
})이제 동일한 예외 인스턴스로 report를 여러 번 호출해도 첫 번째 호출만 리포팅됩니다.
$original = new RuntimeException('Whoops!');
report($original); // 리포팅됨
try {
throw $original;
} catch (Throwable $caught) {
report($caught); // 무시됨
}
report($original); // 무시됨
report($caught); // 무시됨예외 로그 레벨
로그에 메시지를 기록할 때는 로그 레벨이 함께 지정됩니다. 로그 레벨은 메시지의 심각도나 중요도를 나타내며, 어떤 채널에 기록될지에도 영향을 줄 수 있습니다.
특정 예외를 특정 로그 레벨로 기록하고 싶다면 bootstrap/app.php에서 level 메서드를 사용하세요. 첫 번째 인수로 예외 타입을, 두 번째 인수로 로그 레벨을 전달합니다.
use PDOException;
use Psr\Log\LogLevel;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->level(PDOException::class, LogLevel::CRITICAL);
})특정 타입 예외 무시하기
애플리케이션을 개발하다 보면 절대 리포팅하고 싶지 않은 예외 타입이 있을 수 있습니다. 이런 예외는 bootstrap/app.php에서 dontReport 메서드로 등록하면 됩니다. 등록된 클래스는 리포팅되지 않지만, 커스텀 렌더링 로직은 여전히 동작합니다.
use App\Exceptions\InvalidOrderException;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->dontReport([
InvalidOrderException::class,
]);
})또는 예외 클래스에 Illuminate\Contracts\Debug\ShouldntReport 인터페이스를 구현하는 방법도 있습니다. 이 인터페이스가 구현된 예외는 Laravel의 예외 핸들러에 의해 절대 리포팅되지 않습니다.
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Contracts\Debug\ShouldntReport;
class PodcastProcessingException extends Exception implements ShouldntReport
{
//
}특정 조건에 따라 더 세밀하게 제어하고 싶다면 dontReportWhen 메서드에 클로저를 전달하면 됩니다.
use App\Exceptions\InvalidOrderException;
use Throwable;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->dontReportWhen(function (Throwable $e) {
return $e instanceof PodcastProcessingException &&
$e->reason() === 'Subscription expired';
});
})Laravel은 내부적으로 일부 예외를 이미 자동으로 무시합니다. 예를 들어 404 오류, CORS 위반으로 인한 403 응답, 유효하지 않은 CSRF 토큰으로 인한 419 응답 등이 그 대상입니다. 이 중 특정 예외를 다시 리포팅되도록 되돌리고 싶다면 stopIgnoring 메서드를 사용하세요.
use Symfony\Component\HttpKernel\Exception\HttpException;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->stopIgnoring(HttpException::class);
})예외 렌더링
Laravel의 예외 핸들러는 기본적으로 예외를 HTTP 응답으로 변환합니다. 특정 예외 타입에 대해 커스텀 렌더링 로직이 필요하다면 bootstrap/app.php에서 render 메서드로 클로저를 등록하면 됩니다.
클로저는 Illuminate\Http\Response 인스턴스를 반환해야 하며, Laravel은 타입 힌트를 통해 어떤 예외에 적용할지 판단합니다.
use App\Exceptions\InvalidOrderException;
use Illuminate\Http\Request;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->render(function (InvalidOrderException $e, Request $request) {
return response()->view('errors.invalid-order', status: 500);
});
})render 메서드로 Laravel 내장 예외나 Symfony 예외의 렌더링 동작을 재정의할 수도 있습니다. 클로저가 값을 반환하지 않으면 Laravel의 기본 렌더링이 사용됩니다.
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->render(function (NotFoundHttpException $e, Request $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => '요청한 리소스를 찾을 수 없습니다.'
], 404);
}
});
})예외를 JSON으로 렌더링하기
예외를 렌더링할 때 Laravel은 요청의 Accept 헤더를 확인하여 HTML 응답과 JSON 응답 중 무엇을 반환할지 자동으로 결정합니다. 이 판단 로직을 직접 커스터마이즈하고 싶다면 shouldRenderJsonWhen 메서드를 활용하세요.
use Illuminate\Http\Request;
use Throwable;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->shouldRenderJsonWhen(function (Request $request, Throwable $e) {
if ($request->is('admin/*')) {
return true;
}
return $request->expectsJson();
});
})예외 응답 전체 커스터마이즈
드물지만, 예외 핸들러가 반환하는 HTTP 응답 자체를 완전히 커스터마이즈해야 할 때가 있습니다. 이 경우 respond 메서드로 응답 커스터마이즈 클로저를 등록하면 됩니다.
use Symfony\Component\HttpFoundation\Response;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->respond(function (Response $response) {
if ($response->getStatusCode() === 419) {
return back()->with([
'message' => '페이지가 만료되었습니다. 다시 시도해 주세요.',
]);
}
return $response;
});
})리포터블 / 렌더러블 예외
bootstrap/app.php에 모든 처리 로직을 모아두는 대신, 예외 클래스 자체에 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 내장 예외처럼 이미 렌더링 가능한 예외를 상속하고 있다면, 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의 서비스 컨테이너가 자동으로 주입합니다.
리포팅 예외 스로틀링
애플리케이션에서 예외가 대량으로 발생할 경우, 실제로 로그에 기록하거나 외부 오류 추적 서비스로 전송하는 수를 제한하고 싶을 수 있습니다.
무작위 샘플링을 적용하려면 bootstrap/app.php에서 throttle 메서드를 사용하세요. 이 메서드는 Lottery 인스턴스를 반환하는 클로저를 받습니다.
use Illuminate\Support\Lottery;
use Throwable;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->throttle(function (Throwable $e) {
return Lottery::odds(1, 1000);
});
})특정 예외 타입에만 샘플링을 적용할 수도 있습니다. 해당 클래스의 예외일 때만 Lottery 인스턴스를 반환하면 됩니다.
use App\Exceptions\ApiMonitoringException;
use Illuminate\Support\Lottery;
use Throwable;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->throttle(function (Throwable $e) {
if ($e instanceof ApiMonitoringException) {
return Lottery::odds(1, 1000);
}
});
})Lottery 대신 Limit 인스턴스를 반환하면 분당 최대 기록 횟수를 제한할 수도 있습니다. 서드파티 서비스가 갑자기 다운되어 예외가 폭발적으로 증가할 때 로그가 넘치는 것을 막는 데 유용합니다.
use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Throwable;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->throttle(function (Throwable $e) {
if ($e instanceof BroadcastException) {
return Limit::perMinute(300);
}
});
})기본적으로 예외의 클래스명이 레이트 리밋 키로 사용됩니다. Limit의 by 메서드로 키를 직접 지정할 수도 있습니다.
use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Throwable;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->throttle(function (Throwable $e) {
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;
->withExceptions(function (Exceptions $exceptions): void {
$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 예외
일부 예외는 서버의 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의 기본 오류 페이지 템플릿은 vendor:publish Artisan 명령어로 퍼블리시할 수 있습니다. 퍼블리시 후 원하는 대로 수정하면 됩니다.
php artisan vendor:publish --tag=laravel-errors폴백 HTTP 오류 페이지
특정 상태 코드 범위에 대한 "폴백" 오류 페이지를 정의할 수도 있습니다. 특정 상태 코드에 해당하는 뷰 파일이 없을 때 이 페이지가 대신 표시됩니다. resources/views/errors 디렉터리에 4xx.blade.php와 5xx.blade.php 템플릿을 만들면 됩니다.
단, 404, 500, 503은 Laravel이 내부적으로 전용 페이지를 갖고 있으므로 폴백 페이지의 영향을 받지 않습니다. 이 상태 코드들을 커스터마이즈하려면 각 상태 코드에 해당하는 뷰 파일을 개별적으로 만들어야 합니다.