[LARAVEL] 예외 처리 & 로깅

김세연·2025년 9월 23일

Laravel

목록 보기
11/14
post-thumbnail

예외 처리 & 로깅 (Error Handling & Logging)

예외 처리 (Error Handling)

애플리케이션의 '응급실'과 같다.
사용자가 존재하지 않는 페이지에 접속하거나, 데이터베이스 연결이 끊기는 등 예상치 못한 문제가 발생했을 때, 프로그램이 그대로 멈춰버리지 않고 상황을 수습하고 적절한 조치를 취하는 과정이다.

로깅 (Logging)

비행기의 '블랙박스(비행 기록 장치)'와 같다.
애플리케이션에서 발생하는 모든 중요한 사건(에러, 주요 활동 등)을 파일에 기록으로 남기는 것이다.
나중에 문제가 발생했을 때 이 기록을 분석하여 원인을 찾을 수 있다.


예외 처리 (Error Handling) - 응급실 시스템

라라벨에서는 모든 예외(에러)가 발생하면 중앙 응급실 격인 app/Exceptions/Handler.php 파일로 모이게 된다.
개발자는 이 파일에서 특정 종류의 응급 상황에 어떻게 대처할지 미리 지시할 수 있다.

동작 방식

Handler.php 파일의 register() 메서드 안에서 특정 예외에 대한 처리 로직을 등록할 수 있다.

  • 예시:
    외부 API와 통신하다가 실패하는 ApiConnectionException이라는 예외가 발생했다고 가정했을 때 사용자에게 일반적인 에러 페이지 대신 "서비스 점검 중입니다." 라는 안내 페이지를 보여주고 싶을 수 있다.
// app/Exceptions/Handler.php
use App\Exceptions\ApiConnectionException;

public function register()
{
    // register() 메서드 안에 이 코드를 추가합니다.
    $this->renderable(function (ApiConnectionException $e, $request) {
        // ApiConnectionException 예외가 발생하면,
        // 'errors.api-down' 라는 뷰 파일을 보여줘라.
        return response()->view('errors.api-down', [], 503);
    });
}

이제 ApiConnectionException이 발생하면, 라라벨은 자동으로 503 Service Unavailable 상태 코드와 함께 우리가 지정한 안내 페이지를 사용자에게 보여준다.
이처럼 예외 처리를 통해 얘기치 않은 상황에서도 사용자 경험을 관리할 수 있다.


로깅 (Logging) - 블랙박스 기록

로깅은 문제 해결의 첫걸음이다.
운영 서버에서 발생한 에러는 개발자가 직접 볼 수 없기 때문에, 로그 파일에 남겨진 기록이 유일한 단서가 된다.

사용 방법

라라벨은 Log 파사드를 통해 매우 간단하게 로그를 남길 수 있는 방법을 제공한다.
로그 파일은 기본적으로 storage/logs/laravel.log에 저장된다.

  • 로그 레벨:
    로그는 심각도에 따라 여러 레벨로 나뉜다.

  • emergency, alert, critical, error: 심각한 오류

  • warning, notice: 경고 또는 주의가 필요한 정보

  • info, debug: 일반 정보 또는 개발/디버깅용 정보

사용 예시 (컨트롤러):

use Illuminate\Support\Facades\Log;

public function processPayment(Request $request)
{
    $paymentGateway = new PaymentGateway();

    Log::info('결제 시도 시작', ['user_id' => auth()->id(), 'amount' => $request->amount]);

    try {
        $result = $paymentGateway->charge($request->amount);
        Log::info('결제 성공');
        return response()->json(['status' => 'success']);

    } catch (PaymentFailedException $e) {
        // 결제 실패 시, 에러 로그를 상세히 기록한다.
        Log::error('결제 실패 발생!', [
            'user_id' => auth()->id(),
            'error_message' => $e->getMessage()
        ]);

        // 사용자에게는 간단한 에러 메시지를 반환한다.
        return response()->json(['error' => '결제에 실패했습니다.'], 500);
    }
}

