Laravel+TelescopeでSQLの呼び出し階層を出力する
追記
この記事の内容を「Telescope Plus」というパッケージにして、Composer公式リポジトリに登録しました。
以下の記事をご覧ください。
はじめに
みなさん、Telescopeを使っていますか?
Telescopeでは発行したSQL、キャッシュ、セッション、メールなどなど様々な情報を確認できてすごく便利ですよね。
でも、SQLの呼び出し階層をもっと詳しく知ることができたらいいのに・・・って思ったことはありませんか?
この記事を読むと、以下の画像のように、SQLの呼び出し階層をクラス名、または、ファイル名、関数名、行番号を(色付きで)表示できるようになります。
バージョン情報
- PHP 8.0.9
- laravel/framework: 8.54
- laravel/telescope: 4.6
Telesopceのインストール
Laravel公式ドキュメント通りにTelesopeをローカルのみへインストールします。
$ composer require laravel/telescope --dev
$ php artisan telescope:install
$ php artisan migrate
/**
* 全アプリケーションサービスの登録
*
* @return void
*/
public function register()
{
if ($this->app->environment('local')) {
$this->app->register(\Laravel\Telescope\TelescopeServiceProvider::class);
$this->app->register(TelescopeServiceProvider::class);
}
}
"extra": {
"laravel": {
"dont-discover": [
+ "laravel/telescope"
]
}
},
ソースコードの改修
vendor以下のソースコードですが、直接改修してしまいます。
まず、以下のQueryWatcher.phpにデバッグトレースを整形するメソッドを追加します。
文字列のハイライト表示は以下の通りです。
項目 | カラー |
---|---|
APP配下のクラス | 赤 |
データベース関連のクラス | 青 |
ミドルウェア | 緑 |
呼び出し階層の初期値は30階層ですが、お好みの階層に調整してください。
/**
* デバッグトレースを整形する
*
* @param integer $limit
* @return void
*/
protected function formatBacktrace($limit = 30)
{
try {
$backTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, $limit);
$result = collect($backTrace)->map(function($item){
if (array_key_exists('class', $item)) {
// クラス名を含む場合、クラス名を出力する
if (array_key_exists('line', $item)) {
// 行番号を含む場合、行番号を出力する
$line = $item['class'] . ":" . $item['line'] . " " . $item['function'];
} else {
// 行番号を含まない場合(クロージャの場合)、行番号を出力しない
$line = $item['class'] . " " . $item['function'];
}
} else {
// クラス名を含まない場合、ファイル名を出力する
$line = $item['file'] . ":" . $item['line'] . " " . $item['function'];
}
if(str_starts_with($line, 'Laravel\\Telescope\\')) {
// Telescopeのメソッド呼び出しは出力しない
return;
}
if(str_starts_with($line, 'App\\')) {
// APP\始まる場合、赤でハイライト表示する
$htmlLine = "<span style=\"font-weight: bold;color:red\">${line}</span>";
} else if(str_starts_with($line, 'Illuminate\\Database\\')) {
// Illuminate\Database\で始まる場合、青でハイライト表示する
$htmlLine = "<span style=\"font-weight: bold;color:blue\">${line}</span>";
} else if(strpos($line, '\\Middleware\\') !== false) {
// \Middleware\を含む場合、緑でハイライト表示する
$htmlLine = "<span style=\"font-weight: bold;color:green\">${line}</span>";
} else {
$htmlLine = $line;
}
return $htmlLine;
})->join('<br>');
return $result;
} catch (Exception $e) {
Log::error($e->getMessage());
}
}
次に、追加したformatBacktraceメソッドを呼び出すよう、同じQueryWatcher.phpを改修します。
/**
* Record a query was executed.
*
* @param \Illuminate\Database\Events\QueryExecuted $event
* @return void
*/
public function recordQuery(QueryExecuted $event)
{
if (! Telescope::isRecording()) {
return;
}
$time = $event->time;
if ($caller = $this->getCallerFromStackTrace()) {
Telescope::recordQuery(IncomingEntry::make([
'connection' => $event->connectionName,
'bindings' => [],
'sql' => $this->replaceBindings($event),
'time' => number_format($time, 2, '.', ''),
'slow' => isset($this->options['slow']) && $time >= $this->options['slow'],
+ 'file' => $this->formatBacktrace(),
- 'file' => $caller['file'],
'line' => $caller['line'],
'hash' => $this->familyHash($event),
])->tags($this->tags($event)));
}
}
画面を改修します。
呼び出し階層をhtml形式で出力するので、エスケープしないようv-htmlディレクティブで出力するよう修正します。
<tr v-if="slotProps.entry.content.file">
<td class="table-fit font-weight-bold">Location</td>
+ <td v-html="slotProps.entry.content.file"></td>
- <td>
- {{slotProps.entry.content.file}}:{{slotProps.entry.content.line}}
- </td>
</tr>
Laravel Mixを実行して、app.jsをコピーします。
$ cd vendor/laravel/telescope/
$ npm install
$ sail npm run dev
$ cp ./public/app.js ../../../public/vendor/telescope/
以上でソースコードの改修は完了です。
実行結果
画面を操作してTelescopeでSQLを確認すると、以下のように呼び出し階層が表示されます。
まとめ
「なぜ、SQL呼び出し階層を見える化しようと思ったか?」なんですが、Laravel Breezeの仕様を調べていたことがきっかけでした。
いつ、どんなSQLが発行されるのか把握したくて、QueryWatcherクラスにブレークポイントを貼ってスタックトレースをひたすら追っていたんですよね。
次第に面倒になってきて、「ええい!スタックトレースを出力してしまおう!」と考えたことが、この記事を書くきっかけになりました😅
SQLの呼び出し階層が見えるようになったので、どのような経緯でSQLが発行されたのか?、把握しやすくなったと思います。
どこにブレークポイントを設定したらよいかわかりやすくなるので、デバッグも捗りそうですね😄
他にも、redisコマンドの出力なども改善の余地があるように思います。
順番に対応できたらいいなと思っています。
Discussion