【Laravel】ログの出し方と設定(Log::info・logger()・daily・stack・ログの場所)
目次
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=debugLOG_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=dailydaily チャンネルで書くと、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 total1回だけ別のチャンネルや別のファイルに書く
普段は 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)に書かれている内容です。
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の関係」も合わせてどうぞ。




