본문으로 건너뛰기
버전: master

Logging

Introduction

애플리케이션 내에서 어떤 일이 일어나고 있는지 더 잘 이해할 수 있도록, Laravel은 파일, 시스템 에러 로그, 그리고 팀 전체에 알림을 보낼 수 있는 Slack 등 다양한 장소에 로그 메시지를 기록할 수 있는 강력한 로깅 서비스를 제공합니다.

Laravel의 로깅은 "채널(channels)" 기반으로 동작합니다. 각 채널은 로그 정보를 기록하는 특정 방식을 나타냅니다. 예를 들어, single 채널은 하나의 단일 로그 파일에 기록하는 반면, slack 채널은 로그 메시지를 Slack으로 전송합니다. 메시지는 심각도에 따라 여러 채널에 동시에 기록될 수 있습니다.

내부적으로 Laravel은 다양한 강력한 로그 핸들러를 지원하는 Monolog 라이브러리를 사용합니다. 이러한 핸들러의 설정을 매우 쉽게 할 수 있도록 도와주며, 여러 핸들러를 조합해 애플리케이션의 로그 처리를 맞춤 구성할 수 있습니다.

Configuration

애플리케이션의 로깅 동작을 제어하는 모든 설정 옵션은 config/logging.php 설정 파일에 모여 있습니다. 이 파일에서 애플리케이션의 로그 채널을 구성할 수 있으니, 제공되는 여러 채널과 각 옵션을 꼼꼼히 확인하는 것이 좋습니다. 아래에서는 몇 가지 일반적인 옵션을 살펴보겠습니다.

기본적으로 Laravel은 로그 메시지 작성 시 stack 채널을 사용합니다. stack 채널은 여러 개의 로그 채널을 하나로 묶어 사용하기 위한 것입니다. 스택 구성에 관한 자세한 내용은 documentation below를 참고하세요.

Available Channel Drivers

각 로그 채널은 "드라이버"로 동작합니다. 드라이버는 로그 메시지가 실제로 어떻게, 어디에 기록될지를 결정합니다. 모든 Laravel 애플리케이션에서 사용할 수 있는 로그 채널 드라이버는 다음과 같습니다. 대부분의 드라이버에 대한 설정 항목이 이미 config/logging.php에 포함되어 있으므로 파일 내용을 꼭 확인해보세요:

이름설명
custom지정된 팩토리를 호출해 채널을 생성하는 드라이버
daily일별로 회전하는 RotatingFileHandler 기반 Monolog 드라이버
errorlogErrorLogHandler 기반 Monolog 드라이버
monologMonolog 핸들러를 자유롭게 사용할 수 있는 Monolog 팩토리 드라이버
papertrailSyslogUdpHandler 기반 Monolog 드라이버
single단일 파일 또는 경로 기반 로거 채널 (StreamHandler)
slackSlackWebhookHandler 기반 Monolog 드라이버
stack여러 채널을 묶어 "멀티 채널"을 생성하기 위한 래퍼
syslogSyslogHandler 기반 Monolog 드라이버

monologcustom 드라이버에 대해 더 알고 싶으면 advanced channel customization 문서를 확인하세요.

Configuring the Channel Name

기본적으로 Monolog 인스턴스는 현재 환경 이름(예: production, local)과 일치하는 "채널 이름"으로 생성됩니다. 이 값을 변경하려면 채널 설정에 name 옵션을 추가하면 됩니다:

'stack' => [
'driver' => 'stack',
'name' => 'channel-name',
'channels' => ['single', 'slack'],
],

Channel Prerequisites

Configuring the Single and Daily Channels

singledaily 채널은 bubble, permission, locking 세 가지 선택적 설정 항목을 지원합니다.

이름설명기본값
bubble메시지가 처리된 후 다른 채널로 전파(bubble)할지 여부를 나타냅니다.true
locking쓰기 전에 로그 파일 잠금 시도 여부false
permission로그 파일의 퍼미션 (권한)0644

또한, daily 채널의 로그 보존 기간은 LOG_DAILY_DAYS 환경 변수나 days 설정 옵션으로 구성할 수 있습니다.

이름설명기본값
days일간 로그 파일을 몇 일간 보존할지 지정합니다.14

Configuring the Papertrail Channel

papertrail 채널은 hostport 설정이 필요합니다. 일반적으로 PAPERTRAIL_URLPAPERTRAIL_PORT 환경 변수로 지정할 수 있으며, 값을 얻으려면 Papertrail를 참고하세요.

Configuring the Slack Channel

slack 채널에는 url 옵션이 필수입니다. 이는 LOG_SLACK_WEBHOOK_URL 환경 변수로 지정할 수 있으며, 해당 URL은 Slack 팀에 설정한 incoming webhook 주소와 일치해야 합니다.

