当前位置: 首页 > news >正文

Laraver SQL日志 服务开发

Laravel 项目运行中,有时候需要查看sql语句,分析sql运行的耗时,sql语句的复杂程度分析等等

总之,sql的执行在项目中,非常关键,接下来将说明在laravel 中 配置一个sql日志记录服务。

开发思路:

  1. 使用 Laravel 事件系统监听 QueryExecuted 事件
  2. 通过监听服务,将监听到的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()

代码解释

  1. SLOW_QUERY_THRESHOLD 添加一个慢查询的阈值,如果查询时间大于阈值,则是慢查询

  2. 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 不记录等等。

注意事项

  1. 生产环境慎用:频繁查询的应用中,监听所有查询可能影响性能
  2. 抽查监控:可以只记录部分查询

知识点总结

  1. 日志通道配置
  2. EventServiceProvider 事件服务类
  3. QueryExecuted::class 数据库核心类
  4. Listeners 监听器类的开发
  5. 执行流程:事件触发 → 2. 框架查找监听器 → 3. 实例化监听器 → 4. 调用 handle()

相关文章:

  • wsl2配置proxy
  • git配置github
  • [c语言日寄]文件操作
  • OpenAI发布PaperBench,AI代理复现研究能力面临新考验
  • Ubuntu 22.04 一键部署openManus
  • 轻量级搜索接口技术解析:快速实现关键词检索的Java/Python实践
  • 最新全开源码支付系统,赠送3套模板
  • 深度学习基础
  • 在线Pdf文档转换成Excel文档,无需下载,快速转换,批量转换
  • 再来1章linux 系列-0. C语言过、Java半静对、Python纯动和C++对+C
  • 代码随想录算法训练营第三十五天 | 416.分割等和子集
  • 32、web前端开发之JavaScript(一)
  • 烈火烹油的金三银四
  • 2024年蓝桥杯Java B组省赛真题超详解析-类斐波那契循环数
  • 数据结构:链表 (C++实现)
  • 最短路径问题
  • Selenium 元素定位方法详解
  • 在WSL中高效使用Windows目录下的Ollama模型
  • 如何在最新的 Mac mini M4 机器上,让 Ollama 支持局域网访问
  • 91%准确率预测耀斑!国家天文台推出太阳大模型“金乌”,推动天文研究进入AI时代
  • 织梦大气金融类通用企业网站模板/企业seo如何优化
  • 建设网站基本步骤/关键词一般是指什么
  • 做网站空间重要还是程序重要/外汇交易平台
  • 小说主角重生之后做网站/广州新一期lpr
  • 什么叫网站根目录/最新新闻
  • 做网站的品牌公司有哪些/大型网站建设