위 예시처럼, try...catch 블록으로 예외를 직접 잡아서 처리하고, 중요한 상황마다 로그를 남겨두면, 나중에 "8월 15일 새벽 3시에 결제가 실패했는데 원인이 뭐지?" 와 같은 문제를 로그 파일을 분석하여 정확하고 빠르게 파악할 수 있다.

결론적으로, 예외 처리는 에러가 발생했을 때 애플리케이션이 무너지지 않도록 하는 '방어막'이고, 로깅은 그 원인을 추적하고 분석할 수 있게 해주는 'CCTV'와 같다.
이 둘은 견고하고 신뢰성 있는 백엔드를 만드는 데 필수적인 요소이다.


사용자 정의 예외 클래스 (Custom Exceptions)

try...catch 블록에서 모든 예외를 Exception이라는 하나의 클래스로만 잡는 것은, 모든 환자를 '환자'라고만 부르는 것과 같다.
더 구체적인 예외 클래스를 직접 만들어서 사용하면 코드가 훨씬 명확해지고 관리가 쉬워진다.

  • 왜 필요한가?:
    '결제 실패', '재고 부족', '외부 API 인증 실패' 등 문제의 원인은 다양하다.
    각각의 상황에 맞는 예외 클래스(예: PaymentFailedException, InsufficientStockException)를 만들어두면, 에러의 원인을 즉시 파악하고 그에 맞는 처리를 할 수 있다.

  • 생성 및 사용법:

    • php artisan make:exception InsufficientStockException 명령어로 예외 클래스를 만든다.

    • 문제가 발생한 곳에서 이 예외를 발생시킨다(throw).

      if ($product->stock < $quantity) {
        throw new InsufficientStockException('재고가 부족합니다.');
      }
      
    • Handler.php나 컨트롤러에서 이 특정 예외만 잡아서 특별한 처리를 한다.

      // Handler.php
      $this->renderable(function (InsufficientStockException $e, $request) {
        return response()->json(['error' => '죄송합니다. 상품 재고가 부족합니다.'], 422);
      });

로깅 채널과 스택 (Logging Channels & Stacks)

기본적으로 모든 로그는 하나의 파일(laravel.log)에 쌓이지만, 실제 운영 환경에서는 로그의 종류와 심각도에 따라 다른 곳으로 보내고 싶을 때가 많다.

  • 채널 (Channel):
    로그를 어디에 기록할지를 정의한다. (예: daily_files 채널, slack 채널, database 채널 등)

  • 스택 (Stack):
    여러 채널을 하나로 묶는 기능이다.

  • 설정 및 사용법 (config/logging.php):
    이 파일에서 다양한 채널을 설정할 수 있다.
    예를 들어, 아래와 같이 설정하면 심각한 에러는 Slack으로 알림을 보내고, 일반 디버그 정보는 파일에만 기록할 수 있다.

// config/logging.php
'channels' => [
    'stack' => [
        'driver' => 'stack',
        'channels' => ['daily', 'slack'], // 기본적으로 daily와 slack 채널을 함께 사용
    ],

    'daily' => [
        'driver' => 'daily',
        'path' => storage_path('logs/laravel.log'),
        'level' => 'debug', // 모든 레벨의 로그를 기록
    ],

    'slack' => [
        'driver' => 'slack',
        'url' => env('LOG_SLACK_WEBHOOK_URL'),
        'level' => 'critical', // critical 레벨 이상의 로그만 Slack으로 보냄
    ],
],

이제 Log::critical('서버 다운!') 같은 로그를 남기면, 파일에도 기록되고 동시에 Slack으로 즉시 알림이 와서 문제를 빠르게 인지하고 대처할 수 있다.


보고(report)와 렌더링(render)의 분리