기본적으로 Slack에는 critical 레벨 이상의 로그만 전달됩니다. 하지만 LOG_LEVEL 환경 변수나 Slack 로그 채널 설정 배열 내 level 옵션을 변경하여 조정할 수 있습니다.

Logging Deprecation Warnings

PHP, Laravel 및 다른 라이브러리들은 언제 제거될 예정인 기능 등에 대해 사용 중단(deprecation) 경고를 알리는 경우가 많습니다. 이러한 경고를 로그에 기록하고자 한다면, LOG_DEPRECATIONS_CHANNEL 환경 변수나 config/logging.php 설정 파일에 선호하는 deprecations 로그 채널을 지정할 수 있습니다:

'deprecations' => [
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
],

'channels' => [
// ...
]

또는 deprecations라는 이름의 로그 채널을 직접 정의할 수도 있습니다. 만약 해당 이름의 채널이 존재하면, 항상 사용 중단 경고를 이 채널에 기록합니다:

'channels' => [
'deprecations' => [
'driver' => 'single',
'path' => storage_path('logs/php-deprecation-warnings.log'),
],
],

Building Log Stacks

앞서 설명했듯, stack 드라이버는 여러 채널을 하나의 로그 채널로 묶어서 편리하게 쓸 수 있게 해줍니다. 실제 운영 환경에서 볼 수 있을 법한 예시 설정을 살펴보겠습니다:

'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['syslog', 'slack'], // [tl! add]
'ignore_exceptions' => false,
],

'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
'replace_placeholders' => true,
],

'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
'level' => env('LOG_LEVEL', 'critical'),
'replace_placeholders' => true,
],
],

위 설정을 하나씩 살펴봅니다. 먼저 stack 채널은 channels 옵션을 통해 syslogslack 두 채널을 묶고 있습니다. 따라서 로그가 작성될 때 두 채널 모두 메시지를 로그할 기회를 갖게 됩니다. 하지만 아래 설명할 메시지 심각도 / "레벨"에 따라서 각 채널의 기록 여부가 결정됩니다.

Log Levels

위 예시에서 syslogslack 채널 설정에 동봉된 level 옵션을 눈여겨보세요. 이 옵션은 해당 채널이 로그를 기록하기 위한 최소 "레벨"을 정의합니다. Laravel의 로깅은 Monolog을 기반으로 하며, Monolog은 RFC 5424 specification에 정의된 다음 8가지 로그 레벨을 지원합니다. 높은 심각도 순서대로: emergency, alert, critical, error, warning, notice, info, debug 입니다.

예를 들어, 다음과 같이 debug 레벨로 메시지를 로그했다고 가정합시다:

Log::debug('An informational message.');

설정대로라면 syslog 채널은 이 메시지를 시스템 로그에 기록하지만, 에러 메시지가 critical 이상이 아니므로 Slack으로는 전송하지 않습니다. 반면 emergency 메시지를 기록하면, emergency 레벨이 두 채널의 최소 레벨 기준을 모두 넘으므로 시스템 로그와 Slack 양쪽 모두에 전송됩니다:

Log::emergency('The system is down!');

Writing Log Messages

Log facade를 통해 로그에 내용을 기록할 수 있습니다. 앞에서 설명했듯, 로거는 RFC 5424 specification에 정의된 8가지 로그 레벨에 맞는 메서드를 제공합니다: emergency, alert, critical, error, warning, notice, info, debug

use Illuminate\Support\Facades\Log;

Log::emergency($message);
Log::alert($message);
Log::critical($message);
Log::error($message);
Log::warning($message);
Log::notice($message);
Log::info($message);
Log::debug($message);

이들 메서드를 사용해 각 레벨에 맞는 메시지를 기록할 수 있습니다. 기본적으로는 logging 설정 파일에 명시된 기본 로그 채널에 기록됩니다:

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;

class UserController extends Controller
{
/**
* Show the profile for the given user.
*/
public function show(string $id): View
{
Log::info('Showing the user profile for user: {id}', ['id' => $id]);

return view('user.profile', [
'user' => User::findOrFail($id)
]);
}
}

Contextual Information

로그 메서드에는 로그 메시지와 함께 표시할 추가적인 컨텍스트 데이터를 배열로 전달할 수 있습니다.

use Illuminate\Support\Facades\Log;

Log::info('User {id} failed to login.', ['id' => $user->id]);

특정 채널에 기록되는 모든 이후 로그에 포함될 공통 컨텍스트 정보를 지정할 수도 있습니다. 예를 들어, 애플리케이션에 들어오는 각 요청과 연관된 고유 요청 ID를 기록하고싶을 때, Log 파사드의 withContext 메서드를 사용할 수 있습니다.

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;

