【PHP】Laravelでログを設定・出力する方法(チャンネル・stack・daily)

対象バージョン: Laravel 13.x / PHP 8.3+(laravel/framework v13.39.0 で動作確認)

Laravel では、ログの出力先や形式を「チャンネル」として config/logging.php で定義します。この記事は、新規アプリの既定である stack と single / daily の違いを整理したい方や、Log ファサードでメッセージとコンテキストを書きたい方向けです。チャンネル設定、レベル、コンテキスト、Laravel Pail による監視、Feature テストまでを順に説明します。例外をログへ報告する仕組みそのものは【PHP】Laravelでエラー処理(例外の報告・描画とカスタムエラーページ)を実装する方法で扱っており、この記事はチャンネル設定と通常のログ出力に焦点を当てます。

この記事で分かること

  • 既定の LOG_CHANNEL / LOG_STACK / LOG_LEVEL の意味
  • stack・single・daily・monthly チャンネルの役割とファイル名
  • 日次ログの保持日数(max_files / LOG_DAILY_DAYS)
  • Log ファサードでの出力、チャンネル指定、オンデマンドの stack / build
  • チャネルローカルな withContext と共有の shareContext / withoutContext
  • Monolog の tap によるフォーマッタ変更
  • Laravel Pail でのログ監視
  • Log::fake が無い前提での Feature テスト

前提

Laravel 13.x は PHP 8.3 以上が必要です。動作確認は、composer create-project laravel/laravel:^13.0 で新規作成したアプリ(laravel/laravel v13.11.0、laravel/framework v13.39.0、PHP 8.4.27、monolog/monolog 3.12.1、laravel/pail v1.2.7)で行いました。プロジェクトの作成方法は【PHP】Laravel入門:インストールから簡単なサンプルアプリケーションの作成を参照してください。

Slack・Papertrail・syslog など外部サービス向けドライバーの詳細は、この記事の対象外です。

既定のログ設定を確認する

新規アプリの .env(.env.example と同じキー)には、次のログ関連の行があります。

LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug

config/logging.php では、既定チャンネルが env('LOG_CHANNEL', 'stack') です。stack チャンネルは env('LOG_STACK', 'single') をカンマ区切りで分解した子チャンネルへ書き込みます。そのため、上記の既定では Log::info(...) は実質 single(storage/logs/laravel.log)へ出力されます。

スケルトン同梱の config/logging.php 全体は次のとおりです(コメントは省略していません)。

<?php

use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
use Monolog\Processor\PsrLogMessageProcessor;

return [

    /*
    |--------------------------------------------------------------------------
    | Default Log Channel
    |--------------------------------------------------------------------------
    |
    | This option defines the default log channel that is utilized to write
    | messages to your logs. The value provided here should match one of
    | the channels present in the list of "channels" configured below.
    |
    */

    'default' => env('LOG_CHANNEL', 'stack'),

    /*
    |--------------------------------------------------------------------------
    | Deprecations Log Channel
    |--------------------------------------------------------------------------
    |
    | This option controls the log channel that should be used to log warnings
    | regarding deprecated PHP and library features. This allows you to get
    | your application ready for upcoming major versions of dependencies.
    |
    */

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

    /*
    |--------------------------------------------------------------------------
    | Log Channels
    |--------------------------------------------------------------------------
    |
    | Here you may configure the log channels for your application. Laravel
    | utilizes the Monolog PHP logging library, which includes a variety
    | of powerful log handlers and formatters that you're free to use.
    |
    | Available drivers: "single", "daily", "monthly", "slack", "syslog",
    |                    "errorlog", "monolog", "custom", "stack"
    |
    */

    'channels' => [

        'stack' => [
            'driver' => 'stack',
            'channels' => explode(',', (string) env('LOG_STACK', 'single')),
            'ignore_exceptions' => false,
        ],

        'single' => [
            'driver' => 'single',
            'path' => storage_path('logs/laravel.log'),
            'level' => env('LOG_LEVEL', 'debug'),
            'replace_placeholders' => true,
        ],

        'daily' => [
            'driver' => 'daily',
            'path' => storage_path('logs/laravel.log'),
            'level' => env('LOG_LEVEL', 'debug'),
            'max_files' => env('LOG_DAILY_DAYS', 14),
            'replace_placeholders' => true,
        ],

        'monthly' => [
            'driver' => 'monthly',
            'path' => storage_path('logs/laravel.log'),
            'level' => env('LOG_LEVEL', 'debug'),
            'max_files' => 3,
            'replace_placeholders' => true,
        ],

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

        'papertrail' => [
            'driver' => 'monolog',
            'level' => env('LOG_LEVEL', 'debug'),
            'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
            'handler_with' => [
                'host' => env('PAPERTRAIL_URL'),
                'port' => env('PAPERTRAIL_PORT'),
                'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
            ],
            'processors' => [PsrLogMessageProcessor::class],
        ],

        'stderr' => [
            'driver' => 'monolog',
            'level' => env('LOG_LEVEL', 'debug'),
            'handler' => StreamHandler::class,
            'handler_with' => [
                'stream' => 'php://stderr',
            ],
            'formatter' => env('LOG_STDERR_FORMATTER'),
            'processors' => [PsrLogMessageProcessor::class],
        ],

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

        'errorlog' => [
            'driver' => 'errorlog',
            'level' => env('LOG_LEVEL', 'debug'),
            'replace_placeholders' => true,
        ],

        'null' => [
            'driver' => 'monolog',
            'handler' => NullHandler::class,
        ],

        'emergency' => [
            'path' => storage_path('logs/laravel.log'),
        ],

    ],

];

