ホーム>Laravel>【Laravel】ログの出し方と設定(Log::info・logger()・daily・stack・ログの場所)
Laravel

【Laravel】ログの出し方と設定(Log::info・logger()・daily・stack・ログの場所)

いつもご利用ありがとうございます。
この記事にはアフィリエイト広告を含む広告が掲載されており、その広告費によって運営しています。
Amazonのアソシエイトとして、ララテックは適格販売により収入を得ています。

Laravelのログの出し方(Logファサード・logger()・info())と、ログの場所・レベル・コンテキスト・dailyとstackの設定を、Laravel 13で実際にログを書き出して確かめました。日付がずれる原因や、Permission deniedでログが書けないときの直し方も載せています。

Laravel で「この処理がどこまで進んだのか知りたい」「本番で何が起きたのか後から見たい」というときに使うのがログです。

dd() は処理をその場で止めてしまうので、本番やバッチ処理では使えません。

ログなら処理を止めずに、ファイルへ書き残せます。

この記事では、ログの出し方と設定を、実際に Laravel 13 でログを書き出して確かめました!

確かめた環境は、Docker の php:8.4-cli(PHP 8.4.27)・Composer 2.11.2・Laravel 13.42.0・Monolog 3.12.1 です(2026年11月14日)。

composer create-project laravel/laravel で作ったままのプロジェクトで試しています。

ログはどこに出る?storage/logs/laravel.log

何も設定を変えなければ、ログは storage/logs/laravel.log に書き込まれます。

作ったばかりのプロジェクトの .env には、ログの設定が4行入っていました。

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

LOG_CHANNEL=stack は「stack というチャンネルを使う」、LOG_STACK=single は「stack の中身は single チャンネルだけ」という意味です。

single チャンネルの書き込み先が storage/logs/laravel.log なので、結果としてここに出ます。

チャンネルの中身は config/logging.php に書かれています。

'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,
],

ログが出ているかを見るだけなら、ターミナルで tail -f storage/logs/laravel.log を開いておくのが手軽です。

Log ファサードでログを書く

ログを書くには、Illuminate\Support\Facades\Log を使います。

use Illuminate\Support\Facades\Log;

Log::info('ユーザーが登録しました');
Log::debug('デバッグ用のメッセージ');
Log::warning('在庫が少なくなっています');
Log::error('決済に失敗しました');

実行すると、laravel.log にこう書かれました。

[2026-11-13 19:06:26] local.INFO: ユーザーが登録しました  
[2026-11-13 19:06:26] local.DEBUG: デバッグ用のメッセージ  
[2026-11-13 19:06:26] local.WARNING: 在庫が少なくなっています  
[2026-11-13 19:06:26] local.ERROR: 決済に失敗しました  

1行は「[日時] 環境名.レベル: メッセージ」の形です。

local は .env の APP_ENV の値で、本番なら production になります。

日時が日本時間より9時間ずれているのは、タイムゾーンが UTC のままだからです(直し方は下の「ログの時刻が9時間ずれる」で書きます)。

レベルは8種類

メソッドは8つあり、上ほど深刻なレベルです。

Log::emergency('emergency');
Log::alert('alert');
Log::critical('critical');
Log::error('error');
Log::warning('warning');
Log::notice('notice');
Log::info('info');
Log::debug('debug');
[2026-11-13 19:06:26] local.EMERGENCY: emergency  
[2026-11-13 19:06:26] local.ALERT: alert  
[2026-11-13 19:06:26] local.CRITICAL: critical  
[2026-11-13 19:06:26] local.ERROR: error  
[2026-11-13 19:06:26] local.WARNING: warning  
[2026-11-13 19:06:26] local.NOTICE: notice  
[2026-11-13 19:06:26] local.INFO: info  
[2026-11-13 19:06:26] local.DEBUG: debug  

迷ったら、普段の記録は info、開発中だけ見たいものは debug、失敗は error くらいの使い分けで十分だと思います。

LOG_LEVEL より下のレベルは書かれない

.env の LOG_LEVEL は「このレベル以上だけを書く」という設定です。

LOG_LEVEL=warning にして、上の8行をもう一度出してみました。

[2026-11-13 19:06:46] local.EMERGENCY: emergency  
[2026-11-13 19:06:46] local.ALERT: alert  
[2026-11-13 19:06:46] local.CRITICAL: critical  
[2026-11-13 19:06:46] local.ERROR: error  
[2026-11-13 19:06:46] local.WARNING: warning  