class AssignRequestId
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$requestId = (string) Str::uuid();

Log::withContext([
'request-id' => $requestId
]);

$response = $next($request);

$response->headers->set('Request-Id', $requestId);

return $response;
}
}

만약 모든 로그 채널에 걸쳐 공통 컨텍스트를 공유하고자 한다면, Log::shareContext() 메서드를 사용할 수 있습니다. 이 메서드는 현재 생성된 모든 채널과 이후 생성될 모든 채널에 컨텍스트 정보를 제공합니다.

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;

class AssignRequestId
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$requestId = (string) Str::uuid();

Log::shareContext([
'request-id' => $requestId
]);

// ...
}
}

작업 큐(queued jobs)를 처리하는 중에도 로그 컨텍스트를 공유해야 한다면, job middleware를 활용할 수 있습니다.

Writing to Specific Channels

애플리케이션의 기본 로그 채널이 아닌 특정 채널에 메시지를 기록하고 싶을 때도 있습니다. 이 경우 Log 파사드의 channel 메서드를 사용해 구성 파일에 정의된 원하는 채널을 선택할 수 있습니다:

use Illuminate\Support\Facades\Log;

Log::channel('slack')->info('Something happened!');

또한, 여러 채널로 구성된 즉석 로그 스택을 만들어 쓸 수도 있습니다. 이때 stack 메서드를 사용하세요:

Log::stack(['single', 'slack'])->info('Something happened!');

On-Demand Channels

애플리케이션의 logging 설정 파일에 채널 설정이 없어도, 런타임 실행 시 설정 배열을 전달해 즉석 채널을 생성할 수도 있습니다. 이는 Log 파사드의 build 메서드를 통해 가능합니다:

use Illuminate\Support\Facades\Log;

Log::build([
'driver' => 'single',
'path' => storage_path('logs/custom.log'),
])->info('Something happened!');

즉석 채널을 포함하는 스택을 만들고 싶다면, stack 메서드에 즉석 채널 인스턴스를 포함시키면 됩니다:

use Illuminate\Support\Facades\Log;

$channel = Log::build([
'driver' => 'single',
'path' => storage_path('logs/custom.log'),
]);

Log::stack(['slack', $channel])->info('Something happened!');

Monolog Channel Customization

Customizing Monolog for Channels

기존 채널에서 Monolog 구성을 완전히 제어해야 하는 경우도 있습니다. 예를 들어, Laravel 기본 single 채널에서 사용하는 Monolog FormatterInterface 구현을 직접 설정하고 싶을 때가 그렇습니다.

먼저 채널 설정에 tap 배열을 정의하세요. tap 배열에는 Monolog 인스턴스가 생성된 직후 사용자 지정할 클래스들이 명시됩니다. 이러한 클래스들은 원하는 위치에 만들어도 무방합니다:

'single' => [
'driver' => 'single',
'tap' => [App\Logging\CustomizeFormatter::class],
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],

이제 tap 옵션에서 지정한 클래스를 작성할 차례입니다. 클래스는 단 하나의 메서드 __invoke만 필요하며, 이 메서드는 Illuminate\Log\Logger 인스턴스를 받습니다. Illuminate\Log\Logger 인스턴스는 실제 내부 Monolog 인스턴스에 모든 메서드 호출을 프록시합니다:

<?php

namespace App\Logging;

use Illuminate\Log\Logger;
use Monolog\Formatter\LineFormatter;

class CustomizeFormatter
{
/**
* Customize the given logger instance.
*/
public function __invoke(Logger $logger): void
{
foreach ($logger->getHandlers() as $handler) {
$handler->setFormatter(new LineFormatter(
'[%datetime%] %channel%.%level_name%: %message% %context% %extra%'
));
}
}
}

"tap"에 지정한 모든 클래스는 service container가 자동으로 인스턴스 생성 및 의존성 주입을 처리합니다.

Creating Monolog Handler Channels

Monolog에는 다양한 available handlers가 있지만, Laravel은 이 중 일부에 대한 기본 채널만 제공합니다. 경우에 따라 Laravel에서 제공하지 않는 특정 Monolog 핸들러를 단순히 사용하는 맞춤 채널을 만들고 싶을 수 있습니다. 이런 경우 monolog 드라이버를 사용하는 것이 편리합니다.

monolog 드라이버를 사용할 때에는 handler 설정으로 어떤 핸들러를 인스턴스화할지 지정합니다. 핸들러가 필요로 하는 생성자 인자는 handler_with 옵션을 통해 지정할 수 있습니다:

