---
title: "Laravelのログ設定｜logging.phpのチャンネル設計と保持期間・マスキング"
url: "https://www.issoh.co.jp/tech/details/17156/"
published: 2026-08-30
updated: 2026-08-30
categories: ["Laravel"]
publisher: "株式会社一創"
---

# Laravelのログ設定｜logging.phpのチャンネル設計と保持期間・マスキング

Laravelのログは、設定を一度も触らないまま本番に出しても動いてしまいます。ファイルは勝手に増え、個人情報はそのまま書き込まれ、障害が起きたときに追いたい1リクエストだけが他の数万行に埋もれる状態です。この記事では2026年8月30日時点の公式ドキュメントと配布版スケルトン、それにフレームワーク本体の実装まで突き合わせて、`config/logging.php`が何を決めているのか、保持期間の設定キーが12系と13系でどう変わったのか、個人情報をどこで伏せるのかを順に見ていきます。メトリクスの収集と可視化は[LaravelでPrometheus＋Grafana監視を実装](https://www.issoh.co.jp/tech/details/2932/)、リクエスト単位の閲覧は[Laravel Telescopeの使い方](https://www.issoh.co.jp/tech/details/3386/)に譲り、ここでは標準の書き出し側だけを扱います。

## まとめ：既定値・保持期間・マスキングの判断

- 既定の書き出し先は`stack`チャンネルだが、その実体は`LOG_STACK`の既定値である`single`1本だけで、日次分割は最初から効いていない。
- チャンネルに書いた`level`は下限のしきい値で、`stack`にぶら下げた各チャンネルはそれぞれ独立に採否を判定する。
- 日次ローテーションの世代数は12系が`days`、13系が`max_files`。13系は`days`も後方互換で読むが、どちらも未設定なら7世代に落ちる。
- 個人情報を伏せる場所はMonologのprocessorが原則で、出力の形そのものを変えたいときだけ`tap`を併用する。
- ファイル出力を続けるか標準出力に寄せるかは、サーバ台数とログの保存要件で決まる。1台構成なら`daily`のままで足りる。

## logging.phpは既定チャンネルとドライバの2階層で決まる

設定ファイルを開くと最初に出てくるのが`default`キーで、ここに書かれた名前が`Log`ファサードの既定の書き出し先になります。その下の`channels`配列に、名前ごとの実体が並ぶという構造です。上の階層で「どれを使うか」、下の階層で「どう書くか」を決めていると考えると迷いません。

### 既定のLOG\_CHANNELはstackでその実体はsingle1本

13.x系の配布版スケルトンは、`default`に`env('LOG_CHANNEL', 'stack')`を置いています。`stack`は自分では何も書かず、`channels`に並べた名前へ処理を回すだけの取りまとめ役です。ここで見落とされやすいのが、その`channels`の中身が固定の配列ではなく環境変数から組み立てられている点になります。

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

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

配布版の`.env.example`には`LOG_STACK=single`が書かれています。つまり何も変えないまま本番に出すと、`storage/logs/laravel.log`という1ファイルに全部が追記され続ける状態が既定です。日次で分けたいなら`LOG_STACK=daily`に変えるだけで切り替わり、複数指定したいときは`LOG_STACK=daily,slack`のようにカンマ区切りで並べます。日本語の解説記事には「stackの既定はdaily」と書いてあるものが少なくありませんが、少なくとも12系と13系の配布版はどちらも`single`です。

### 10種のドライバは書き込み先と用途で3つの系統に分けて考える

13.xのドキュメントが挙げるドライバは`custom`・`daily`・`monthly`・`errorlog`・`monolog`・`papertrail`・`single`・`slack`・`stack`・`syslog`の10種です。数だけ見ると多く感じますが、書き込み先で束ねると3系統に収まります。ディスクのファイルに書く`single`・`daily`・`monthly`、OSやプロセスの標準機構に渡す`syslog`・`errorlog`、そして外部サービスへ送る`slack`・`papertrail`の3つです。残る`monolog`と`custom`は、Monolog側の任意のハンドラを持ち込むための入口にあたります。

配布版のスケルトンには、この10種のほかに`stderr`・`null`・`emergency`という3つのチャンネル定義も同梱されています。`stderr`は`monolog`ドライバで`StreamHandler`を標準エラー出力に向けたもので、コンテナ運用のときにそのまま使えます。`null`は捨て先で、`LOG_DEPRECATIONS_CHANNEL`の既定値がこれになっているため、非推奨警告は初期状態では記録されません。

### 単発の書き込み先はLog::channelとLog::buildで変える

既定チャンネル以外に書きたい場面では、その場で行き先を指定できます。設定ファイルに定義済みの名前を使うなら`Log::channel()`、その場限りの組み合わせなら`Log::stack()`、設定ファイルに存在しない構成を実行時に組み立てるなら`Log::build()`という使い分けです。

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

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

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

バッチ処理の結果だけを別ファイルに分けたい、といった要件はこの`Log::build()`で足ります。専用チャンネルを増やすほどでもない一時的な切り分けに向きます。

## ログレベルの割り当てとstackの積み方で本番環境の通知量が決まる

どのチャンネルにも書ける`level`キーは、そのチャンネルが受け付ける下限を指します。ここを雑に決めると、通知チャンネルが鳴りやまないか、逆に肝心の障害が届かないかのどちらかに振れます。

### RFC 5424の8段階は本番運用では3層に丸めて割り当てる

Monologが持つレベルはRFC 5424に沿った8段階で、深刻な順に`emergency`・`alert`・`critical`・`error`・`warning`・`notice`・`info`・`debug`と並びます。8段階すべてに別々の意味を持たせて運用しきるのは難しく、実務では3層に丸めるほうが機能します。人を起こす層を`error`以上、あとで追える形で残す層を`info`以上、開発中しか見ない層を`debug`とする割り当てです。

この3層をチャンネルに写すと、通知先は`level`を`error`に、保存先は`info`に、ローカル用は`debug`に設定することになります。配布版が`slack`チャンネルの`level`を`critical`にしているのは通知を絞る意図ですが、`critical`まで上げると多くのアプリで何も鳴りません。未捕捉の例外はハンドラ経由で`error`として記録されるため、通知の入口は`error`に置くほうが実態に合います。

### levelは下限値でstack配下の各チャンネルが独立に採否を判定する

`stack`にぶら下げたチャンネルは、渡ってきた1件のレコードを各自で判定します。親が通したから子も書く、という連鎖ではありません。`syslog`を`debug`、`slack`を`error`にしておけば、`Log::debug()`はsyslogにだけ残り、`Log::emergency()`は両方に届きます。1本の呼び出しで宛先を出し分けられるのがstackの利点です。

```
'stack' => [
    'driver' => 'stack',
    'channels' => ['daily', 'slack'],
    'ignore_exceptions' => false,
],

'daily' => [
    'driver' => 'daily',
    'path' => storage_path('logs/laravel.log'),
    'level' => 'info',
    'max_files' => 30,
],

'slack' => [
    'driver' => 'slack',
    'url' => env('LOG_SLACK_WEBHOOK_URL'),
    'level' => 'error',
],
```

### ignore\_exceptionsとbubbleの既定値が事故を生む場面

配布版の`stack`は`ignore_exceptions`を`false`にしています。ログの書き込み中に例外が出たとき、それを握りつぶさない設定です。Slackのwebhookが落ちたりログディレクトリの権限が足りなかったりすると、その例外がアプリ側へ跳ね返り、ログを出そうとして本体が500を返します。外部サービスをstackに入れるなら、ここを`true`にして書き込み失敗を切り離すか、外部への送信そのものを非同期に逃がすかを先に決めておきます。

`single`・`daily`・`monthly`には`bubble`・`locking`・`permission`の3つの任意オプションがあり、既定はそれぞれ`true`・`false`・`0644`です。`bubble`が`true`のままだと、そのチャンネルが処理したレコードは後続のハンドラにも回ります。同じ内容を2箇所に書いてしまっているときは、まずここを疑ってください。

## 日次ローテーションの設定キーはLaravel 12系と13系で名前が違う

ログファイルの世代管理は、この記事で扱う中でもっとも版差が出た部分です。アップグレード時に古い`config/logging.php`をそのまま持ち込むと、意図と違う世代数で回り続けます。

### 12系はdaysで13系はmax\_filesになりmonthlyが増えた

12.x系の配布版は`daily`チャンネルに`days`キーを置き、`LOG_DAILY_DAYS`の既定値14を与えていました。13.x系ではこれが`max_files`に変わり、あわせて月次で分割する`monthly`チャンネルが追加されています。環境変数名は`LOG_DAILY_DAYS`のまま据え置かれているので、`.env`だけ見ていると差分に気づけません。

フレームワーク本体の実装まで下りると、13.xの`LogManager`にある`createDailyDriver()`は世代数を`max_files`、`days`、直値の7という順で解決しています。`days`も後方互換で読まれるため、12系の設定を持ち込んでも保持期間が消えるわけではありません。ただし両方のキーが無い場合のフォールバックは7で、配布版の14はあくまで設定ファイル側が与えている値です。チャンネル定義を自作して`path`と`level`だけ書いたときに、意図せず7世代で切られるのはこの分岐が理由になります。

### Laravelの版ごとに保持期間の設定キーと既定値を突き合わせる

| 項目           | Laravel 12.x     | Laravel 13.x       |
| ------------ | ---------------- | ------------------ |
| dailyの世代数キー  | days             | max\_files（daysも可） |
| 配布版configの既定 | 14               | 14                 |
| 環境変数         | LOG\_DAILY\_DAYS | LOG\_DAILY\_DAYS   |
| キー未設定時の値     | 7                | 7                  |
| monthlyチャンネル | なし               | あり（既定3世代）          |
| ドライバ総数       | 9種               | 10種                |

実機で確かめるなら、`php artisan tinker`から`config('logging.channels.daily')`を叩いて解決後の配列を見るのが早いです。設定をキャッシュ済みの本番でも、その値がそのまま返ります。

### permissionとlockingはコンテナ運用で先に決めておく

ファイル権限の既定値`0644`は、PHP-FPMとキューワーカーを別ユーザーで動かしている構成でつまずきます。先に書き込んだ側の所有でファイルが作られ、もう片方が同じファイルを開けません。`permission`を`0664`に上げてグループを揃えるか、そもそも両者のログを分けるかのどちらかで解決します。キューワーカー側のログ運用は[Laravel Queueの使い方](https://www.issoh.co.jp/tech/details/17154/)の常駐の前提とあわせて考えてください。

`locking`は書き込み前にファイルロックを取る設定で、既定は`false`です。複数プロセスが同一ファイルに追記する構成では行が混ざる可能性があるため`true`が候補になりますが、ロック待ちの分だけ書き込みが遅くなります。プロセスごとにファイルを分けられるなら、そちらのほうが素直な解決です。

## 外部への転送とコンテキスト付与で障害時に追跡可能なログを作る

ログを1行ずつ眺めて障害を追える規模は、案件が育つと早々に超えます。どこへ送るかと、1行に何を持たせるかの2点を設計しておくと、あとから収集基盤を足すときの手戻りが減ります。

### slackとpapertrailとsyslogは前提条件がそれぞれ違う

`slack`ドライバはIncoming Webhookの`url`が必須で、`LOG_SLACK_WEBHOOK_URL`から与えます。`papertrail`は`host`と`port`が必要で、配布版では`SyslogUdpHandler`をTLS付きの接続文字列で叩く形です。`syslog`はOS側のsyslogに投げるだけなので追加の資格情報は要りませんが、コンテナ内で動かすとホスト側にsyslogデーモンが居ないことがあります。

DatadogやNew Relicのように専用ドライバが無い送り先は、`monolog`ドライバで該当のMonologハンドラを直接指定します。`handler`にクラス名、`handler_with`にコンストラクタ引数を渡す形式です。ハンドラが自前のフォーマッタを持つ場合は`formatter`に`default`と書いて、Laravel側の`LineFormatter`の差し込みを止めます。

### withContextとshareContextでリクエストIDを全行に通す

1リクエストが出した数十行を1本の糸で束ねるには、共通のIDを全行に載せます。ミドルウェアで`Log::withContext()`を呼べば、それ以降そのチャンネルに書かれる全レコードにキーが付きます。チャンネルをまたいで共有したいなら`Log::shareContext()`のほうです。

```
public function handle(Request $request, Closure $next): Response
{
    $requestId = (string) Str::uuid();

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

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

    return $response;
}
```

レスポンスヘッダにも同じIDを返しておくと、利用者から「この画面でエラーが出た」と連絡を受けたときに、ヘッダの値だけでログを引ける状態です。キュー経由の処理へIDを引き継ぐ場合は、ジョブミドルウェアで同じことをします。

### 標準エラー出力へJSONで吐いてログ収集と保存は基盤側に任せる

コンテナで動かすなら、ファイルに書かず標準エラー出力に流して収集はプラットフォームに任せる形が扱いやすくなります。配布版の`stderr`チャンネルには`LOG_STDERR_FORMATTER`を読む`formatter`が用意されているので、環境変数にMonologの`JsonFormatter`を指定するだけで1行1JSONになります。設定ファイルを触らずに出力形式を切り替えられる作りです。

この構成にすると、ローテーションもディスク容量も考えずに済む代わりに、収集基盤が落ちている間のログは失われます。監査目的で保存義務があるログをここに混ぜないことだけ気をつけてください。

## 個人情報のマスキングはtapとprocessorの2箇所で作る

フォームの入力値をそのまま`Log::info()`に渡す実装は、レビューで見つけ次第止めるべきものです。とはいえ全箇所を人の目で防ぎ切るのは現実的ではないので、書き出しの直前で機械的に伏せる層を1枚入れておきます。

### tapでフォーマッタを差し替えてチャンネルごとの出力形式を握る

チャンネル設定に`tap`配列を書くと、Monologのインスタンス生成直後に指定クラスの`__invoke()`が呼ばれます。ここでハンドラのフォーマッタを差し替えれば、そのチャンネルの出力形式を丸ごと握れます。

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

ただし`tap`でやるのは形式の変更までに留めます。値そのものを書き換える処理をフォーマッタに埋め込むと、フォーマッタを差し替えた瞬間にマスキングも消えるという壊れ方をします。

### processorでcontextを走査して該当キーを伏せる

値を伏せる場所はprocessorです。Monolog 3系のprocessorは`LogRecord`を受け取って`LogRecord`を返す形になっており、`with()`で`context`だけを差し替えられます。伏せるキーの一覧を定数で持っておき、再帰的に走査するのが定石です。

```
class MaskSensitive
{
    private const KEYS = ['password', 'token', 'tel', 'email'];

    public function __invoke(LogRecord $record): LogRecord
    {
        return $record->with(context: $this->mask($record->context));
    }

    private function mask(array $rows): array
    {
        foreach ($rows as $key => $value) {
            if (is_array($value)) {
                $rows[$key] = $this->mask($value);
            } elseif (in_array($key, self::KEYS, true)) {
                $rows[$key] = '[FILTERED]';
            }
        }

        return $rows;
    }
}
```

設定側では`monolog`ドライバのチャンネルなら`processors`キーにクラス名を並べます。`daily`のようにドライバ側が`processors`を受けないチャンネルでは、`tap`の中から`pushProcessor()`を呼んで差し込みます。伏せ字にした値は復元できないので、調査に要る識別子まで潰していないかは実機の出力で確かめてください。

### マスキングしても個人情報が漏れる3つの経路を処理前に確実に塞ぐ

processorが見るのは`message`と`context`だけです。そこを通らない経路が3つあります。1つ目は例外のスタックトレースで、メソッドの引数に渡ったパスワードが文字列として乗る経路です。2つ目は`DB::listen()`やクエリログで、バインド値が配列のまま出ます。3つ目はHTTPクライアントのリクエストボディを丸ごと記録している箇所です。

いずれも「ログに出す前に落とす」しか手がありません。例外側は例外ハンドラの`context()`で出す情報を明示的に絞り、クエリログは本番で止め、HTTPクライアントはボディではなくステータスコードと所要時間だけを残す方針にします。`APP_DEBUG`が`true`のまま本番に出ていると、例外画面に環境変数がまるごと並ぶ点もあわせて確認してください。

## 受託案件でログ設定を決める採用条件と見送る構成の判断基準を整理する

ここまでの設定項目のうち、案件の最初に決めておくべきものは多くありません。台数と保存要件の2つが決まれば、残りはほぼ機械的に埋まります。

### 採用条件はサーバ台数とログ保存要件の2軸で構成を切り分けて判断する

1台構成で保存要件が「障害調査に使える程度」なら、`LOG_STACK`を`daily`にして`max_files`を30前後に置き、通知だけSlackへ回す構成で足ります。追加のミドルウェアは要りません。2台以上に増えた時点でファイルは各サーバに散るため、標準エラー出力へJSONで出して収集基盤に寄せるか、`syslog`で1箇所に集約するかの判断が必要になります。ここはセッションの保存先を決めるときと同じ分岐で、[Laravelのセッション管理](https://www.issoh.co.jp/tech/details/17152/)と同じタイミングで決めると構成が揃います。

保存要件に監査や法定保存が絡む場合は、別扱いにします。アプリのログと同じ経路に乗せると保持期間の変更に巻き込まれるので、専用チャンネルを切って保存先も分けるほうが後々もめません。

### 見送るのはsingleのまま外部転送だけを先に足してしまう構成です

逆に採らないほうがよい構成もあります。`single`のまま外部転送だけ足す形は、ファイルが無制限に膨らんでディスクを埋めます。数百MBを超えたログファイルは末尾を読むだけでも重く、障害の最中に開けません。日次分割を入れずに外部転送を先に足すのは順序が逆です。

もう1つ見送るのは、全チャンネルの`level`を`debug`のまま本番に出す構成です。SQLもキャッシュのヒットも全部出るため、量が増えるだけでなく個人情報の混入経路も比例して増えます。`debug`はローカルと検証環境までに留めてください。

### 運用に組み込むのは保持期間とログレベルと書き込み監視という3点

設定を書いて終わりにせず、運用側に3つ組み込みます。保持期間はディスク使用量の推移を見て四半期ごとに見直すこと、レベルは通知が鳴りすぎたときに閾値を上げるのではなく発生元を潰すこと、そして書き込み失敗そのものを監視することの3点です。3つ目は見落とされがちで、権限やディスク満杯でログが書けなくなっても、アプリは静かに動き続けます。ログの最終更新時刻を監視対象に入れておくと気づけます。

これらの見直しは公開直後より、運用が半年ほど回ってからのほうが判断材料が揃います。自社では[保守運用／内製化支援](https://www.issoh.co.jp/service/system/maintenance/)として、ログの保持期間や通知の閾値をアプリの実際の挙動に合わせて調整するところまで継続的に引き受けています。

## よくある質問

### ログの出力先を変えたのに反映されないのはなぜですか？

本番で設定をキャッシュしている場合、`config/logging.php`や`.env`を書き換えてもキャッシュ済みの値が使われ続けます。`php artisan config:clear`を実行するか、デプロイ手順にキャッシュの再生成を組み込んでください。キャッシュ後は`env()`がnullを返す点にも注意します。

### ログファイルが増え続けるのは放置してよいですか？

放置は勧めません。`single`は1ファイルに追記し続け、`daily`も`max_files`を明示しなければフレームワーク側のフォールバックである7世代で切られます。想定と違う世代数で回っていることがあるので、実機で`storage/logs`の中身を数えて確認してください。

### 本番でログをリアルタイムに見る方法はありますか？

`laravel/pail`を開発依存として入れると`php artisan pail`で追尾できます。PCNTL拡張が前提で、2026年8月時点の最新はv1.2.7です。レベルや利用者IDで絞り込むオプションがあるため、末尾を読む従来のコマンドと検索を組み合わせるより速く目的の行に届きます。

### 非推奨警告のログはどこで有効にしますか？

設定ファイルの`deprecations`キーで指定します。既定は`LOG_DEPRECATIONS_CHANNEL`が`null`チャンネルを向いているため記録されません。メジャーバージョンの移行前に`daily`などへ向け、`LOG_DEPRECATIONS_TRACE`を`true`にすると発生箇所まで追えます。

### スタックにSlackを足したら本体が落ちたのですが？

`stack`の`ignore_exceptions`が`false`だと、webhookへの送信失敗がアプリ側へ例外として伝播します。`true`に変えて書き込み失敗を切り離すか、通知そのものをキュー経由の非同期に逃がしてください。前者は失敗に気づけなくなるため、送信の成否は別途監視します。

## 関連記事

- [LaravelでPrometheus＋Grafana監視を実装｜メトリクス収集・可視化・アラート設定](https://www.issoh.co.jp/tech/details/2932/)：数値の収集と可視化、発報の設計はこちらです。
- [Laravel Telescopeの使い方｜設定と本番運用の判断基準【2026年版】](https://www.issoh.co.jp/tech/details/3386/)：リクエスト単位で中身を眺めるための道具です。
- [Laravel Queueの使い方｜ジョブ定義からworker常駐と失敗ジョブ運用まで](https://www.issoh.co.jp/tech/details/17154/)：ワーカー側のログ運用の前提を扱っています。
- [Laravelのセッション管理｜ドライバ選定とCSRF・複数台構成の共有設計](https://www.issoh.co.jp/tech/details/17152/)：複数台構成で決めることが共通します。

---

出典: [Laravelのログ設定｜logging.phpのチャンネル設計と保持期間・マスキング](<https://www.issoh.co.jp/tech/details/17156/>)（株式会社一創）
