본문 바로가기

오류 처리

번역일: 2026년 6월 20일

오류 처리

소개

새 Laravel 프로젝트를 시작하면 오류 및 예외 처리는 이미 자동으로 설정되어 있습니다. 필요에 따라 bootstrap/app.phpwithExceptions 메서드를 통해 예외를 어떻게 리포트하고 렌더링할지 세밀하게 제어할 수 있습니다.

withExceptions 클로저에 전달되는 $exceptions 객체는 Illuminate\Foundation\Configuration\Exceptions 인스턴스로, 애플리케이션의 예외 처리 전반을 담당합니다. 이 문서 전체에서 이 객체를 중심으로 설명합니다.

설정

config/app.phpdebug 옵션은 오류 정보를 사용자에게 얼마나 상세하게 표시할지를 결정합니다. 기본값은 .env 파일의 APP_DEBUG 환경 변수를 따릅니다.

로컬 개발 환경에서는 APP_DEBUGtrue로 설정하세요. 운영 환경에서는 반드시 false로 설정해야 합니다. true로 두면 민감한 설정 정보가 사용자에게 노출될 위험이 있습니다.

예외 처리하기

예외 리포팅

예외 리포팅은 예외를 로그에 기록하거나 Sentry, Flare 같은 외부 서비스로 전송하는 데 사용됩니다. 기본적으로는 로깅 설정에 따라 예외가 기록됩니다. 물론 원하는 방식으로 자유롭게 커스터마이징할 수 있습니다.

예외 유형마다 다른 방식으로 리포팅하고 싶다면, bootstrap/app.php에서 report 메서드로 클로저를 등록하면 됩니다. Laravel은 클로저의 타입 힌트를 보고 어떤 예외에 해당하는지 자동으로 판별합니다.

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

report 메서드로 커스텀 리포팅 콜백을 등록하더라도, Laravel은 기본 로깅 설정에 따라 예외를 계속 기록합니다. 기본 로깅 스택으로의 전파를 막으려면 stop 메서드를 사용하거나, 콜백에서 false를 반환하면 됩니다.

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

NOTE

특정 예외 클래스에 리포팅 동작을 직접 정의하려면 리포트 가능한 예외를 활용할 수도 있습니다.

전역 로그 컨텍스트

Laravel은 기본적으로 로그 메시지에 현재 로그인한 사용자의 ID를 자동으로 포함합니다. 여기에 더해 bootstrap/app.php에서 context 메서드를 사용하면, 모든 예외 로그에 공통적으로 포함될 컨텍스트 데이터를 직접 정의할 수 있습니다.

->withExceptions(function (Exceptions $exceptions) { $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) { $exceptions->dontReportDuplicates(); })

이렇게 설정하면 같은 예외 인스턴스에 대해 report 헬퍼를 여러 번 호출해도 첫 번째 호출만 실제로 리포팅됩니다.

$original = new RuntimeException('문제가 발생했습니다!'); 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) { $exceptions->level(PDOException::class, LogLevel::CRITICAL); })

특정 예외 무시하기

애플리케이션을 개발하다 보면 절대로 리포팅하고 싶지 않은 예외 유형이 생깁니다. 이런 예외는 bootstrap/app.php에서 dontReport 메서드로 등록하면 됩니다. 등록된 클래스는 리포팅되지 않지만, 커스텀 렌더링 로직은 여전히 적용될 수 있습니다.

use App\Exceptions\InvalidOrderException; ->withExceptions(function (Exceptions $exceptions) { $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 { // }

Laravel은 내부적으로 404 HTTP 오류나 유효하지 않은 CSRF 토큰으로 인한 419 응답 등 일부 예외를 기본으로 무시합니다. 특정 예외 유형에 대한 무시 설정을 해제하려면 bootstrap/app.php에서 stopIgnoring 메서드를 사용하세요.

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

예외 렌더링

Laravel의 예외 핸들러는 기본적으로 예외를 HTTP 응답으로 변환해줍니다. 특정 예외 유형에 대해 커스텀 렌더링 로직을 적용하고 싶다면 bootstrap/app.php에서 render 메서드를 사용하세요.

render 메서드에 전달하는 클로저는 Illuminate\Http\Response 인스턴스를 반환해야 합니다. response 헬퍼로 생성할 수 있으며, Laravel은 클로저의 타입 힌트를 보고 어떤 예외에 적용할지 결정합니다.

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); }); })

render 메서드로 NotFoundHttpException처럼 Laravel이나 Symfony에 내장된 예외의 렌더링 동작을 재정의할 수도 있습니다. 클로저에서 값을 반환하지 않으면 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' => '레코드를 찾을 수 없습니다.' ], 404); } }); })

예외를 JSON으로 렌더링하기

예외를 렌더링할 때 Laravel은 요청의 Accept 헤더를 기반으로 HTML 또는 JSON 응답 중 무엇을 반환할지 자동으로 판단합니다. 이 판단 로직을 직접 제어하고 싶다면 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(); }); })

예외 응답 전체 커스터마이징

드물지만 Laravel 예외 핸들러가 생성하는 HTTP 응답 전체를 수정해야 하는 경우가 있습니다. 이때는 respond 메서드로 응답 커스터마이징 클로저를 등록하세요.

use Symfony\Component\HttpFoundation\Response; ->withExceptions(function (Exceptions $exceptions) { $exceptions->respond(function (Response $response) { if ($response->getStatusCode() === 419) { return back()->with([ 'message' => '페이지가 만료되었습니다. 다시 시도해 주세요.', ]); } return $response; }); })

리포트 가능·렌더링 가능한 예외

bootstrap/app.php에 모든 예외 처리 로직을 몰아넣는 대신, 예외 클래스 자체에 reportrender 메서드를 직접 정의할 수 있습니다. 이 메서드가 존재하면 프레임워크가 자동으로 호출합니다.

<?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 메서드를 사용하세요. throttle 메서드는 Lottery 인스턴스를 반환하는 클로저를 받습니다.

use Illuminate\Support\Lottery; use Throwable; ->withExceptions(function (Exceptions $exceptions) { $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) { $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) { $exceptions->throttle(function (Throwable $e) { if ($e instanceof BroadcastException) { return Limit::perMinute(300); } }); })

기본적으로 제한 키는 예외 클래스명이 사용됩니다. Limitby 메서드로 원하는 키를 직접 지정할 수 있습니다.

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()); } }); })

물론 예외 유형에 따라 LotteryLimit을 조합해서 사용하는 것도 가능합니다.

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 예외

일부 예외는 서버의 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 기본 오류 페이지 템플릿을 게시하려면 다음 Artisan 명령어를 사용하세요. 게시한 후 원하는 대로 수정할 수 있습니다.

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

폴백 HTTP 오류 페이지

특정 HTTP 상태 코드에 해당하는 전용 뷰가 없을 때 사용할 폴백 오류 페이지를 정의할 수 있습니다. resources/views/errors 디렉터리에 4xx.blade.php5xx.blade.php 파일을 만들면 됩니다.

단, 404, 500, 503 오류는 Laravel 내부에 전용 처리 페이지가 있으므로 폴백 페이지가 적용되지 않습니다. 이 상태 코드의 페이지를 커스터마이징하려면 각각의 전용 오류 페이지 파일을 별도로 만들어야 합니다.

이 문서는 Laravel 공식 문서(MIT)를 한국 개발자를 위해 번역·재구성한 것입니다.

번역일: 2026년 6월 20일