notice・info・debug の3行は、エラーにもならず、ただ書かれませんでした。

「Log::debug を書いたのにログに出ない!」というときは、まず LOG_LEVEL を見てください。

本番で config:cache をしているなら、.env を変えたあとに php artisan config:clear も要ります(詳しくは「Laravelのキャッシュクリアのコマンド一覧」で確かめています)。

logger() と info() のヘルパー

ファサードを use しなくても、ヘルパー関数で書けます。

logger('logger() で出したメッセージ');
logger()->error('logger()->error() で出したメッセージ');
info('info() で出したメッセージ');
logs()->warning('logs()->warning() で出したメッセージ');
[2026-11-13 19:06:26] local.DEBUG: logger() で出したメッセージ  
[2026-11-13 19:06:26] local.ERROR: logger()->error() で出したメッセージ  
[2026-11-13 19:06:26] local.INFO: info() で出したメッセージ  
[2026-11-13 19:06:26] local.WARNING: logs()->warning() で出したメッセージ  

logger('...') は DEBUG、info('...') は INFO で書かれました。

logger() は DEBUG なので、LOG_LEVEL を info 以上にしている本番では出ません。

本番でも残したいメッセージは、info() か Log::info() で書くのが無難です。

変数の中身はコンテキスト(第2引数)で渡す

変数の中身を一緒に残したいときは、第2引数に配列で渡します。

Log::info('注文を受け付けました', ['order_id' => 123, 'total' => 4980]);
Log::info('ユーザー {name} がログインしました', ['name' => '山田']);
Log::info('モデルを渡す', ['user' => $user]);
Log::info('配列を渡す', ['items' => ['りんご', 'みかん']]);
[2026-11-13 19:06:26] local.INFO: 注文を受け付けました {"order_id":123,"total":4980} 
[2026-11-13 19:06:26] local.INFO: ユーザー 山田 がログインしました {"name":"山田"} 
[2026-11-13 19:06:26] local.INFO: モデルを渡す {"user":{"App\\Models\\User":{"name":"山田","email":"[email protected]"}}} 
[2026-11-13 19:06:26] local.INFO: 配列を渡す {"items":["りんご","みかん"]} 

メッセージの後ろに JSON で付きました。

{name} のように書くと、コンテキストの値で置き換えられます(config の replace_placeholders が true になっているため)。

モデルを渡すと、クラス名と属性がそのまま出ます。

メールアドレスのような個人情報も出てしまうので、ログに残す項目は選んだほうがよいです。

メッセージに配列をそのまま渡すと

第1引数に配列を渡しても、エラーにはなりませんでした。

Log::info(['a' => 1]);
Log::info(json_encode(['a' => 1, 'name' => '山田']));
Log::info(print_r(['a' => 1], true));
[2026-11-13 19:06:26] local.INFO: array (
  'a' => 1,
)  
[2026-11-13 19:06:26] local.INFO: {"a":1,"name":"山田"}  
[2026-11-13 19:06:26] local.INFO: Array
(
    [a] => 1
)  

配列は var_export の形で、複数行に分かれて書かれます。

json_encode() で文字列にすると、日本語が 山田 のようにエスケープされて読めません。

1行で読みやすく残すなら、Log::info('メッセージ', $array) のようにコンテキストで渡すのが一番でした。

同じ値を毎回付ける withContext

リクエストごとの ID などを全部のログに付けたいときは、Log::withContext() を先に呼びます。

Log::withContext(['request_id' => 'abc-123']);
Log::info('1つ目');
Log::info('2つ目', ['step' => 2]);
[2026-11-13 19:06:33] local.INFO: 1つ目 {"request_id":"abc-123"} 
[2026-11-13 19:06:33] local.INFO: 2つ目 {"request_id":"abc-123","step":2} 

後のログのコンテキストに、request_id が足されました。

日付ごとにファイルを分ける daily

laravel.log は1つのファイルに書き足し続けるので、運用が長いと大きくなります。

日付ごとに分けたいときは、.env の LOG_STACK を daily にします。

LOG_STACK=daily

daily チャンネルで書くと、laravel-2026-11-13.log のように日付の入ったファイルができました。

storage/logs/
├── laravel-2026-11-13.log
└── laravel.log

古いファイルは LOG_DAILY_DAYS の数だけ残る

daily には、残すファイルの数の設定(max_files)があり、既定は14です。

.env の LOG_DAILY_DAYS で変えられます。

LOG_DAILY_DAYS=3 にして、11月5日〜13日の9つの古いファイルを置いた状態でログを書いてみました。

