自定义日志
自定义日志格式
App\Services\QueryFormatter
<?php
namespace App\Services;
use Illuminate\Log\Logger;
class QueryFormatter
{
public function __invoke(Logger $logger)
{
$mongoLogger = $logger->getLogger();
foreach ($mongoLogger->getHandlers() as $handler) {
$handler->setFormatter(new QueryFormat());
}
}
}
App\Services\QueryFormat
<?php
namespace App\Services;
use Illuminate\Support\Str;
use Monolog\Formatter\LineFormatter;
class QueryFormat extends LineFormatter
{
public function format(array $recode): string
{
$vars = $this->normalize($recode);
$queries = data_get($vars, 'context.query');
if (empty($queries)) {
return '';
}
$extra = data_get($vars, 'context.extra');
$output = "[ SQL ] [ ? ] [ ? ] [ RunTime:? ms ]\n";
$line = '';
$totalRuntime=0;
foreach ($queries as $key => $query) {
if (!isset($query['bindings']) || !isset($query['sql']) || !isset($query['time'])) {
return $line;
}
$sql = Str::replaceArray('?', $query['bindings'], $query['sql']);
$line .= Str::replaceArray('?', [$key + 1, $sql, $query['time']], $output);
}
$str = str_repeat('-', 60) . "\n";
$header = '';
if (!empty($extra)) {
$headerFormat = "[?] ? ? ?\n";
$header = Str::replaceArray('?', array_values($extra), $headerFormat);
}
return $header . $line . $str;
}
}
自定义的 Monolog 通道
config/loggin.php
添加
'sql' => [
'driver' => 'daily',
'path' => storage_path('logs/sql/sql.log'),
'level' => 'debug',
'days' => 14,
'tap' => [App\Services\QueryFormatter::class],
],
'cli' => [
'driver' => 'daily',
'path' => storage_path('logs/sql/cli.log'),
'level' => 'debug',
'days' => 14,
'tap' => [App\Services\QueryFormatter::class],
],
注册QueryListen监听器
<?php
namespace App\Providers;
use App\Contracts\QueryListen;
use App\Services\QueryListenService;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
$this->app->singleton(QueryListen::class, function () {
return new QueryListenService();
});
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
//
//监听DB Query
DB::listen(function ($query) {
$listener = $this->app->make(QueryListen::class);
$listener->push([
'time' => $query->time,
'sql' => $query->sql,
'bindings' => $query->bindings,
]);
});
}
}
SQL契约
app/Contracts/QueryListen.php
<?php
namespace App\Contracts;
interface QueryListen
{
public function push(array $query=[]);
public function write(array $extra=[]);
}
SQL监听服务类
app/Services/QueryListenService.php
<?php
namespace App\Services;
use App\Contracts\QueryListen;
use Illuminate\Support\Facades\Log;
class QueryListenService implements QueryListen
{
private $query;
private static $instance;
public function __construct()
{
$this->query = collect();
}
public static function getInstance()
{
$instance = self::$instance;
if (empty($instance)) {
$instance = new static();
self::$instance = $instance;
}
return $instance;
}
public function push(array $query = [])
{
$this->query->push($query);
}
public function write(array $extra = [])
{
$channel = is_cli() ? 'cli' : 'sql';
Log::channel($channel)->info('', [
'query' => $this->query->toArray(),
'extra' => $extra,
]);
}
}
注册全局中件间
QueryListenerMiddleware
<?php
namespace App\Http\Middleware;
use App\Contracts\QueryListen;
use Closure;
use Illuminate\Http\Request;
class QueryListenerMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
$service = resolve(QueryListen::class);
$time = time();
$response = $next($request);
$service->write([
'time' => date('Y-m-d H:i:s', $time),
'ip' => $request->getClientIp(),
'method' => is_cli() ? 'cli' : $request->method(),
'url' => $request->getHost() . $request->getRequestUri(),
]);
return $response;
}
}
全局路由
app/Http/Kernel.php
的$middleware
属性加入一行
\App\Http\Middleware\QueryListenerMiddleware::class,
基本完成
运行控制器进行CURD操作,能看到类似效果
在storage\logs\sql
目录下生成sql-2020-09-18.log
类似文件
[2020-09-18 14:07:49] 127.0.0.1 GET www.demo.com/query
[ SQL ] [ 1 ] [ select * from `wja_user` where `la_user`.`user_id` = 1 limit 1 ] [ RunTime:8.3 ms ]
[ SQL ] [ 2 ] [ select * from `wja_user` where `la_user`.`user_id` = 2 limit 1 ] [ RunTime:0.63 ms ]
在控制台命令模式下没有记录,所以不要灰心,继续折腾,几经周折,在artisan
文件修改如下:
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
$time = time();
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput,
$output = new Symfony\Component\Console\Output\ConsoleOutput
);
/*
|--------------------------------------------------------------------------
| Shutdown The Application
|--------------------------------------------------------------------------
|
| Once Artisan has finished running, we will fire off the shutdown events
| so that any final work may be done by the application before we shut
| down the process. This is the last thing to happen to the request.
|
*/
$kernel->terminate($input, $status);
//获取SQL监听器
$queryListen = $app->make(\App\Contracts\QueryListen::class);
//写入监听到的SQL日志
$queryListen->write([
'time' => date('Y-m-d H:i:s', $time),
'ip' => '127.0.0.1',
'method' => '',
'cmd' => implode(' ', $_SERVER['argv']),
]);
exit($status);
最效效果
在storage\logs\sql
目录下发现cli-2020-09-18.log
类似文件
打开cli-2020-09-18.log
------------------------------------------------------------
[2020-09-18 14:07:24] 127.0.0.1 artisan test
[ SQL ] [ 1 ] [ select `username` from `wja_user` where `user_id` = 1 limit 1 ] [ RunTime:4.85 ms ]
------------------------------------------------------------
备注
在routes\console.php
下注册测试命令
Artisan::command('test', function () {
$userID = \App\Models\Users::where('user_id', 1)->value('username');
$this->comment($userID);
})->purpose("test cmd");
`