├── .gitignore ├── LICENSE ├── composer.json ├── readme.md └── src └── QueryLogger ├── QueryLogger.php └── QueryLoggerServiceProvider.php /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /vendor 3 | composer.lock 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Rodrigo Pedra Brum 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rodrigopedra/laravel-query-logger", 3 | "description": "Laravel query logger", 4 | "keywords": [ 5 | "query logger", 6 | "rodrigopedra", 7 | "laravel" 8 | ], 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Rodrigo Pedra Brum", 13 | "email": "rodrigo.pedra@gmail.com" 14 | } 15 | ], 16 | "require": { 17 | "php": "^8.2", 18 | "ext-pdo": "*", 19 | "illuminate/contracts": "^11.0|^12.0", 20 | "illuminate/support": "^11.0|^12.0", 21 | "illuminate/database": "^11.0|^12.0", 22 | "psr/log": "^2|^3" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "RodrigoPedra\\QueryLogger\\": "src/QueryLogger/" 27 | } 28 | }, 29 | "extra": { 30 | "laravel": { 31 | "providers": [ 32 | "RodrigoPedra\\QueryLogger\\QueryLoggerServiceProvider" 33 | ] 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Query Logger for Laravel 2 | 3 | - *for Laravel versions 5.0 and 5.1 use the 1.0 release* 4 | - *for Laravel versions from 5.2 to 7.x with PHP < 7.4 use 2.x release* 5 | 6 | Writes all queries to the log when `APP_DEBUG=true`. 7 | 8 | This package tries to replace the bindings with their values so SQL queries becomes easier to debug. 9 | 10 | ## Installation 11 | 12 | ``` 13 | composer require rodrigopedra/laravel-query-logger --dev 14 | ``` 15 | 16 | ## Configuration 17 | 18 | As releases 3.x+ requires at least Laravel version 5.5, the service provider 19 | should be configured automatically using Laravel's package auto-discovery. 20 | 21 | If you are not using pakacge auto-discovery, you will need to add the provider to your `config/app.php`: 22 | 23 | ```php 24 | // in your config/app.php add the provider to the service providers array 25 | 26 | 'providers' => [ 27 | /* ... */ 28 | 29 | 'RodrigoPedra\QueryLogger\QueryLoggerServiceProvider', 30 | ] 31 | ``` 32 | 33 | ### License 34 | 35 | This package is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT) 36 | -------------------------------------------------------------------------------- /src/QueryLogger/QueryLogger.php: -------------------------------------------------------------------------------- 1 | logger->debug($this->sql($event), [ 18 | 'time' => $event->time, 19 | 'connection' => $event->connectionName, 20 | 'database' => $event->connection->getDatabaseName(), 21 | 'bindings' => $event->bindings, 22 | 'callSpot' => $this->guessCallSpot(), 23 | ]); 24 | } 25 | 26 | protected function sql(QueryExecuted $event): string 27 | { 28 | try { 29 | return $event->toRawSql(); 30 | } catch (\Throwable) { 31 | return $this->toSQL($event); 32 | } 33 | } 34 | 35 | public function toSQL(QueryExecuted $event): string 36 | { 37 | $pdo = \method_exists($event->connection, 'getPdo') 38 | ? $event->connection->getPdo() 39 | : null; 40 | 41 | $dateFormat = $event->connection->getQueryGrammar()->getDateFormat(); 42 | 43 | $bindings = $event->connection->prepareBindings($event->bindings); 44 | $bindings = \array_map(fn ($value) => $this->prepareValue($event, $pdo, $dateFormat, $value), $bindings); 45 | 46 | return $this->prepareQuery($event->sql, $bindings); 47 | } 48 | 49 | protected function prepareQuery(string $query, array $bindings): string 50 | { 51 | foreach ($bindings as $key => $value) { 52 | $regex = \is_numeric($key) 53 | ? "/(?connection, 'escape')) { 65 | try { 66 | return $event->connection->escape($value); 67 | } catch (\Throwable) { 68 | } 69 | } 70 | 71 | if (\is_null($value)) { 72 | return 'NULL'; 73 | } 74 | 75 | if (\is_bool($value)) { 76 | return $value ? '1' : '0'; 77 | } 78 | 79 | if (\is_int($value) || \is_float($value)) { 80 | return \strval($value); 81 | } 82 | 83 | if (\is_string($value) && ! \mb_check_encoding($value, 'UTF-8')) { 84 | return $this->quote($pdo, '[BINARY DATA]'); 85 | } 86 | 87 | if ($value instanceof \DateTimeInterface) { 88 | $value = $value->format($dateFormat); 89 | } 90 | 91 | if ($value instanceof \Stringable) { 92 | $value = \strval($value); 93 | } 94 | 95 | if (\is_object($value) && \method_exists($value, 'toString')) { 96 | $value = $value->toString(); 97 | } 98 | 99 | // objects not implementing __toString() or toString() will fail here 100 | return $this->quote($pdo, \strval($value)); 101 | } 102 | 103 | protected function quote(?\PDO $pdo, string $value): string 104 | { 105 | if ($pdo) { 106 | return $pdo->quote($value); 107 | } 108 | 109 | $search = ["\\", "\x00", "\n", "\r", "'", '"', "\x1a"]; 110 | $replace = ["\\\\", "\\0", "\\n", "\\r", "\'", '\"', "\\Z"]; 111 | 112 | return "'" . \str_replace($search, $replace, $value) . "'"; 113 | } 114 | 115 | protected function guessCallSpot(): array 116 | { 117 | $stack = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS); 118 | $vendor = \DIRECTORY_SEPARATOR . 'vendor' . \DIRECTORY_SEPARATOR; 119 | 120 | foreach ($stack as $trace) { 121 | if (\array_key_exists('file', $trace) && ! \str_contains($trace['file'], $vendor)) { 122 | return Arr::only($trace, ['file', 'line', 'function']); 123 | } 124 | } 125 | 126 | return ['file' => null, 'line' => null, 'function' => null]; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/QueryLogger/QueryLoggerServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->hasDebugModeEnabled()) { 18 | $events->listen(QueryExecuted::class, QueryLogger::class); 19 | } 20 | } 21 | } 22 | --------------------------------------------------------------------------------