오류 처리
번역일: 2026년 6월 20일
오류 처리
소개
새 Laravel 프로젝트를 시작하면 오류 및 예외 처리가 이미 기본으로 설정되어 있습니다. 그러나 언제든지 bootstrap/app.php의 withExceptions 메서드를 통해 예외를 어떻게 보고하고 렌더링할지 직접 커스터마이징할 수 있습니다.
withExceptions 클로저에 전달되는 $exceptions 객체는 Illuminate\Foundation\Configuration\Exceptions의 인스턴스로, 애플리케이션의 예외 처리를 전담합니다. 이 문서 전반에 걸쳐 이 객체를 자세히 살펴보겠습니다.
설정
config/app.php의 debug 옵션은 오류 발생 시 사용자에게 얼마나 상세한 정보를 보여줄지 결정합니다. 기본적으로 이 값은 .env 파일의 APP_DEBUG 환경 변수를 따릅니다.
로컬 개발 환경에서는 APP_DEBUG를 true로 설정하세요.
WARNING
운영(production) 환경에서는 APP_DEBUG 값을 반드시 false로 유지해야 합니다. true로 설정하면 민감한 설정값이 최종 사용자에게 노출될 위험이 있습니다.
예외 처리하기
예외 보고하기
예외 보고는 예외를 로그에 기록하거나 Sentry, Flare 같은 외부 서비스로 전송하는 용도로 사용됩니다. 기본적으로는 로깅 설정에 따라 예외가 기록됩니다.
특정 예외 타입에 따라 보고 방식을 달리하고 싶다면, bootstrap/app.php에서 report 메서드로 클로저를 등록하면 됩니다. Laravel은 클로저의 타입 힌트를 보고 어떤 예외에 적용할지 판단합니다.
use App\Exceptions\InvalidOrderException;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->report(function (InvalidOrderException $e) {
// ...
});
})report 메서드로 커스텀 보고 콜백을 등록하더라도, 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
특정 예외의 보고 방식을 커스터마이징할 때는 보고 가능 예외(reportable exceptions)를 활용하는 방법도 있습니다.
전역 로그 컨텍스트
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 HTTP 오류나 유효하지 않은 CSRF 토큰으로 인한 419 응답 등 일부 예외를 기본으로 무시합니다. 특정 예외 타입을 더 이상 무시하지 않도록 하려면 stopIgnoring 메서드를 사용하세요.
use Symfony\Component\HttpKernel\Exception\HttpException;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->stopIgnoring(HttpException::class);
})예외 렌더링하기
기본적으로 Laravel의 예외 핸들러는 예외를 HTTP 응답으로 변환합니다. 특정 예외 타입에 대해 커스텀 렌더링 클로저를 등록하려면 bootstrap/app.php에서 render 메서드를 사용하세요.
클로저는 response 헬퍼로 생성한 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 메서드로 NotFoundHttpException 같은 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();
});
})예외 응답 전체 커스터마이징
드물지만, Laravel 예외 핸들러가 생성하는 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 또는 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의 서비스 컨테이너가 자동으로 주입해 줍니다.
보고 예외 스로틀링
애플리케이션에서 대량의 예외가 발생하는 경우, 실제로 로그에 기록하거나 외부 오류 추적 서비스에 전송하는 예외 수를 제한하고 싶을 수 있습니다.
예외를 무작위 샘플링하려면 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 뷰 파일을 생성하세요. 이 파일은 애플리케이션에서 발생하는 모든 404 오류에 사용됩니다. errors 디렉터리 안의 뷰 파일명은 대응하는 HTTP 상태 코드와 일치해야 합니다. abort 함수에 의해 발생한 Symfony\Component\HttpKernel\Exception\HttpException 인스턴스는 $exception 변수로 뷰에 전달됩니다.
<h2>{{ $exception->getMessage() }}</h2>Laravel의 기본 오류 페이지 템플릿을 게시하려면 vendor:publish Artisan 명령어를 사용하세요. 게시 후 자유롭게 수정할 수 있습니다.
php artisan vendor:publish --tag=laravel-errors폴백 HTTP 오류 페이지
특정 HTTP 상태 코드 범위에 대한 "폴백" 오류 페이지를 정의할 수도 있습니다. 발생한 상태 코드에 해당하는 전용 페이지가 없을 때 이 페이지가 사용됩니다. resources/views/errors 디렉터리에 4xx.blade.php와 5xx.blade.php 템플릿을 생성하면 됩니다.
단, 404, 500, 503 오류는 Laravel이 내부적으로 전용 페이지를 가지고 있으므로 폴백 페이지의 영향을 받지 않습니다. 이 상태 코드의 페이지를 변경하려면 각각 개별 오류 페이지 파일을 따로 만들어야 합니다.