Monolog は多くの PHP コードで使われているロギングライブラリです。フレームワークに搭載されていることも多いです。
Seldaek/monolog: Sends your logs to files, sockets, inboxes, databases and various web services
Monolog リポジトリでは次の様に使用されているライブラリが示されています。
- Frameworks and libraries using PSR-3
can be used very easily with Monolog since it implements the interface. - Symfony comes out of the box with Monolog.
- Laravel comes out of the box with Monolog.
- Lumen comes out of the box with Monolog.
- PPI comes out of the box with Monolog.
- CakePHP is usable with Monolog via the cakephp-monolog plugin.
- Slim is usable with Monolog via the Slim-Monolog log writer.
- XOOPS 2.6 comes out of the box with Monolog.
- Aura.Web_Project comes out of the box with Monolog.
- Nette Framework can be used with Monolog via contributte/monolog extension.
- Proton Micro Framework comes out of the box with Monolog.
- FuelPHP comes out of the box with Monolog.
- Equip Framework comes out of the box with Monolog.
- Yii 2 is usable with Monolog via the yii2-monolog or yii2-psr-log-target plugins.
- Hawkbit Micro Framework comes out of the box with Monolog.
- SilverStripe 4 comes out of the box with Monolog.
フレームワークの内部ではおおよそ(Laravel, FuelPHP, CakePHP のみ確認) Monolog をいい感じにインスタンス化してログレベルに適した呼び方でメソッドを呼び出すことでログを記録しやすくしています。この Monolog のログフォーマットを操作できると後の運用やデモ環境におけるデバッグが容易になります。
ざっくばらんに言えば Monolog はログのメタデータや現実行環境データの取得元を定義する Processer, ログの形式を定義する Formatter, ログの出力先を定義する Handler, それらをまとめる Logger から成ります。Logger が Processer を元に定義されたログを Formatter 付きの形式で Handler に出力するのが基本です。
Monologは次の様に動作させられます
// file_put_contents('hoge.txt', 'fugaMsg', FILE_APPEND) とほとんど変わらない最小構成 $log = new \Monolog\Logger( 'hoge_log', [new \Monolog\Handler\StreamHandler(__DIR__.'/hoge.log', \Monolog\Logger::DEBUG)], ); $log->debug('これはデバッグログ'); // ↓がファイルに追記されます // [2020-10-19T11:49:25.631086+09:00] hoge_log.DEBUG: これはデバッグログ [] []
// 実行環境情報も入れた状態 $log = new \Monolog\Logger( 'hoge_log', [new \Monolog\Handler\StreamHandler(__DIR__.'/hoge.log', \Monolog\Logger::DEBUG)], // UidProcessor は Processor についてのユニーク ID を生成します // WebProcessor は url, ip などの web 通信についての情報を PHP 定義済み変数から取得します [new \Monolog\Processor\UidProcessor(), new \Monolog\Processor\WebProcessor()] ); $log->getHandlers()[0]->setFormatter(new \Monolog\Formatter\JsonFormatter()); $log->debug('これはデバッグログ'); // ↓がファイルに追記されます。一行で完結する JSON なので構造を扱うには便利ですが読み取りにはスクリプトが欲しくなります。 // {"message":"これはデバッグログ","context":{},"level":100,"level_name":"DEBUG","channel":"hoge_log","datetime":"2020-10-19T11:57:30.310655+09:00","extra":{"uid":"465167a","url":"/browser-api/hoge/search?page=0&perPage=30","ip":"192.168.99.1","http_method":"GET","server":"","referrer":null}}
各 Handler 毎に細かく制御することも可能です。次の記事が図解付きの日本語で分かりやすく詳しかったです。
PHPのロガーMonologを理解しよう | QUARTETCOM TECH BLOG
どの様な Handler, Processor, Formatter があるかは次にまとまっています。この辺りを探せば手製で何か作らなくとも既製品でさくっとやりたいことができるかもしません。
monolog/02-handlers-formatters-processors.md at master · Seldaek/monolog
ここからは Laravel で使われている Monolog を編集する方法を紹介します。
ログ 6.x Laravel#チャンネル用Monologカスタマイズ
リンクにある様に tap 配列に Monolog インスタンス加工クラスを渡します。この設定と Monolog 加工クラスを書くだけでログフォーマットを好き勝手できます。
'single' => [ 'driver' => 'single', 'tap' => [App\Logging\DetailFormatter::class], // ←ここで Monolog インスタンス加工クラスを定義 'path' => storage_path('logs/laravel.log'), 'level' => 'debug', ],
Monolog インスタンス加工クラスは例えば次の様に記述できます。
Monolog インスタンス加工クラスソースコード
<?php namespace App\Logging; use Monolog\Formatter\LineFormatter; use Monolog\Handler\FormattableHandlerInterface; use Monolog\Handler\ProcessableHandlerInterface; use Monolog\Logger; use Monolog\Processor\IntrospectionProcessor; use Monolog\Processor\ProcessIdProcessor; use Monolog\Processor\UidProcessor; use Monolog\Processor\WebProcessor; class DetailFormatter { /** * ログのフォーマット. * Monolog の基底の他に Processor を追加することで変数的に呼べる部分が増えます * @var string */ private const LOG_FORMAT = '[%datetime% %channel%.%level_name% url:%extra.url% ip:%extra.ip% uid:%extra.uid% %extra.class%::%extra.function%(%extra.file% %extra.line%)] %message% %context%'.PHP_EOL; /** * 渡されたロガーインスタンス(Monolog インスタンス)のカスタマイズ * * @param Logger $monolog * @return void */ public function __invoke($monolog) { // ミリ秒単位の日時表記で↑の定数のフォーマットのログを生成するフォーマッターを定義 $formatter = new LineFormatter(self::LOG_FORMAT, 'Y/m/d H:i:s.v', true, true); $uidProcessor = new UidProcessor(); // プロセッサー単位のユニーク ID を発行する. handlers ループ外でインスタンス化しないと同じログがハンドラー毎に異なる UID を持つことになる // 出力先単位で foreach foreach ($monolog->getHandlers() as $handler) { if ($handler instanceof FormattableHandlerInterface) { // メッセージの形式が指定可能ならば先の一行フォーマットを適用 $handler->setFormatter($formatter); } if ($handler instanceof ProcessableHandlerInterface) { // Processor を扱えるならば各実行環境情報を持つ Processor を追加 // コメントを書きやすいのでほぼ全てここで new // 同じ対象のログなのにハンドラーによってユニークIDが違うことになり惨事を招く UidProcessor のみ外 $handler->pushProcessor(new IntrospectionProcessor(Logger::DEBUG, ['Illuminate\\'])); // ログメッセージの発生源となった行/ファイル:クラス/関数 $handler->pushProcessor(new WebProcessor()); // 現在のウェブリクエストの url/method とリモート IP $handler->pushProcessor(new ProcessIdProcessor()); // プロセスID $handler->pushProcessor($uidProcessor); } } } }
このコードでフォーマットを定義すると
\Log::debug('hoge'); \Log::debug('fuga');
から
[2020/10/20 14:29:36.716 local.DEBUG url:/ ip:192.168.99.1 uid:61b863a App\Providers\AppServiceProvider::boot(/work/backend/app/Providers/AppServiceProvider.php 30)] hoge [2020/10/20 14:29:36.718 local.DEBUG url:/ ip:192.168.99.1 uid:61b863a App\Providers\AppServiceProvider::boot(/work/backend/app/Providers/AppServiceProvider.php 31)] fuga
といったログが取れます。これを使うことでリクエスト単位のログ、異常動作の発生場所、動作の重いURL、異常な操作を行うアクセス元などといった様々な情報を集めやすくできます。