Laravel SQLのログをカスタムログチャンネルで別ファイルに出力させる
- クエリビルダとか使うけど、実際に発行されたSQL文とバインドパラメータをログに出したい
- アプリのログと一緒ぢゃなくて別ファイル
sql.log
に出したいし、日ごとにログローテートしたい
手順1. カスタムログチャンネルを作成する
作り方は公式マニュアルを参考にした。
(※すみません。手元のLaravelが5.7だと思って5.7のマニュアルを読んでたのですが、後で確認したら5.8でした。なので上に書いたLaravelのバージョンと相違があります。)
ここでは config/logging.php
の channels
の一番下に、sqlQueryLog
なるカスタムチャンネルを追加する。
'channels' => [
...
'sqlQueryLog' => [
'driver' => 'custom',
'via' => App\Logging\CreateSQLQueryLogger::class,
'path' => storage_path('logs/sql.log'),
'level' => 'debug', // ログレベル debug 以上だけ出力
'days' => 14, // 14日分のログを保持する
],
],
sqlQueryLog
は私が勝手につけた名前なので、べつに好きな名前でよい。必要なのは driver
に custom
を指定するのと、via
にMonologインスタンスを生成するためのクラス (※まだ無い。今から作る) を指定すること。
というわけで App\Logging\CreateSQLQueryLogger
を作る。これはカスタムチャンネル用のMonologインスタンスを生成するためのクラスで、Monolog\Logger
を返す __invoke()
メソッドが必要。
<?php echo '<?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
ファイルにログが出力される。
\Log::channel('sqlQueryLog')->debug('!!!!!');
[2019-08-23 12:39:35] SQL.DEBUG: !!!!!
手順2. SQLクエリをログ出力させる
公式マニュアルの以下ページに DB::listen()
でSQLクエリを取得する方法が書いてあるので、これを使う。
起動時に呼ばれるサービスプロバイダ AppServiceProvider
の boot()
で、以下のようにクエリリスナを登録する。クエリリスナの中で、先ほど作ったカスタムログチャンネル sqlQueryLog
にSQLクエリを出力させる。
<?php echo '<?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文・バインドパラメータが出力される。
\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
とかに出力される。
参考にさせていただいた記事
PHPのロガーMonologを理解しよう | QUARTETCOM TECH BLOG