Laravel 项目运行中,有时候需要查看sql语句,分析sql运行的耗时,sql语句的复杂程度分析等等
总之,sql的执行在项目中,非常关键,接下来将说明在laravel 中 配置一个sql日志记录服务。
开发思路:
- 使用 Laravel 事件系统监听 QueryExecuted 事件
- 通过监听服务,将监听到的sql信息记录到日志文件
代码开发
1. 日志通道配置一个记录sql的日志通道
文件:config/logging.php
'channels' => [
'sqllog' => [
'driver' => 'daily',
'path' => storage_path('logs/sql.log'),
'level' => 'debug',
'days' => 7,
'permission' => 0664,
],
];
通道名称 sqllog,驱动程序 daily , path
是路径,level
为等级,days
保留7天,permission
为文件权限
2.事件系统配置监听事件
文件:app/Providers/EventServiceProvider.php
protected $listen = [
QueryExecuted::class => [
LogDBQuery::class,
],
];
-
QueryExecuted::class
是 Laravel 数据库系统中的一个核心事件
,也是 Laravel 数据库层最基础也是最有用
的事件之一,其在数据库完成查询后触发。- 触发的时机:
- SELECT 查询
- INSERT/UPDATE/DELETE 操作
- 事务操作(BEGIN, COMMIT, ROLLBACK)
- 存储过程调用
- 事件对象包含的信息
- public string $sql; // 执行的SQL语句(带占位符)
- public array $bindings; // 绑定的参数值
- public float $time; // 执行时间(毫秒)
- public string $connectionName; // 使用的连接名称(如’mysql’)
- public object $connection; // 数据库连接实例
- 触发的时机:
-
LogDBQuery::class 是自己开发一个监听服务类
3.监听服务开放
目录:app/Listeners,创建类 LogDBQuery.php
<?php
namespace App\Listeners;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Events\QueryExecuted;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class LogDBQuery
{
// 慢查询阈值(毫秒)
protected const SLOW_QUERY_THRESHOLD = 500;
/**
* 处理查询事件
*/
public function handle(QueryExecuted $event): void
{
// 只在本地和测试环境记录
if(!App::environment(['local', 'testing'])){
return;
}
// 随机抽取 10% 的查询日志
if(random_int(1, 100) > 10){
return;
}
$context = $this->buildLogContext($event);
if($event->time > self::SLOW_QUERY_THRESHOLD){
Log::channel('sqllog')->warning("Slow query detected", $context);
}else{
Log::channel('sqllog')->debug("Query executed", $context);
}
}
/**
* 格式化 SQL 语句
*/
protected function formatSql(string $sql, array $bindings): string
{
if(empty($bindings)){
return $sql;
}
$escapedBindings = array_map(fn($value) => $this->escapeBinding($value), $bindings);
return Str::replaceArray('?', $escapedBindings, $sql);
}
/**
* 转义绑定参数
*/
protected function escapeBinding(mixed $value): string
{
if(is_null($value)){
return 'NULL';
}
if(is_bool($value)){
return $value ? '1' : '0';
}
if(is_numeric($value)){
return (string)$value;
}
return "'" . addslashes((string)$value) . "'";
}
/**
* 构建日志上下文
*/
protected function buildLogContext(QueryExecuted $event): array
{
return [
'sql' => $this->formatSql($event->sql, $event->bindings),
'time' => $event->time . 'ms',
'connection' => $event->connectionName,
'bindings' => $event->bindings,
'raw_sql' => $event->sql,
];
}
}
监听类执行流程
1事件触发 → 2. 框架查找监听器 → 3. 实例化监听器 → 4. 调用 handle()
代码解释:
-
SLOW_QUERY_THRESHOLD
添加一个慢查询的阈值,如果查询时间大于阈值,则是慢查询 -
handle
自动执行:
- 当非生产环境,则执行
if(!App::environment(['local', 'testing'])){
return;
}
- 随机抽查 10%的日志记录
if(random_int(1, 100) > 10){
return;
}
- 构建日志上下文数组,记录sql的语句,时间,连接等信息
protected function buildLogContext(QueryExecuted $event): array
{
return [
'sql' => $this->formatSql($event->sql, $event->bindings),
'time' => $event->time . 'ms',
'connection' => $event->connectionName,
'bindings' => $event->bindings,
'raw_sql' => $event->sql,
];
}
- 检测是否是慢查询,记录不同级别的日志,提高日志的可观察性
if($event->time > self::SLOW_QUERY_THRESHOLD){
Log::channel('sqllog')->warning("慢查询SQL", $context);
}else{
Log::channel('sqllog')->debug("执行SQL", $context);
}
完成上述构建,测试一下:
[2025-03-31 14:10:26] local.DEBUG: 执行SQL {"sql":"select count(*) as aggregate from `project_info` where `project_info`.`deleted_at` is null","time":"24.21ms","connection":"mysql","bindings":[],"raw_sql":"select count(*) as aggregate from `project_info` where `project_info`.`deleted_at` is null"}
[2025-03-31 14:10:26] local.DEBUG: 执行SQL {"sql":"select * from `project_info` where `project_info`.`deleted_at` is null order by `id` desc limit 10 offset 0","time":"24.77ms","connection":"mysql","bindings":[],"raw_sql":"select * from `project_info` where `project_info`.`deleted_at` is null order by `id` desc limit 10 offset 0"}
在实际项目中,还可以进行后续的扩展,例如 排除哪个连接 connectionName
不记录,那个 sql 不记录等等。
注意事项:
生产环境慎用
:频繁查询的应用中,监听所有查询可能影响性能
- 抽查监控:可以
只记录部分
查询
知识点总结:
- 日志通道配置
- EventServiceProvider 事件服务类
- QueryExecuted::class 数据库核心类
- Listeners 监听器类的开发
- 执行流程:事件触发 → 2. 框架查找监听器 → 3. 实例化监听器 → 4. 调用 handle()