--- 書く前
laravel-2026-11-05.log
laravel-2026-11-06.log
laravel-2026-11-07.log
laravel-2026-11-08.log
laravel-2026-11-09.log
laravel-2026-11-10.log
laravel-2026-11-11.log
laravel-2026-11-12.log
laravel-2026-11-13.log
--- 書いた後(LOG_DAILY_DAYS=3)
laravel-2026-11-12.log
laravel-2026-11-13.log
laravel-2026-11-14.log

今日の分を含めて3つだけが残り、古い7つは消えました。

障害の調査で1か月前のログを見たいこともあるので、本番では日数を短くしすぎないほうがよいと思います。

single と daily の両方に書く

LOG_STACK はカンマで区切って、複数のチャンネルを並べられます。

LOG_STACK=single,daily

これで4行のログを書くと、laravel.log と laravel-2026-11-13.log の両方に4行ずつ書かれました。

  4 storage/logs/laravel-2026-11-13.log
  4 storage/logs/laravel.log
  8 total

1回だけ別のチャンネルや別のファイルに書く

普段は stack のまま、特定のログだけ別のチャンネルに書くこともできます。

Log::channel('daily')->info('daily チャンネルへ');
Log::stack(['single', 'daily'])->info('single と daily の両方へ');

Log::channel('daily') の行は日付のファイルにだけ、Log::stack([...]) の行は laravel.log と日付のファイルの両方に書かれました。

config/logging.php にないファイルへ書きたいときは、Log::build() でその場でチャンネルを作れます。

Log::build([
    'driver' => 'single',
    'path' => storage_path('logs/import.log'),
])->info('インポートを始めました', ['rows' => 120]);
[2026-11-13 19:06:33] local.INFO: インポートを始めました {"rows":120} 

storage/logs/import.log ができて、そこにだけ書かれました。

CSV の取り込みのようなバッチの記録を、アプリ全体のログと分けたいときに便利です!

Docker で動かしていてコンテナのログ(docker logs)で見たいときは、最初から用意されている stderr チャンネルが使えます。

Log::channel('stderr')->info('stderr へ出したメッセージ', ['id' => 1]);

標準出力には何も出ず、標準エラー出力にだけ1行出ました。

[2026-11-13 19:06:33] local.INFO: stderr へ出したメッセージ {"id":1} 

エラーは自分で書かなくてもログに残る

画面で例外が起きたときは、Log::error() を書かなくても、Laravel が自動で laravel.log に書きます。

わざと null のプロパティを読むルートを作って、アクセスしてみました。

Route::get('/boom', function () {
    $user = null;
    return $user->name;
});

ステータスは 500 で、laravel.log にはメッセージとスタックトレースが書かれていました。

