Laravel Deprecation Logger:LOG_DEPRECATIONS_CHANNEL=nullが効かない理由
🎭 序章:謎のEMERGENCYログとの遭遇
PHP 8.5へのアップグレード作業中、予期せぬログが出現した。
[2025-12-25 09:34:53] laravel.EMERGENCY: Unable to create configured logger. Using emergency logger.
「設定したのに...なぜ?」
.envファイルにはLOG_DEPRECATIONS_CHANNEL=nullと設定し、本番環境では非推奨警告を無効化したはずだった。しかし、laravel.logにdeprecation警告を吐き出し続ける。
🔍 問題の発見
PHP 8.5へのアップグレード後、Symfonyの非推奨警告が出力されていることに気がついた。
[2025-12-25 09:34:53] laravel.WARNING: Since symfony/http-foundation 7.4:
Request::get() is deprecated, use properties ->attributes, query or request directly instead.
開発環境では記録したいが、本番環境では不要。そこで、Laravelのドキュメント通りに設定した。
設定したこと
.env
LOG_DEPRECATIONS_CHANNEL=null
期待した動作
- 非推奨警告がログに記録されない
実際の動作
-
laravel.logに警告が出力される - 加えて、謎の
EMERGENCYログも出現
「何かがおかしい...」
🤔 最初の仮説:環境変数が読まれていない?
まず疑ったのは、環境変数が正しく読み込まれているかどうか。
sail artisan tinker
> echo env('LOG_DEPRECATIONS_CHANNEL');
// 出力: (何も表示されない = null)
環境変数は正しくnullになっている。では、なぜ動かない?
Laravelの設定キャッシュの可能性
sail artisan config:clear
sail artisan config:cache
しかし、結果は変わらず。コンテナを再起動しても同じ。
「設定は読まれているのに、なぜ効かない?」
📚 設定ファイルの構造を理解する
config/logging.phpを改めて確認する。
return [
'default' => env('LOG_CHANNEL', 'stack'),
// トップレベルのdeprecations設定
'deprecations' => [
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
],
'channels' => [
'stack' => [...],
'single' => [...],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
// 🤔 deprecationsチャネルが定義されていない!
],
];
ここで重要な発見:
-
トップレベルに
deprecations設定がある -
channels内には
deprecationsが定義されていない
Laravelのドキュメントでは、トップレベルのdeprecations.channelを設定すれば動くはず...なのに。
🕵️ Laravel内部コードへのダイブ
ここからが本番。Laravelのソースコードを読み解く。
1. エントリーポイント:HandleExceptions.php
非推奨エラーが発生すると、HandleExceptions::handleDeprecationError()が呼ばれる。
// vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php
public function handleDeprecationError($message, $file, $line, $level = E_DEPRECATED)
{
// ...
$this->ensureDeprecationLoggerIsConfigured(); // 👈 設定を確認
$options = static::$app['config']->get('logging.deprecations') ?? [];
with($logger->channel('deprecations'), function ($log) use (...) { // 👈 ここで例外発生!
$log->warning($message);
});
}
2. 設定を確認する:ensureDeprecationLoggerIsConfigured()
protected function ensureDeprecationLoggerIsConfigured()
{
$config = static::$app['config'];
// 既に定義されていればスキップ
if ($config->get('logging.channels.deprecations')) {
return;
}
// トップレベルのdeprecations設定から取得
if (is_array($options = $config->get('logging.deprecations'))) {
$driver = $options['channel'] ?? 'null'; // 👈 'null'を取得
} else {
$driver = $options ?? 'null';
}
// 動的に設定をコピー
$config->set('logging.channels.deprecations',
$config->get("logging.channels.{$driver}")); // 👈 nullチャネルをコピー
}
ここまでは正常に動作しているはず...
3. ログチャネルを取得する:LogManager.php
問題は次の行で発生する。
$logger->channel('deprecations')
内部で何が起きているのか?
// vendor/laravel/framework/src/Illuminate/Log/LogManager.php
protected function get($name, ?array $config = null)
{
try {
return $this->channels[$name] ?? with($this->resolve($name, $config), function ($logger) use ($name) {
// ...
return $this->channels[$name] = $loggerWithContext;
});
} catch (Throwable $e) {
// 👈 ここで例外をキャッチ!
return tap($this->createEmergencyLogger(), function ($logger) use ($e) {
$logger->emergency('Unable to create configured logger. Using emergency logger.', [
'exception' => $e,
]);
});
}
}
例外が発生すると、Emergency Loggerにフォールバックする!
4. 何の例外が発生している?
protected function resolve($name, ?array $config = null)
{
$config ??= $this->configurationFor($name); // 👈 ここで設定を取得
if (is_null($config)) {
throw new InvalidArgumentException("Log [{$name}] is not defined."); // 👈 これだ!
}
// ...
}
protected function configurationFor($name)
{
return $this->app['config']["logging.channels.{$name}"]; // 👈 nullが返る
}
5. Emergency Loggerの正体
protected function createEmergencyLogger()
{
$config = $this->configurationFor('emergency');
$handler = new StreamHandler(
$config['path'] ?? $this->app->storagePath().'/logs/laravel.log', // 👈 laravel.log!
$this->level(['level' => 'debug'])
);
return new Logger(
new Monolog('laravel', $this->prepareHandlers([$handler])),
$this->app['events']
);
}
真実が明らかになった!
💡 真犯人を発見:タイミングの問題
問題の流れを整理する:
-
101行目:
ensureDeprecationLoggerIsConfigured()が実行される -
149行目:
$config->set('logging.channels.deprecations', ...)で動的に設定 -
105行目:
$logger->channel('deprecations')を呼び出し -
LogManager内部:
configurationFor('deprecations')が実行される -
しかし: 設定が反映される前に
nullが返される -
結果:
InvalidArgumentExceptionがスロー -
フォールバック: Emergency Logger(
laravel.log)が使用される
つまり、設定の動的追加とLogManagerの内部処理の間に競合状態(Race Condition)が存在する!
なぜ競合が起きるのか?
$config->set()は実行時の動的設定だが、LogManagerの内部キャッシュや処理順序により、設定が反映される前に参照されてしまう。
✅ 解決策とその理由
解決策:channels.deprecationsを明示的に定義する
// config/logging.php
'channels' => [
// ... 他のチャネル
'deprecations' => [
'driver' => env('LOG_DEPRECATIONS_CHANNEL') === null ? 'monolog' : 'single',
'handler' => env('LOG_DEPRECATIONS_CHANNEL') === null ? NullHandler::class : null,
'path' => env('LOG_DIR', storage_path('logs')) . '/deprecations.log',
'level' => 'warning',
'replace_placeholders' => true,
'permission' => 0660,
],
],
なぜこれで解決するのか?
ensureDeprecationLoggerIsConfigured()の最初のチェックを見てほしい:
if ($config->get('logging.channels.deprecations')) {
return; // 👈 既に定義されていれば何もしない
}
明示的に定義することで:
- 動的な設定追加をスキップ
-
LogManagerが直接設定を参照できる - タイミング問題を回避
環境に応じた制御
開発環境 (.env)
LOG_DEPRECATIONS_CHANNEL=deprecations
→ singleドライバーでdeprecations.logに記録
本番環境 (.env.production)
# LOG_DEPRECATIONS_CHANNELをnullに設定、もしくは削除
LOG_DEPRECATIONS_CHANNEL=null
→ env()がnullを返し、NullHandlerで無効化
🎯 結論:Laravelのバグか、それとも?
これはバグなのか?
技術的には: Yes、タイミング問題による不具合
実用的には: Workaround(回避策)が存在する
Laravelコミュニティへの報告は必要?
- 報告する価値はある:他の開発者も同じ問題に遭遇している可能性
- しかし修正は難しい:内部アーキテクチャの大きな変更が必要
-
現実的な対応:
channels.deprecationsを明示的に定義する
マージリクエストのコミットメッセージ例
fix: Add explicit deprecations channel configuration
Laravelのdeprecation logger初期化タイミングの問題を回避するため、
logging.channels.deprecationsを明示的に定義。
これにより、環境変数LOG_DEPRECATIONS_CHANNELで
deprecation警告の出力を制御できるようになる。
- 開発環境: LOG_DEPRECATIONS_CHANNEL=deprecations でログ記録
- 本番環境: 環境変数を未設定にすることでログを無効化
🎓 学んだこと
-
Laravelの設定は2層構造
- トップレベル設定(
logging.deprecations) - チャネル設定(
logging.channels.*)
- トップレベル設定(
-
動的設定には限界がある
-
config->set()は万能ではない - タイミング問題を考慮する必要がある
-
-
Emergency Loggerの存在
- ログ設定に失敗すると
laravel.logにフォールバック - デバッグの手がかりとして
EMERGENCYログを確認
- ログ設定に失敗すると
-
フレームワークのソースコードを読む価値
- ドキュメントだけでは分からない問題がある
- 内部実装を理解することで根本原因を特定できる
📚 参考資料
- Laravel Logging Documentation # Logging Deprecation Warnings
- Monolog Documentation
- Laravel Framework Source Code:
Illuminate\Foundation\Bootstrap\HandleExceptionsIlluminate\Log\LogManager
Discussion