laravel-debugbar、Clockwork、Telescope.......
等等等等,telescope是laravel作者開發的,功能強大介面美觀沒得說,但是laravel版本勸退(5.7.7+)
Clockwork和laravel-debugbar都必須是瀏覽器中除錯,postman中就只能打log了....
有時候的sql又真的複雜,開發時檢視orm組裝的sql還是很有必要的,
打log有許多方法,但是我就是想看這一個介面的全部sql,我還不想去翻日誌
想了想就折騰了這段程式碼
思路是這樣的,
1.記日誌(當有指定引數時,往指定的檔案裡寫sql日誌)
給全域性加一個引數,dumpSql
,當有dumpSql引數時,app/Providers/AppServiceProvider.php
中的boot
方法里加DB::listen
往指定的檔案裡寫sql日誌
2.記日誌之前清空該指定檔案(保證每次記錄的都是當前請求介面的sql日誌)
這樣就保證每次請求的api中的sql資訊都是新的
3.響應中讀取該指定檔案,並將讀取的內容組成陣列,將該陣列放入響應中
這裡我用的是後置中介軟體,可以很方便的格式化響應資料,可以看這裡:小白折騰伺服器(七):自定義介面錯誤響應格式
思路很簡單,看程式碼吧~
在app/Providers/AppServiceProvider.php
中新增程式碼:
public function boot()
{
$filename = storage_path('/logs/sql.log');
if (file_exists($filename)) {
unlink($filename);
}
// 只在本地開發環境啟用 SQL 日誌
if (app()->environment(['local', 'dev']) && request('dumpSql', 0)) {
DB::listen(function ($query) use ($filename) {
// $sql = vsprintf(str_replace("?", "'%s'", $query->sql), $query->bindings);
$sql = Str::replaceArray('?', $query->bindings, $query->sql);
$sqlArr = [
'date' => (string)now(),
'sql' => $sql,
'time' => $query->time,
];
$logPath = storage_path('/logs/');
if (!file_exists($logPath)) {
mkdir($logPath, 0777, true);
}
file_put_contents($filename, json_encode($sqlArr, 320) . PHP_EOL, FILE_APPEND);
});
}
}
在後置中介軟體中加一個私有方法:
這個方法是按行讀取指定檔案,並將內容裝進陣列
protected function readFileByLine($filename)
{
$fh = fopen($filename, 'r');
$sql = [];
while (!feof($fh)) {
$sql[] = json_decode(fgets($fh), true);
}
fclose($fh);
if (is_null(end($sql))) {
array_pop($sql);
}
return $sql;
}
修改後置中介軟體:
後置中介軟體格式化響應的時候,加上這3行程式碼,
當有dumpSql
引數 && 存在指定sql日誌檔案時,拼接sql響應引數。
$sqlFilename = storage_path('/logs/sql.log');
if (request('dumpSql', 0) && file_exists($sqlFilename)) {
$data['sql'] = $this->readFileByLine($sqlFilename);
}
可以看下效果:
全部的後置中介軟體程式碼如下:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\JsonResponse;
use Illuminate\Pagination\LengthAwarePaginator;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class After
{
/**
* 處理成功返回自定義格式
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
*
* @return mixed
*/
public function handle($request, Closure $next)
{
$response = $next($request);
if (is_array($response)) {
return $response;
}
// 如果是匯出Excel型別直接返回
if ($response instanceof BinaryFileResponse) {
return $response;
}
// 執行動作
$oriData = $response->getOriginalContent();
$content = json_decode($response->getContent(), true) ?? $oriData;
$content = is_array($oriData) ? $oriData : $content;
if ($content['code'] ?? 0) {
return $response;
}
$data['data'] = isset($content['data']) ? $content['data'] : $content;
if ($content['meta'] ?? []) {
$data['meta'] = [
'total' => $content['meta']['total'],
'page' => $content['meta']['page'] ?? $content['meta']['current_page'] ?? 0,
'size' => $content['meta']['size'] ?? $content['meta']['per_page'] ?? 0,
];
}
if ($oriData instanceof LengthAwarePaginator) {
$data['meta'] = [
'total' => $content['total'],
'page' => $content['current_page'],
'size' => (int)$content['per_page'],
];
}
if ($data['data']['data'] ?? []) {
$data['data'] = $data['data']['data'];
}
$sqlFilename = storage_path('/logs/sql.log');
if (request('dumpSql', 0) && file_exists($sqlFilename)) {
$data['sql'] = $this->readFileByLine($sqlFilename);
}
$message = ['code' => 0, 'message' => 'success', 'data' => []];
$temp = ($content) ? array_merge($message, $data) : $message;
$response = $response instanceof JsonResponse ? $response->setData($temp) : $response->setContent($temp);
return $response;
}
/**
* @param $filename
*
* @return array
*/
protected function readFileByLine($filename)
{
$fh = fopen($filename, 'r');
$sql = [];
while (!feof($fh)) {
$sql[] = json_decode(fgets($fh), true);
}
fclose($fh);
if (is_null(end($sql))) {
array_pop($sql);
}
return $sql;
}
}
自己寫的小方法,最大的好處是自己熟悉,少依賴,如果用別人的包,可能一更新還得等作者更新,
當然最大的好處是比較方便啦^_^
一定一定要加 app()->environment(['local', 'dev'])
這個判斷呦