[2026-11-14 04:07:01] local.ERROR: Attempt to read property "name" on null {"exception":"[object] (ErrorException(code: 0): Attempt to read property \"name\" on null at /root/app/routes/web.php:17)
[stacktrace]
#0 /root/app/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php(267): ...
#1 /root/app/routes/web.php(17): ...

1回のエラーで52行のスタックトレースが書かれたので、ログが大きくなる原因はたいていこれです。

存在しない URL の 404 では、ログは1行も増えませんでした。

try〜catch で例外を受け止めて処理を続けたいけれど、ログには残したいときは report() を使います。

try {
    throw new RuntimeException('CSV の3行目が読めません');
} catch (Throwable $e) {
    report($e);
    // ここから先の処理も続く
}
[2026-11-13 19:06:33] local.ERROR: CSV の3行目が読めません {"exception":"[object] (RuntimeException(code: 0): CSV の3行目が読めません at (ファイルのパス):77)
[stacktrace]
#0 {main}
"} 

ERROR として書かれ、report() の後ろの処理もそのまま進みました。

ログの時刻が9時間ずれる

ここまでのログの時刻は 19:06 などで、日本時間ではありません。

Laravel 13 の config/app.php は、タイムゾーンが 'timezone' => 'UTC' になっていました。

'Asia/Tokyo' に変えると、日本時間で書かれるようになりました。

'timezone' => 'Asia/Tokyo',
[2026-11-14 04:06:46] local.INFO: ユーザーが登録しました  

daily のファイル名もこのタイムゾーンの日付になり、変える前は laravel-2026-11-13.log、変えた後は laravel-2026-11-14.log に書かれました。

「日本時間の朝9時より前のログが、前の日のファイルに入っている」というときは、これが原因です。

Permission denied でログが書けないとき

Web サーバーのユーザー(www-data など)が storage に書き込めないと、ログを書く時点でエラーになります。

root が作った laravel.log(-rw-r--r-- root root)に、www-data のユーザーで書こうとしてみました。

   UnexpectedValueException 

  The stream or file "/root/app/storage/logs/laravel.log" could not be opened in append mode: Failed to open stream: Permission denied
The exception occurred while attempting to log: ユーザーが登録しました

artisan のコマンドを root や自分のユーザーで動かしたときに laravel.log ができて、そのあと Web サーバーから書けなくなる、という順番で起きやすいエラーです。

storage と bootstrap/cache の持ち主を Web サーバーのユーザーにすると、書けるようになりました。

chown -R www-data:www-data storage bootstrap/cache
[2026-11-14 04:07:08] local.WARNING: 在庫が少なくなっています  
[2026-11-14 04:07:08] local.ERROR: 決済に失敗しました  

chmod -R 777 でも書けるようにはなりますが、誰でも書き換えられる状態になるのでおすすめしません。

同じ Permission denied でも file_put_contents で出るときは、「file_put_contents failed to open stream: Permission deniedを解決する方法」も見てみてください。

ログをターミナルで流して見る php artisan pail

Laravel 13 の新規プロジェクトには、開発用に laravel/pail(v1.3.0)が最初から入っていました。

php artisan pail で、ログを流しながら見られます。

ただ、PHP の pcntl 拡張がないと動きません。

   RuntimeException 

  The [pcntl] extension is required to run Pail.

pcntl を入れた環境で php artisan pail を開いたまま、上の4行のログと /boom の 500 エラーを起こすと、こう流れてきました。

   INFO  Tailing application logs.                                            Press Ctrl+C to exit  
                                                                   Use -v|-vv to show more details  
┌ 04:07:37 INFO ───────────────────────────────────────────────────────────────────────────────────┐
│ ユーザーが登録しました                                                                           │
└───────────────────────────────────────────────────────────────────────────────────────── artisan ┘
┌ 04:07:37 ERROR ──────────────────────────────────────────────────────────────────────────────────┐
│ 決済に失敗しました                                                                               │
└───────────────────────────────────────────────────────────────────────────────────────── artisan ┘
┌ 04:07:37 ErrorException ────────────────────────────────────────────────────── routes/web.php:17 ┐
│ Attempt to read property "name" on null                                                          │
└───────────────────────────────────────────────────────────────────── GET: /boom • Auth ID: guest ┘

(途中の DEBUG と WARNING の2つは省いています)

画面のエラーには、URL とログインしているユーザー(ここでは guest)まで付いています。

tail -f よりだいぶ見やすいです (・∀・)

参考:公式ドキュメントにある使い方

ここからは、公式ドキュメント(Laravel 13.x の Logging)に書かれている内容です。

出典: Logging - Laravel 13.x

  • Log::withContext() はそのチャンネルにだけ付き、全部のチャンネルに付けたいときは Log::shareContext() を使うとのことです。
  • php artisan pail には、--filter(例外の種類やファイルなど)・--message・--level・--user で絞り込むオプションがあり、-v・-vv で詳しく(スタックトレースまで)表示できるそうです。
  • slack チャンネルは LOG_SLACK_WEBHOOK_URL を設定して使い、既定では critical 以上のログだけが Slack に送られるとのことです。
  • config/logging.php のチャンネルに tap を書くと、Monolog のフォーマッターを差し替えて、1行の形を変えられるようです。

まとめ

  • ログは storage/logs/laravel.log に出る(.env の LOG_CHANNEL=stack・LOG_STACK=single)
  • 書くのは Log::info() など8つのレベル。logger() は DEBUG、info() は INFO
  • 出ないときは LOG_LEVEL を見る。指定より下のレベルはエラーなしで捨てられる
  • 変数はコンテキスト(第2引数)で渡すと1行の JSON で残る
  • 日付で分けるなら LOG_STACK=daily。残す数は LOG_DAILY_DAYS(既定14)
  • 時刻とファイルの日付は config/app.php の timezone(既定は UTC)
  • Permission denied は storage の持ち主を Web サーバーのユーザーにする

処理を止めて中身を見たいときの dd() などは、「Laravelでデバッグする方法」にまとめています。

.env と config の関係は「Laravelのconfigとenvの関係」も合わせてどうぞ。

フィードバックのお願い
この記事のフィードバックがありましたらYoutubeの適当な動画にコメントしていただいたり、お問い合わせからご連絡ください。