このファイルにある slack / papertrail / syslog などの定義は、必要になったときに使うためのものです。以降では stack・single・daily・monthly を中心に説明します。

利用できるドライバー

Laravel 13.x のドキュメントに載っているチャンネルドライバーは、stack、single、daily、monthly、slack、syslog、errorlog、monolog、custom、null です(papertrail は monolog ハンドラ経由)。monthly は 13.x で第一級のドライバーとして追加されており、月次でファイルをローテーションします。

ドライバー 概要(この記事での扱い)
stack 複数チャンネルへまとめて書く
single 1 ファイル(laravel.log)へ追記
daily 日次ファイル(laravel-YYYY-MM-DD.log)
monthly 月次ファイル(laravel-YYYY-MM.log)

stack チャンネル

stack は子チャンネルのリストを持ちます。既定では ignore_exceptions が false です。true にすると、失敗したハンドラが他のハンドラを止めないように Monolog の WhatFailureGroupHandler で包みます。

実行時に一時的なスタックを組む場合は、Log::stack を使います。

use Illuminate\Support\Facades\Log;

Log::stack(['single', 'daily'])->info(
    'VERIFY_STACK_EXPLICIT single+daily',
    ['tag' => 'stack']
);

この呼び出しでは、同じメッセージが storage/logs/laravel.log(single)と当日の storage/logs/laravel-YYYY-MM-DD.log(daily)の両方に書き込まれます。

single / daily / monthly とファイル名

  • single … path で指定したファイル(既定は storage/logs/laravel.log)へ、日付サフィックスなしで追記します。
  • daily … Monolog の日次ローテーションにより、{filename}-YYYY-MM-DD.log 形式になります。既定の path が storage/logs/laravel.log のとき、例として storage/logs/laravel-2026-11-03.log が作られます。
  • monthly … 月次ローテーションで laravel-YYYY-MM.log(例: laravel-2026-11.log)になります。保持数は設定の max_files(既定 3)です。days キーのフォールバックはありません。

出力例(single)は次の形式です。

[2026-11-03 01:13:46] local.INFO: VERIFY_SINGLE info message {"tag":"single"}

日次ログの保持日数(max_files / LOG_DAILY_DAYS)

日次・月次の保持は、設定キー max_files で指定します。スケルトンの daily チャンネルは次のとおりです。

'daily' => [
    'driver' => 'daily',
    'path' => storage_path('logs/laravel.log'),
    'level' => env('LOG_LEVEL', 'debug'),
    'max_files' => env('LOG_DAILY_DAYS', 14),
    'replace_placeholders' => true,
],

.env に LOG_DAILY_DAYS を書いていない場合でも、上記の第二引数により保持日数は 14 です。変更する場合は .env に次を追加します(既存の LOG_* 行と重複させないでください)。