Handler.php에서 예외를 처리할 때, 두 가지 주요 단계를 거친다.
이 둘을 구분해서 이해하는 것이 중요하다.

  • report():
    예외를 기록하는 단계이다.
    이 예외를 로그 파일에 남길지, 외부 에러 추적 서비스(Sentry, Bugsnag 등)에 보낼지를 결정한다.
    사용자에게는 아무것도 보여주지 않는다.

  • render():
    예외를 사용자에게 어떻게 보여줄지 결정하는 단계이다.
    이 예외를 특정 에러 페이지로 보여줄지, JSON 응답으로 변환할지를 결정한다.

예를 들어, 결제 실패 예외(PaymentFailedException)가 발생했을 때,

  • report() 단계에서는 Slack으로 긴급 알림을 보내고,

  • render() 단계에서는 사용자에게 "결제에 실패했습니다. 다시 시도해 주세요." 라는 안내 페이지를 보여주도록 분리해서 처리할 수 있다.


로그 컨텍스트 (Logging Context)

로그를 남길 때, 문제 해결에 도움이 되는 추가적인 문맥 정보를 함께 기록하는 것이 매우 중요하다.

  • 예시:
    단순히 "주문 생성 실패" 라고만 기록하는 것보다, 어떤 사용자의 어떤 상품에 대한 주문이었는지 함께 기록하는 것이 훨씬 유용하다.
Log::error('주문 생성 실패', [
    'user_id' => $user->id,
    'product_id' => $product->id,
    'ip_address' => $request->ip()
]);

또한 Log::withContext()를 사용하면, 특정 요청이 처리되는 동안 발생하는 모든 로그에 request-id 같은 고유한 식별자를 자동으로 추가하여, 여러 로그 메시지를 하나의 흐름으로 묶어서 추적하는 것을 훨씬 쉽게 만들 수 있다.

마지막 한 걸음

외부 에러 추적 서비스 (External Error Tracking Services)

로그 파일은 수동적으로 분석해야 하지만, 전문 에러 추적 서비스들은 능동적으로 에러를 분석하고, 그룹화하며, 알림을 보내주는 역할을 한다. 마치 24시간 대기하는 응급실 전문의 팀과 같다.

  • 대표적인 서비스:
    Sentry, Bugsnag, Flare (라라벨 전용)
  • 주요 기능:
    • 에러 그룹화:
      동일한 에러가 1,000번 발생하면, 1,000줄의 로그 대신 '1,000번 발생한 에러' 하나로 묶어서 보여준다.
    • 상세한 컨텍스트:
      에러 발생 시의 사용자 정보, 요청 데이터, 이전 활동 기록(Breadcrumbs) 등을 자동으로 수집하여 버그의 원인을 파악하는 데 결정적인 단서를 제공한다.
    • 지능적인 알림:
      새로운 종류의 에러가 발생했을 때만 Slack이나 이메일로 알림을 보내준다.

중앙화된 로깅 시스템 (Centralized Logging Systems)

애플리케이션의 규모가 커져서 여러 서버에서 실행될 경우, 각 서버에 흩어져 있는 로그 파일을 일일이 확인하는 것은 불가능하다.
중앙화된 로깅 시스템은 모든 서버의 로그를 하나의 중앙 서버로 모아서 검색하고 분석할 수 있게 해준다.

  • 대표적인 기술/서비스:
    ELK Stack (Elasticsearch, Logstash, Kibana), Papertrail, Datadog
  • 장점:
    분산된 환경에서도 모든 로그를 한눈에 파악하고, 강력한 검색과 시각화 도구를 통해 시스템의 상태를 모니터링할 수 있다.

결론

예외 처리와 로깅의 철학은 단순히 버그를 잡는 것을 넘어선다.
이것은 "문제가 발생할 것을 미리 예상하고 대비한다"는 방어적 프로그래밍(Defensive Programming) 자세의 핵심이다.

profile
공부 재밌따

0개의 댓글