• クエリビルダとか使うけど、実際に発行されたSQL文とバインドパラメータをログに出したい
  • アプリのログと一緒ぢゃなくて別ファイル sql.log に出したいし、日ごとにログローテートしたい

[ PHP 7.1.9 / Laravel 5.8 ]

  1. 手順1. カスタムログチャンネルを作成する
  2. 手順2. SQLクエリをログ出力させる

手順1. カスタムログチャンネルを作成する

作り方は公式マニュアルを参考にした。

(※すみません。手元のLaravelが5.7だと思って5.7のマニュアルを読んでたのですが、後で確認したら5.8でした。なので上に書いたLaravelのバージョンと相違があります。)

ここでは config/logging.phpchannels の一番下に、sqlQueryLog なるカスタムチャンネルを追加する。

config/logging.php
'channels' => [
    ...
    'sqlQueryLog' => [
        'driver' => 'custom',
        'via' => App\Logging\CreateSQLQueryLogger::class,
        'path' => storage_path('logs/sql.log'),
        'level' => 'debug',  // ログレベル debug 以上だけ出力
        'days' => 14,        // 14日分のログを保持する
    ],
],

sqlQueryLog は私が勝手につけた名前なので、べつに好きな名前でよい。必要なのは drivercustom を指定するのと、via にMonologインスタンスを生成するためのクラス (※まだ無い。今から作る) を指定すること。

というわけで App\Logging\CreateSQLQueryLogger を作る。これはカスタムチャンネル用のMonologインスタンスを生成するためのクラスで、Monolog\Logger を返す __invoke() メソッドが必要。

app/Logging/CreateSQLQueryLogger.php
<?php

namespace App\Logging;

use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Formatter\LineFormatter;

class CreateSQLQueryLogger
{
    /**
     * SQLクエリ用Monologインスタンス生成
     * @param  array  $config
     * @return \Monolog\Logger
     */
    public function __invoke(array $config)
    {
        // 引数の $config には、config/logging.php で sqlQueryLog に設定した path とか days とかが入ってる!

        // 'debug' とかの文字列をMonologが使えるログレベルに変換
        $level = Logger::toMonologLevel($config['level']);

        // 日ごとにログローテートするハンドラ作成
        $hander = new RotatingFileHandler($config['path'], $config['days'], $level);  

        // 改行コードを出力する&カラのコンテキストを出力しないフォーマッタを設定
        $hander->setFormatter(new LineFormatter(null, null, true, true));

        // Monologインスタンス作成してハンドラ設定して返却
        $logger = new Logger('SQL');  // ロガー名は 'SQL' にした。これはログに出力される
        $logger->pushHandler($hander);
        return $logger;
    }
}

このクラス名も私がマニュアルを参考に勝手につけた名前なので、どんな名前でも構わないはず。

__invoke() が返すMonologインスタンスは好みで作ればよい。ここでは「日ごとにログローテート (保持日数は days で設定)」「改行コードを出力する」「カラのコンテキストは無視する」ようなやつにした。

これでカスタムログチャンネルはできあがり。アプリ内のどこかしらで以下のように書くと、sqlQueryLog チャンネルを通って、storage/logs/sql-YYYY-MM-DD.log ファイルにログが出力される。

PHP
\Log::channel('sqlQueryLog')->debug('!!!!!');
ログ出力
[2019-08-23 12:39:35] SQL.DEBUG: !!!!!  

手順2. SQLクエリをログ出力させる

公式マニュアルの以下ページに DB::listen() でSQLクエリを取得する方法が書いてあるので、これを使う。

起動時に呼ばれるサービスプロバイダ AppServiceProviderboot() で、以下のようにクエリリスナを登録する。クエリリスナの中で、先ほど作ったカスタムログチャンネル sqlQueryLog にSQLクエリを出力させる。

app/Providers/AppServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    // 略

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        \DB::listen(function($query) {
            $str = $query->time.' ms -> '.$query->sql;
            if ($query->bindings) {
                $str .= "\n".'bind: '.var_export($query->bindings, true);
            }
            \Log::channel('sqlQueryLog')->debug($str);
        });
    }
}

↑のコードはちょっとごちゃごちゃしてるけど、各情報は以下の通りに取得できるので、フォーマットは好きな感じで。

  • $query->time ... 実行時間ミリ秒 (float)
  • $query->sql ... 実際のSQL文 (string)
  • $query->bindings ... バインドパラメータ (array)

あとはアプリ内のどこかしらで以下のようにクエリ実行すると、storage/logs/sql-YYYY-MM-DD.log ファイルに実行時間・実際のSQL文・バインドパラメータが出力される。

PHP
\DB::table('mytable')->where('id', 1)->get();
ログ出力
[2019-08-23 18:30:27] SQL.DEBUG: 89.67 ms -> select * from "mytable" where "id" = ?
bind: array (
  0 => 1,
)  

もちろん普通に \Log::debug() とかで書いたログは、従来通り laravel.log とかに出力される。

参考にさせていただいた記事