LOG_DAILY_DAYS=14

フレームワーク実装では、daily ドライバーに限りレガシーな days キーも受け付けます(max_files が無ければ days、どちらも無ければコード上の既定 7)。両方ある場合は max_files が優先されます。新しく書く設定では max_files / LOG_DAILY_DAYS を使い、古い設定で days が残っていても動く、と覚えておけば十分です。スケルトン既定の 14 を、コード内部のフォールバック 7 と混同しないでください。

ログを書き込む(Log ファサード)

Illuminate\Support\Facades\Log で、RFC 5424 の 8 レベル(emergency / alert / critical / error / warning / notice / info / debug)を書けます。チャンネル設定の level は最小重大度のしきい値で、それより低いメッセージは出力されません。

use Illuminate\Support\Facades\Log;

Log::info('VERIFY_DEFAULT_STACK via Log::info', ['tag' => 'default']);
Log::channel('single')->info('VERIFY_SINGLE info message', ['tag' => 'single']);
Log::channel('daily')->info('VERIFY_DAILY info message', ['tag' => 'daily']);
Log::channel('monthly')->info('VERIFY_MONTHLY info message', ['tag' => 'monthly']);

設定ファイルに無いチャンネルをその場で作る場合は Log::build を使います。

use Illuminate\Support\Facades\Log;

$logger = Log::build([
    'driver' => 'single',
    'path' => storage_path('logs/ondemand.log'),
    'level' => 'debug',
]);
$logger->info('VERIFY_BUILD on-demand channel');

レベルしきい値の例です。level を warning にしたチャンネルでは、debug / info はファイルに残らず、warning / error だけが残ります。

$warnOnly = Log::build([
    'driver' => 'single',
    'path' => storage_path('logs/verify-warning-only.log'),
    'level' => 'warning',
]);
$warnOnly->debug('VERIFY_LEVEL should_NOT_appear');
$warnOnly->info('VERIFY_LEVEL should_NOT_appear_info');
$warnOnly->warning('VERIFY_LEVEL should_appear_warning');
$warnOnly->error('VERIFY_LEVEL should_appear_error');

ジョブやキュー処理の中で Log::info / Log::error を書く例は、【PHP】Laravelでキューとジョブを実装する方法とその利用シーンにもあります。スケジュール実行のログ運用を考える場合は、【PHP】Laravelでタスクスケジューリングを実装する方法も参照してください。

コンテキスト情報を付ける

呼び出しごとの第 2 引数配列は、ログ行のメッセージ直後の JSON オブジェクト(context)に入ります。

同じチャンネルインスタンスで以降の行にも付けたい場合は withContext を使います(そのチャンネルにだけ効きます)。

use Illuminate\Support\Facades\Log;

Log::channel('single')->withContext(['request-id' => 'ctx-with-001']);
Log::channel('single')->info('VERIFY_WITH_CONTEXT after withContext');
Log::channel('daily')->info('VERIFY_WITH_CONTEXT on daily (should lack request-id from single)');

すでに解決済みのチャンネルを含め、以降に共通で付けたい場合は shareContext です。

Log::shareContext(['trace-id' => 'shared-trace-999']);
Log::channel('single')->info('VERIFY_SHARE_CONTEXT on single');
Log::channel('daily')->info('VERIFY_SHARE_CONTEXT on daily');

Log::flushSharedContext() は、これから解決するチャンネル向けの共有コンテキストだけを消します。すでにチャンネルへ適用済みのコンテキストは残り続けます。チャンネル側を消すときは Log::withoutContext()(キー指定も可)を使います。

別系統として、Illuminate\Support\Facades\Context で追加したデータは ContextLogProcessor によりログの Monolog extra 側へ自動付与されます。Log::withContext / shareContext とは別物で、既定の LineFormatter では context と extra が別々の JSON オブジェクトとして並びます。

Monolog を tap でカスタマイズする

チャンネル設定の tap に invokable クラスを並べると、生成後の Illuminate\Log\Logger を受け取れます。次は、行頭に [TAP] を付けるフォーマッタ例です。

app/Logging/CustomizeFormatter.php:

