search
LoginSignup
208
Help us understand the problem. What are the problem?

posted at

updated at

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

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 を編集

<?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;

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

        DB::listen(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);
            }

            Log::debug('SQL', ['sql' => $sql, 'time' => "{$query->time} ms"]);
        });

        Event::listen(TransactionBeginning::class, function (TransactionBeginning $event): void {
            Log::debug('START TRANSACTION');
        });

        Event::listen(TransactionCommitted::class, function (TransactionCommitted $event): void {
            Log::debug('COMMIT');
        });

        Event::listen(TransactionRolledBack::class, function (TransactionRolledBack $event): void {
            Log::debug('ROLLBACK');
        });
    }
}

config/logging.php を編集

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

    'sql' => [
        'enable' => env('LOG_SQL_ENABLE', false),
    ],
];

.env を編集

LOG_SQL_ENABLE=true

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

お試し実行

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"} 

関連記事

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
What you can do with signing up
208
Help us understand the problem. What are the problem?