【PHP】【Laravel】Monolog のログフォーマットを操作する

 Monolog は多くの PHP コードで使われているロギングライブラリです。フレームワークに搭載されていることも多いです。
Seldaek/monolog: Sends your logs to files, sockets, inboxes, databases and various web services
 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、異常な操作を行うアクセス元などといった様々な情報を集めやすくできます。

>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG