Laravel 4 - 记录SQL查询

35

关于在Laravel 4中记录SQL查询的问题已经有几个提问了。但我已经尝试了几乎所有的方法,仍然不能满足我的需求。

这是我的情况:

  1. 在我的PHP视图文件中,我向服务器发出AJAX请求
  2. 接收到AJAX请求后,会运行一个原始参数化的Postgres SQL查询(例如:

    DB::select('select * from my_table where id=?', array(1))

如果我使用

Event::listen('illuminate.query', function($sql)
{
  Log::error($sql);
});

我只是收到“select * from my_table where id=?”作为日志消息,而实际上没有填充ID值。

如果我使用

$queries = DB::getQueryLog();
$last_query = end($queries);
Log::error(print_r($last_query, true));

我仍然没有包含ID的最终SQL查询。

最后,如果我使用像https://github.com/loic-sharma/profiler这样的日志记录工具 - 它不会显示任何内容,因为我正在进行AJAX请求。

我已经尽力了吗?还有其他更好的方法吗?


我认为你可以将$bindings作为Event::listen调用的函数的第二个参数传递。因此,... , function($sql, $bindings) { ... - Jeemusu
我相信 PDO 是 Laravel 所有查询的基础,它从未给 PHP 机会看到最终运行的 SQL。它分别提供 SQL 和绑定变量,这就是为什么 Laravel 的查询日志器显示绑定变量是分开的 - PHP 看不到最终的东西。在最好的情况下,我们只能尝试使用原始查询并自己填充绑定变量来重构它。当然,MySQL 和其他数据库都有自己的查询日志器,如果需要可以使用它们(它们通常会减慢数据库的速度,所以只用于测试:D)。 - fideloper
我实际上使用loic-sharma和AJAX请求。我在Laravel中添加了一个过滤器,以便将分析器数据附加到所有JsonResponses。然后,使用jQuery的ajaxSetupdataFilter,我会自动更新每个json响应上的分析器HTML。如果您有兴趣,我可以添加一个带有代码的答案。 - Matteus Hemström
是的,那将会很好! - ericbae
@ericbae 在 ajax-profiler-thing 上添加了一个答案。希望它有用。这是一个相当酷的开发功能 :) - Matteus Hemström
如果您正在使用 Laravel 5,请参阅此帖子:https://dev59.com/el4c5IYBdhLWcg3wl7Mg#34638344(附注:我很愚蠢,在使用5.2时尝试了这里的方法,希望其他人不要犯同样的错误) - ch271828n
6个回答

56

这是我目前用于记录SQL查询的代码。您可以将其放入主路由文件中,然后在数据库配置中添加“log”=> true。

if (Config::get('database.log', false))
{           
    Event::listen('illuminate.query', function($query, $bindings, $time, $name)
    {
        $data = compact('bindings', 'time', 'name');

        // Format binding data for sql insertion
        foreach ($bindings as $i => $binding)
        {   
            if ($binding instanceof \DateTime)
            {   
                $bindings[$i] = $binding->format('\'Y-m-d H:i:s\'');
            }
            else if (is_string($binding))
            {   
                $bindings[$i] = "'$binding'";
            }   
        }       

        // Insert bindings into query
        $query = str_replace(array('%', '?'), array('%%', '%s'), $query);
        $query = vsprintf($query, $bindings); 

        Log::info($query, $data);
    });
}

感谢Jeemusu的答案提到了将绑定变量插入预处理语句中。


2
有没有办法记录我们想要的特定查询,而不是所有查询? - ericbae
1
记录到默认的Laravel日志文件。我如何记录到仅用于查询的文件中?例如,记录到app/storage/logs/sql.txt文件中。 - ejunker
1
我修复了这行代码: $query = str_replace(array('%', '?'), array('%%', ''%s''), $query); 因此,你会在单引号内有字符串。 - bonaccorso.p
很棒的答案。但我想知道为什么这个要放在routes.php文件里? - KingKongFrog
@KingKongFrog,路由文件只是在任何应用程序逻辑执行之前始终加载的东西。也许start/global.php更合适。 - Collin James
FYI,这是一个非常好的解决方案,但如果您的查询包含本来就有%或?的字符串,比如在“car.com/index.php?getCar=17”中,它将会出现错误! - Sliq

11

你应该能够通过将$bindings作为Event函数的第二个参数来找到这些绑定。

Event::listen('illuminate.query', function($sql, $bindings, $time){
    echo $sql;          // select * from my_table where id=? 
    print_r($bindings); // Array ( [0] => 4 )
    echo $time;         // 0.58 

    // To get the full sql query with bindings inserted
    $sql = str_replace(array('%', '?'), array('%%', '%s'), $sql);
    $full_sql = vsprintf($sql, $bindings);
});

在 Laravel 3.x 中,我认为事件监听器被称为 laravel.query


9

继续@Collin James的回答

如果你想要仅为sql记录到一个独立的文件中,可以使用以下方法:

if (Config::get('database.log', false)) {
    Event::listen('illuminate.query', function($query, $bindings, $time, $name) {
        $data = compact('bindings', 'time', 'name');

        // Format binding data for sql insertion
        foreach ($bindings as $i => $binding) {
            if ($binding instanceof \DateTime) {
                $bindings[$i] = $binding->format('\'Y-m-d H:i:s\'');
            } else if (is_string($binding)) {
                $bindings[$i] = "'$binding'";
            }
        }

        // Insert bindings into query
        $query = str_replace(array('%', '?'), array('%%', '%s'), $query);
        $query = vsprintf($query, $bindings);

        $log = new Logger('sql');
        $log->pushHandler(new StreamHandler(storage_path().'/logs/sql-' . date('Y-m-d') . '.log', Logger::INFO));

        // add records to the log
        $log->addInfo($query, $data);
    });
}

在文件顶部加上以下内容:

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

这将把所有的查询记录到一个名为sql-YYYY-mm-dd.log的文件中,位置在storage/logs/目录下。


6
虽然被接受的答案是正确的,但这个答案解释了如何在使用jQuery进行Ajax请求时更新loic-sharma profiler。使用这种方法,不需要阅读文件日志。

步骤1

第一个问题是在每个Ajax请求中向客户端发送更新的分析器数据。可以通过Laravel应用程序的“after”事件来解决此问题。

app/filters.php:

App::after(function($request, $response)
{
    // If it's a JsonResponse and the profiler is enabled, include it in the response.
    if($response instanceof \Illuminate\Http\JsonResponse && Profiler::isEnabled()) {

        $profiler = Profiler::getFacadeRoot();
        $profilerJson = json_encode($profiler->render());
        $content = substr($response->getContent(), 0, -1) . ',"profiler":' . $profilerJson . '}';
        $response->setContent($content);
    }
});
App::after 过滤器将在每个 Laravel 请求上运行。上面闭包的第一行确保它只会继续执行,如果响应是 JsonResponse 类型并且启用了分析器。如果是这种情况,则呈现分析器并将 HTML 附加到 JSON 对象中。 注意:此代码假定返回的 JSON 是对象。因此,对于数组,它将失败:Response::json(array(1,2,3))

步骤2

现在,更新后的分析器 HTML 正在发送到客户端,我们必须使用 JavaScript 更新 DOM 中的新分析器 HTML。每次客户端收到 JSON 响应时都应该发生这种情况。jQuery 提供全局 Ajax 事件处理程序,非常适合实现此目的。
$(document).ajaxSuccess(function(event, request, settings) {
    try {
        json = jQuery.parseJSON(request.responseText);
        if(json.profiler) {
            renderProfiler(json.profiler);
        }
    } catch(error) {
        // Its not JSON.
        return;
    }
});

以下是将旧的分析工具替换为新分析工具的方法:

以下是将旧的分析工具替换为新分析工具的方法:

renderProfiler = function(data) {
    // Remove previous 
    $anbu = $('.anbu');
    $anbu.prev().remove(); // Removes <style> tag of profiler
    $anbu.next().next().remove(); // Removes the inline script tag of profiler
    $anbu.next().remove(); // Removes jQuery script tag by the profiler
    $anbu.remove(); // Removes the <div> tag of profiler
    $(document.body).append(data);
};

使用方法

现在只需将响应返回为以下内容:

return Response::json(array(
    'user' => Auth::user()
));

Laravel会追加性能分析器的HTML代码。Javascript将捕获JSON响应并更新DOM。您将在网页上看到SQL查询和时间。

注意

虽然代码经过测试,但可能会有一两个错误。这也不是我实际操作的完全方式。我不是在JSON响应中发送HTML,而是使用性能分析器的实际数据扩展对象。在客户端,我使用mustache模板来渲染性能分析器。


好的,但是我应该把jQuery代码放在哪里呢?因为我只是在测试,所以还没有任何视图。目前,我的控制器路由直接返回模型,这很好,因为我安装了一个Chrome扩展程序(JSONView),可以获得格式良好的JSON。但我想看到获取它们所需的查询! - Kenmore
@Zuko 这个答案使用了loic-sharma分析器将分析输出渲染为HTML。在你的情况下,这不是你想要的。你可以直接提供来自分析器的原始数据,而不是将分析器数据嵌入到HTML中。如果你这样做,你可以跳过_步骤2_。请参考这个例子(一个_步骤1_的改编)。 - Matteus Hemström

2

虽然这个问题最初是针对Laravel 4的,但我通过谷歌搜索到了这里,但我正在使用Laravel 5。

使用中间件记录所有查询的新方法在Laravel 5中已经出现,但如果您喜欢相同的方法,这里提供了与Laravel 5兼容的代码,由Collin James提供。

if (Config::get('database.log', false))
{
    Event::listen('Illuminate\Database\Events\QueryExecuted', function($query)
    {
        $bindings = $query->bindings;
        $time = $query->time;
        $name = $query->connection->getName();
        $data = compact('bindings', 'time', 'name');

        // Format binding data for sql insertion
        foreach ($bindings as $i => $binding)
        {
            if ($binding instanceof \DateTime)
            {
                $bindings[$i] = $binding->format('\'Y-m-d H:i:s\'');
            }
            else if (is_string($binding))
            {
                $bindings[$i] = "'$binding'";
            }
        }

        // Insert bindings into query
        $query = str_replace(array('%', '?'), array('%%', '%s'), $query->sql);
        $query = vsprintf($query, $bindings);

        Log::info($query, $data);
    });
}

1
这是我一直在使用的内容:
DB::listen(function ($query, $bindings, $time, $connection) {
    $fullQuery = vsprintf(str_replace(array('%', '?'), array('%%', '%s'), $query), $bindings);
    $result = $connection . ' (' . $time . '): ' . $fullQuery;

    dump($result);
    // You can of course log the $result
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接