首發於: 個人博客
以前 Lumen
框架從 5.6
升級到 5.7
。發現 laravel-sql-logger
包不能正常紀錄日誌了。進行排查,發現是 Lumen
框架沒有對 DB
類型注入 event
對象,致使不能正常對其進行SQL監聽。php
那麼解決方案也很是簡單。mysql
// file: bootstrap/app.php $app["db"]->connection()->setEventDispatcher($app["events"]); // 在下面的註冊前加入這一行便可 $app->register(Mnabialek\LaravelSqlLogger\Providers\ServiceProvider::class);
可是這也讓我對如何實現SQL紀錄產生了興趣。接下來,咱們就具體瞭解一下如何實現SQL監聽。laravel
咱們知道在Larvel
上很是簡單。只須要以下方法便可對其進行SQL監聽:git
namespace App\Providers; use Illuminate\Support\Facades\DB; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { /** * 啓動應用服務 * * @return void */ public function boot() { DB::listen(function ($query) { // $query->sql // $query->bindings // $query->time }); } //... }
可是在 Lumen
上這種辦法是沒有辦法使用的。Lumen
有一些本身的調試SQL的方法,可是這些並非咱們想要的。因此咱們只能本身寫監聽事件。github
具體的解決方案是,咱們首先建立一個Listener文件。web
// file: app\Listeners\QueryListener.php namespace App\Listeners; use Illuminate\Database\Events\QueryExecuted; class QueryListener { /** * Create the event listener. * * @return void */ public function __construct() { // } /** * Handle the event. * * @param ExampleEvent $event * @return void */ public function handle(QueryExecuted $event) { dd($event); // 此處直接對其進行打印 } }
接下來咱們直接對其進行註冊sql
// file: app/Providers/EventServiceProvider.php namespace App\Providers; use App\Listeners\QueryListener; use Illuminate\Database\Events\QueryExecuted; use Laravel\Lumen\Providers\EventServiceProvider as ServiceProvider; class EventServiceProvider extends ServiceProvider { /** * The event listener mappings for the application. * * @var array */ protected $listen = [ QueryExecuted::class => [ // 這行 QueryListener::class, // 還有這行 ], ]; }
別忘了註冊 Service Providers
數據庫
// file: bootstrap/app.php' /* |-------------------------------------------------------------------------- | Register Service Providers |-------------------------------------------------------------------------- | | Here we will register all of the application's service providers which | are used to bind services into the container. Service providers are | totally optional, so you are not required to uncomment this line. | */ // $app->register(App\Providers\AppServiceProvider::class); // $app->register(App\Providers\AuthServiceProvider::class); $app->register(App\Providers\EventServiceProvider::class); // 取消對這一行的註釋
接下來就是實操了。首先咱們建立一個控制器bootstrap
//file: app/Http/Controllers/UserController.php namespace App\Http\Controllers; use App\User; class UserController extends Controller { public function one() { return User::where("id", 1)->first(); // 在控制器執行查詢方法 } }
註冊路由app
// file: routes/web.php $router->get('/one', "UserController@one"); // 定義訪問路由
最後別忘了開啓DB功能以及填寫數據庫配置(這一段你們確定都會,我就不貼代碼了)。
那麼咱們來嘗試運行一下:
QueryExecuted {#65 ▼ +sql: "select * from `users` where `id` = ? limit 1" +bindings: array:1 [▶] +time: 2.06 +connection: MySqlConnection {#66 ▶} +connectionName: "mysql" }
發現已經成功了。那麼就能夠執行日誌記錄了。
咱們修改一下代碼:
// file: app/Listeners/QueryListener.php namespace App\Listeners; use Illuminate\Database\Events\QueryExecuted; use Illuminate\Support\Facades\Log; class QueryListener { /** * Create the event listener. * * @return void */ public function __construct() { // } /** * Handle the event. * * @param ExampleEvent $event * @return void */ public function handle(QueryExecuted $event) { $query = $event->sql; // 獲取SQL語句 foreach ($event->bindings as $bind) { $query = preg_replace('/\?/', (is_numeric($bind) ? $bind : '\'' . $bind . '\''), $query, 1); // 將SQL中的?替換爲實際的值 } Log::info("query: {$query} time: {$event->time}ms"); // 將SQL和執行時間打印到日誌 } }
雖然已經實現了SQL紀錄,可是這並非咱們想要的,由於將SQL和錯誤日誌放在一塊兒。閱讀起來很是難受。那麼我就須要放到一個單獨的文件裏面去就行了。
// file: app/Listeners/QueryListener.php namespace App\Listeners; use Illuminate\Database\Events\QueryExecuted; use Illuminate\Http\Request; class QueryListener { /** * 寫入的文件路徑 * @var string */ protected $writeFile; /** * Create the event listener. * * @return void */ public function __construct() { $this->writeFile = storage_path() . DIRECTORY_SEPARATOR . "logs" . DIRECTORY_SEPARATOR . "sql-" . date("Ymd") . ".log"; // 定義輸出的日誌路徑ß } /** * Handle the event. * * @param QueryExecuted $event * @param Request $request * @return void */ public function handle(QueryExecuted $event) { $query = $event->sql; foreach ($event->bindings as $bind) { $query = preg_replace('/\?/', (is_numeric($bind) ? $bind : '\'' . $bind . '\''), $query, 1); } file_put_contents($this->writeFile, "query: {$query} time: {$event->time}ms", FILE_APPEND); // 直接使用 file_put_contents 對內容進行輸入。而且注意 FILE_APPEND 若是不加就變成覆蓋了,這個常量的做用是對文件進行追加寫入。 } }
接下來繼續改進,就是說咱們目前是在全部的環境中進行打印的,可是咱們僅僅須要在開發環境進行調試。因此咱們能夠進行以下修改:
// file: app/Providers/EventServiceProvider.php namespace App\Providers; use App\Listeners\QueryListener; use Illuminate\Database\Events\QueryExecuted; use Laravel\Lumen\Providers\EventServiceProvider as ServiceProvider; class EventServiceProvider extends ServiceProvider { /** * The event listener mappings for the application. * * @var array */ protected $listen = []; public function register() { if (env("APP_ENV") == "local") { // 判斷環境 $events = app('events'); $events->listen(QueryExecuted::class, QueryListener::class); // 手動註冊監聽器 } } }
那麼SQL監聽功能就實現了。
其實 laravel-sql-logger
還有一些高級的顯示功能。好比說打印日誌的時候會順帶着打印請求URL。打印請求時間等。這些我就不這裏具體完善了。若是你們有興趣,能夠本身想辦法實現。很簡單的。