LoginSignup
269
231

Laravel SQLの実行クエリログを出力する

Last updated at Posted at 2019-06-15

2024/4/10 追記: こちらの記事をライブラリ化しました。

https://qiita.com/ucan-lab/items/dc33ab7df03ebf485898

新しいLaravelのバージョンに合わせてアップデートした記事を書きました。
Laravel 10.15.0 以降を使用している場合はご参考にどうぞ。

https://qiita.com/ucan-lab/items/ef95805818b843ce5bce

LaravelではEloquentやQueryBuilderを介してデータベースとやり取りしますが、実際に実行されるSQLを確認する時に使用する方法を3つご紹介します。

環境

  • PHP 8.0
  • MySQL 8.0.17
  • Laravel 8.23.1

1. QueryBuilderをSQLに変換したい時

クエリビルダには、 toSql() メソッドを呼び出すことでSQL文を取得できます。プレースホルダの値は getBindings() で取得できます。

$query = \App\User::where('id', 1);
dd($query->toSql(), $query->getBindings());

tinkerで確認する時にも便利です。

2. 複数のQueryBuilderのSQLを確認したい時

SQL実行前に DB::enableQueryLog() でクエリログを有効化し、SQL実行後に DB::getQueryLog() メソッドを利用することで、そのクエリの内容や、実行時間を取得できます。

\DB::enableQueryLog();
\App\User::all();
dd(\DB::getQueryLog());

有効化した後のクエリログを取得できます。

3. サービスプロバイダーに登録してログファイルに自動出力

  • 環境変数でログ出力の有効/無効を切り替えする

DatabaseQueryServiceProvider を作成

$ php artisan make:provider DatabaseQueryServiceProvider

app/Providers/DatabaseQueryServiceProvider.php ファイルが生成される。

config/app.php へ追記

    'providers' => [
        // ... 省略
        App\Providers\DatabaseQueryServiceProvider::class,
    ],

providers の配列に作成した DatabaseQueryServiceProvider を登録します。

app/Providers/DatabaseQueryServiceProvider.php を編集

app/Providers/DatabaseQueryServiceProvider.php
<?php

declare(strict_types=1);

namespace App\Providers;

use Carbon\Carbon;
use DateTime;
use Illuminate\Database\Events\TransactionBeginning;
use Illuminate\Database\Events\TransactionCommitted;
use Illuminate\Database\Events\TransactionRolledBack;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\ServiceProvider;

final class DatabaseQueryServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     */
    public function register(): void
    {
        if (config('logging.sql.enable') !== true) {
            return;
        }

        DB::listen(static function ($query): void {
            $sql = $query->sql;

            foreach ($query->bindings as $binding) {
                if (is_string($binding)) {
                    $binding = "'{$binding}'";
                } elseif (is_bool($binding)) {
                    $binding = $binding ? '1' : '0';
                } elseif (is_int($binding)) {
                    $binding = (string) $binding;
                } elseif (is_float($binding)) {
                    $binding = (string) $binding;
                } elseif ($binding === null) {
                    $binding = 'NULL';
                } elseif ($binding instanceof Carbon) {
                    $binding = "'{$binding->toDateTimeString()}'";
                } elseif ($binding instanceof DateTime) {
                    $binding = "'{$binding->format('Y-m-d H:i:s')}'";
                }

                $sql = preg_replace('/\\?/', $binding, $sql, 1);
            }

            if ($query->time > config('logging.sql.slow_query_time')) {
                Log::warning(sprintf('%.2f ms, SQL: %s;', $query->time, $sql));
            } else {
                Log::debug(sprintf('%.2f ms, SQL: %s;', $query->time, $sql));
            }
        });

        Event::listen(static fn (TransactionBeginning $event) => Log::debug('START TRANSACTION'));
        Event::listen(static fn (TransactionCommitted $event) => Log::debug('COMMIT'));
        Event::listen(static fn (TransactionRolledBack $event) => Log::debug('ROLLBACK'));
    }
}

config/logging.php を編集

config/logging.php
return [
    /*
    |--------------------------------------------------------------------------
    | Custom Log
    |--------------------------------------------------------------------------
    */

    'sql' => [
        'enable' => env('LOG_SQL_ENABLE', false),
        'slow_query_time' => env('LOG_SQL_SLOW_QUERY_TIME', 2000), // ms
    ],
];

.env を編集

LOG_SQL_ENABLE=true
LOG_SQL_SLOW_QUERY_TIME=1000

※本番環境ではパフォーマンスやセキュリティ上の問題から無効化した方が良いです!

お試し実行

routes/web.php

<?php

use App\User;

Route::get('/', function () {
    $user = DB::transaction(function () {
        $user = factory(User::class)->create();
        $user->name = 'change name';
        $user->save();
        $user->delete();

        return factory(User::class)->create();
    });

    User::find($user->id);

    return view('welcome');
});

適当にSQLを発行しそうな処理を加えて、いつものウェルカムページを表示する。

storage/logs/laravel-***.log

ログファイルを確認すると次のようなデータベースクエリログが出力されていればok

[2019-06-15 07:14:09] local.DEBUG: START TRANSACTION  
[2019-06-15 07:14:09] local.DEBUG: SQL {"sql":"insert into `users` (`name`, `email`, `email_verified_at`, `password`, `remember_token`, `updated_at`, `created_at`) values ('Pietro Conn', 'leuschke.kiarra@example.net', '2019-06-15 07:14:09', 'yIXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'rch9DXbn5W', '2019-06-15 07:14:09', '2019-06-15 07:14:09')","time":"2.19 ms"} 
[2019-06-15 07:14:09] local.DEBUG: SQL {"sql":"update `users` set `name` = 'change name', `users`.`updated_at` = '2019-06-15 07:14:09' where `id` = 23","time":"1.66 ms"} 
[2019-06-15 07:14:09] local.DEBUG: SQL {"sql":"delete from `users` where `id` = 23","time":"1.33 ms"} 
[2019-06-15 07:14:09] local.DEBUG: SQL {"sql":"insert into `users` (`name`, `email`, `email_verified_at`, `password`, `remember_token`, `updated_at`, `created_at`) values ('Zoila Wintheiser', 'eokon@example.com', '2019-06-15 07:14:09', 'yIXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Ya6yCp6UTX', '2019-06-15 07:14:09', '2019-06-15 07:14:09')","time":"1.61 ms"} 
[2019-06-15 07:14:09] local.DEBUG: COMMIT  
[2019-06-15 07:14:09] local.DEBUG: SQL {"sql":"select * from `users` where `users`.`id` = 24 limit 1","time":"4.41 ms"} 

関連記事

269
231
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
269
231