<?php

namespace App\Logging;

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

class CustomizeFormatter
{
    public function __invoke(Logger $logger): void
    {
        foreach ($logger->getHandlers() as $handler) {
            $handler->setFormatter(new LineFormatter(
                '[TAP]%datetime% %level_name%: %message% %context%'.PHP_EOL,
                'Y-m-d H:i:s',
                true,
                true
            ));
        }
    }
}

config/logging.php のチャンネル定義へ、次のキーを追加します(既存キーと重複させないでください)。

'tap' => [App\Logging\CustomizeFormatter::class],

出力例:

[TAP]2026-11-03 01:14:05 INFO: VERIFY_TAP message {"k":"v"}

Laravel Pail でログを監視する

Laravel Pail(laravel/pail)は、新規アプリでは require-dev に含まれます(確認環境では v1.2.7)。別途入れる場合のドキュメント上のコマンドは次です。

composer require --dev laravel/pail

ドキュメントでは PCNTL 拡張が必要とされています。基本の起動は php artisan pail です。動作確認では、次のようにレベルで絞り込みました。

php artisan pail --level=error

ドキュメントおよび --help には、--filter / --message / --user(--auth は --user のエイリアス)などのオプションも載っています。この記事では --level 以外の出力結果までは検証していません。

Feature テストでログを確かめる

laravel/framework v13.39.0 の Log ファサードに Log::fake() はありません。一時ファイルへ書く single チャンネルを Log::build で作り、内容を読み取って断言できます。Feature テストの基本は【PHP】Laravelでテストを実装する方法を参照してください。

tests/Feature/LoggingChannelTest.php の例です。

<?php

namespace Tests\Feature;

use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Tests\TestCase;

class LoggingChannelTest extends TestCase
{
    public function test_single_channel_writes_expected_line(): void
    {
        $path = storage_path('logs/test-assert.log');
        if (File::exists($path)) {
            File::delete($path);
        }

        $logger = Log::build([
            'driver' => 'single',
            'path' => $path,
            'level' => 'debug',
        ]);
        $logger->info('TEST_ASSERT_LINE', ['foo' => 'bar']);

        $this->assertFileExists($path);
        $contents = File::get($path);
        $this->assertStringContainsString('TEST_ASSERT_LINE', $contents);
        $this->assertStringContainsString('"foo":"bar"', $contents);
    }

    public function test_log_facade_has_no_fake_method(): void
    {
        $this->assertFalse(
            method_exists(\Illuminate\Support\Facades\Log::class, 'fake'),
            'Log::fake should not exist on this framework version'
        );
        $ref = new \ReflectionClass(\Illuminate\Support\Facades\Log::class);
        $this->assertFalse($ref->hasMethod('fake'));
    }
}

よくあるつまずき

  • Log::info の行き先が分からない … 既定は LOG_CHANNEL=stack かつ LOG_STACK=single なので、storage/logs/laravel.log を見てください。daily に切り替えている場合は日付付きファイルです。
  • 保持日数を 7 だと思い込む … スケルトンは LOG_DAILY_DAYS 未設定でも 14 です。フレームワークが両方のキーを欠いたときだけ使うコード上の 7 とは別です。
  • flushSharedContext で消えたと思ったコンテキストが残る … 共有バッファだけが対象です。解決済みチャンネルからは withoutContext で消してください。
  • Log::fake() が無い … 13.x では用意されていません。一時ファイルチャンネルで断言してください。
  • 例外の報告設定をこの記事で探してしまう … withExceptions や report コールバックはエラー処理の記事側です。

まとめ

  • 新規アプリの既定は LOG_CHANNEL=stack → LOG_STACK=single → storage/logs/laravel.log です。
  • 日次ローテーションは daily、保持は max_files / LOG_DAILY_DAYS(スケルトン既定 14)で設定します。
  • Log::channel / Log::stack / Log::build で出力先を切り替えられます。
  • コンテキストはチャネルローカルの withContext と共有の shareContext を区別し、消し方(flushSharedContext と withoutContext)にも注意してください。
  • 監視には Pail、テストには一時ファイルチャンネルが使えます。

関連記事

参考リンク

コメントする