'logentries' => [
'driver' => 'monolog',
'handler' => Monolog\Handler\SyslogUdpHandler::class,
'handler_with' => [
'host' => 'my.logentries.internal.datahubhost.company.com',
'port' => '10000',
],
],

Monolog Formatters

monolog 드라이버를 사용할 경우 기본 포매터는 Monolog LineFormatter입니다. 그러나 formatterformatter_with 설정을 활용해 핸들러에 전달할 포매터 타입을 사용자 지정할 수 있습니다:

'browser' => [
'driver' => 'monolog',
'handler' => Monolog\Handler\BrowserConsoleHandler::class,
'formatter' => Monolog\Formatter\HtmlFormatter::class,
'formatter_with' => [
'dateFormat' => 'Y-m-d',
],
],

만약 자체 포매터를 제공하는 핸들러라면, formatter 옵션에 default 값을 줄 수 있습니다:

'newrelic' => [
'driver' => 'monolog',
'handler' => Monolog\Handler\NewRelicHandler::class,
'formatter' => 'default',
],

Monolog Processors

Monolog은 로그 메시지가 기록되기 전 처리하는 프로세서도 지원합니다. 사용자가 직접 프로세서를 작성하거나, Monolog이 제공하는 existing processors offered by Monolog를 활용할 수 있습니다.

monolog 드라이버에서 프로세서를 사용자 지정하려면 채널 설정에 processors 배열을 추가합니다:

'memory' => [
'driver' => 'monolog',
'handler' => Monolog\Handler\StreamHandler::class,
'handler_with' => [
'stream' => 'php://stderr',
],
'processors' => [
// Simple syntax...
Monolog\Processor\MemoryUsageProcessor::class,

// With options...
[
'processor' => Monolog\Processor\PsrLogMessageProcessor::class,
'with' => ['removeUsedContextFields' => true],
],
],
],

Creating Custom Channels via Factories

Monolog 인스턴스 생성과 구성을 완전 직접 제어하는 맞춤 채널을 만들고 싶다면, config/logging.phpcustom 드라이버 유형을 지정할 수 있습니다. 그리고 via 옵션에 Monolog 인스턴스를 생성할 팩토리 클래스명을 적어야 합니다:

'channels' => [
'example-custom-channel' => [
'driver' => 'custom',
'via' => App\Logging\CreateCustomLogger::class,
],
],

custom 드라이버 채널을 설정한 후에는 Monolog 인스턴스를 생성하는 클래스를 작성합니다. 이 클래스는 __invoke 메서드 하나만 필요하며, 이 메서드는 채널 설정 배열을 받아 Monolog 로거 인스턴스를 반환하면 됩니다:

<?php

namespace App\Logging;

use Monolog\Logger;

class CreateCustomLogger
{
/**
* Create a custom Monolog instance.
*/
public function __invoke(array $config): Logger
{
return new Logger(/* ... */);
}
}

Tailing Log Messages Using Pail

문제가 있을 때 디버깅하거나 특정 종류의 오류를 실시간 모니터링할 때 애플리케이션 로그를 실시간으로 확인(테일)해야 할 때가 많습니다.

Laravel Pail은 Laravel 애플리케이션의 로그 파일을 명령줄에서 간편하게 조회할 수 있는 패키지입니다. 일반적인 tail 명령과 달리 Pail은 Sentry나 Flare 같은 다양한 로그 드라이버와도 작동하며, 유용한 필터링 기능을 제공합니다.

Pail 사용 예시 화면

Installation

Laravel Pail 사용을 위해서는 PCNTL PHP 확장 모듈이 필요합니다.

Composer를 이용해 개발 환경 전용 패키지로 Pail을 설치하세요:

composer require --dev laravel/pail

Usage

로그 테일링을 시작하려면 pail 명령어를 실행하세요:

php artisan pail

출력 내용을 좀 더 자세히 보고 누락 없이 표시하려면 -v 옵션을 사용합니다:

php artisan pail -v

예외 스택 트레이스까지 최대 상세하게 표시하려면 -vv 옵션을 사용하세요:

php artisan pail -vv

로그 테일링을 종료하려면 언제든지 Ctrl+C로 중단할 수 있습니다.

Filtering Logs

--filter

--filter 옵션을 사용하면 로그를 유형, 파일, 메시지, 스택 트레이스 내용 등으로 필터링할 수 있습니다:

php artisan pail --filter="QueryException"

--message

메시지 내용만으로 로그를 필터링하려면 --message 옵션을 사용합니다:

php artisan pail --message="User created"

--level

--level 옵션은 로그 log level로 필터링할 때 사용합니다:

php artisan pail --level=error

--user

특정 사용자가 인증된 상태에서 기록된 로그만 표시할 때는 사용자 ID를 --user 옵션에 넘깁니다:

php artisan pail --user=1