Skip to content

Commit f25ada6

Browse files
authored
Merge pull request #4 from pascalbaljet/report-query-callback
Optional callback to filter queries that are reported in the `QueryLogWritten` event
2 parents 4d88fff + 96c684e commit f25ada6

File tree

13 files changed

+193
-123
lines changed

13 files changed

+193
-123
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,14 @@ jobs:
88
strategy:
99
fail-fast: true
1010
matrix:
11-
php: [8.3, 8.2, 8.1]
11+
php: [8.3, 8.2]
1212
laravel: [11.*, 10.*]
1313
stability: [prefer-lowest, prefer-stable]
1414
include:
1515
- laravel: 11.*
1616
testbench: 9.*
1717
- laravel: 10.*
1818
testbench: 8.*
19-
exclude:
20-
- laravel: 11.*
21-
php: 8.1
2219

2320
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
2421

README.md

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,16 @@ It reports a lot of metadata like total query count, total execution time, origi
1919
```bash
2020
$ composer require onlime/laravel-sql-reporter --dev
2121
```
22-
in console to install this module (Notice `--dev` flag - it's recommended to use this package only for development).
22+
in console to install this module (Notice `--dev` flag - it's recommended to use this package only for development).
2323

2424
Laravel uses package auto-discovery, and it will automatically load this service provider, so you don't need to add anything into the `providers` section of `config/app.php`.
25-
25+
2626
2. Run the following in your console to publish the default configuration file:
27-
27+
2828
```bash
2929
$ php artisan vendor:publish --provider="Onlime\LaravelSqlReporter\Providers\ServiceProvider"
3030
```
31-
31+
3232
By default, you should not edit published file because all the settings are loaded from `.env` file by default.
3333

3434
3. In your `.env` file add the following entries:
@@ -48,7 +48,7 @@ It reports a lot of metadata like total query count, total execution time, origi
4848
SQL_REPORTER_FORMAT_HEADER_FIELDS="origin,datetime,status,user,env,agent,ip,host,referer"
4949
SQL_REPORTER_FORMAT_ENTRY_FORMAT="-- Query [query_nr] [[query_time]]\\n[query]"
5050
```
51-
51+
5252
and adjust values to your needs. You can skip variables for which you want to use default values.
5353

5454
To only log DML / modifying queries like `INSERT`, `UPDATE`, `DELETE`, but not logging any updates on
@@ -58,17 +58,19 @@ It reports a lot of metadata like total query count, total execution time, origi
5858
SQL_REPORTER_QUERIES_INCLUDE_PATTERN="/^(?!SELECT).*/i"
5959
SQL_REPORTER_QUERIES_EXCLUDE_PATTERN="/^UPDATE.*(last_visit|remember_token)/i"
6060
```
61-
61+
6262
If you have also `.env.example` it's recommended to add those entries also in `.env.example` file just to make sure everyone knows about those env variables. Be aware that `SQL_REPORTER_DIRECTORY` is directory inside storage directory.
63-
63+
6464
To find out more about those setting please take a look at [Configuration file](config/sql-reporter.php)
65-
65+
6666
4. Make sure directory specified in `.env` file exists in storage path, and you have valid permissions to create and modify files in this directory (If it does not exist this package will automatically create it when needed, but it's recommended to create it manually with valid file permissions)
6767

6868
5. Make sure on live server you will set logging SQL queries to false in your `.env` file: `SQL_REPORTER_QUERIES_ENABLED=false`. This package is recommended to be used only for development to not impact production application performance.
6969

7070
## Optional
7171

72+
### GeoIP support
73+
7274
For optional GeoIP support (adding country information to client IP in log headers), you may install [stevebauman/location](https://github.com/stevebauman/location) in your project:
7375

7476
```bash
@@ -78,6 +80,24 @@ $ php artisan vendor:publish --provider="Stevebauman\Location\LocationServicePro
7880

7981
It will be auto-detected, no configuration needed for this. If you wish to use a different driver than the default [IpApi](https://ip-api.com/), e.g. `MaxMind` make sure you correctly configure it according to the docs: [Available Drivers](https://github.com/stevebauman/location#available-drivers)
8082

83+
### `QueryLogWritten` event
84+
85+
This package fires a `QueryLogWritten` event after the log file has been written. You may use this event to further debug or analyze the logged queries in your application. The queries are filtered by the `SQL_REPORTER_QUERIES_REPORT_PATTERN` setting, which comes with a sensible default to exclude `SELECT` queries and some default tables like `sessions`, `jobs`, `bans`, `logins`. If you don't want to filter any queries, you may leave this setting empty.
86+
87+
In addition to the pattern, you may also configure a callback to define your own custom filtering logic, for example, in your `AppServiceProvider`:
88+
89+
```php
90+
use Onlime\LaravelSqlReporter\SqlQuery;
91+
use Onlime\LaravelSqlReporter\Writer;
92+
93+
Writer::shouldReportQuery(function (SqlQuery $query) {
94+
// Only include queries in the `QueryLogWritten` event that took longer than 100ms
95+
return $query->time > 100;
96+
});
97+
```
98+
99+
With the `SqlQuery` object, you have access to both `$rawQuery` and the (unprepared) `$query`/`$bindings`. The filter possibilities by providing a callback to `Writer::shouldReportQuery()` are endless!
100+
81101
## Development
82102
83103
Checkout project and run tests:
@@ -87,10 +107,10 @@ $ git clone https://github.com/onlime/laravel-sql-reporter.git
87107
$ cd laravel-sql-reporter
88108
$ composer install
89109
90-
# run unit tests
91-
$ vendor/bin/phpunit
110+
# run both Feature and Unit tests
111+
$ vendor/bin/pest
92112
# run unit tests with coverage report
93-
$ XDEBUG_MODE=coverage vendor/bin/phpunit
113+
$ vendor/bin/pest --coverage
94114
```
95115
96116
## FAQ
@@ -101,7 +121,7 @@ This package was inspired by [mnabialek/laravel-sql-logger](https://github.com/m
101121

102122
- Query logging is not triggered upon each query execution but instead at a final step, using `RequestHandled` and `CommandFinished` events.
103123
- This allows us to include much more information about the whole query executions like total query count, total execution time, and very detailed header information like origin (request URL/console command), authenticated user, app environment, client browser agent / IP / hostname.
104-
- This package is greatly simplified and only provides support for Laravel 10+ / PHP 8.1+
124+
- This package is greatly simplified and only provides support for Laravel 10+ / PHP 8.2+
105125
- It uses the Laravel built-in query logging (`DB::enableQueryLog()`) which logs all queries in memory, which should perform much better than writing every single query to the log file.
106126
- By default, `onlime/laravel-sql-reporter` produces much nicer log output, especially since we only write header information before the first query.
107127

@@ -152,8 +172,8 @@ All changes are listed in [CHANGELOG](CHANGELOG.md)
152172
153173
## Caveats
154174
155-
- If your application crashes, this package will not log any queries, as logging is only triggered at the end. As alternative, you could use [mnabialek/laravel-sql-logger](https://github.com/mnabialek/laravel-sql-logger) which triggers sql logging on each query execution.
156-
- It's currently not possible to log slow queries into a separate logfile. I wanted to keep that package simpel.
175+
- If your application crashes, this package will not log any queries, as logging is only triggered at the end of the request cycle. As alternative, you could use [mnabialek/laravel-sql-logger](https://github.com/mnabialek/laravel-sql-logger) which triggers sql logging on each query execution.
176+
- It's currently not possible to log slow queries into a separate logfile. I wanted to keep that package simple.
157177
158178
## TODO
159179

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
}
1515
],
1616
"require": {
17-
"php": "^8.1",
17+
"php": "^8.2",
1818
"illuminate/support": "^10.15|^11.0",
1919
"illuminate/filesystem": "^10.15|^11.0",
2020
"illuminate/container": "^10.15|^11.0"

src/Config.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public function queriesExcludePattern(): string
8888
*/
8989
public function queriesReportPattern(): string
9090
{
91-
return $this->repository->get('sql-reporter.queries.report_pattern');
91+
return $this->repository->get('sql-reporter.queries.report_pattern') ?: '';
9292
}
9393

9494
/**

src/Formatter.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ public function __construct(
2323
public function getLine(SqlQuery $query): string
2424
{
2525
$replace = [
26-
'[query_nr]' => $query->number(),
26+
'[query_nr]' => $query->number,
2727
'[datetime]' => Carbon::now()->toDateTimeString(),
28-
'[query_time]' => $this->time($query->time()),
28+
'[query_time]' => $this->time($query->time),
2929
'[query]' => $this->getQueryLine($query),
3030
'[separator]' => $this->separatorLine(),
3131
'\n' => PHP_EOL,
@@ -115,7 +115,7 @@ protected function originLine(): string
115115
*/
116116
protected function getQueryLine(SqlQuery $query): string
117117
{
118-
return $query->rawQuery().';';
118+
return $query->rawQuery.';';
119119
}
120120

121121
/**

src/Providers/SqlReporterServiceProvider.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class SqlReporterServiceProvider extends ServiceProvider
1717
/**
1818
* {@inheritdoc}
1919
*/
20-
public function register()
20+
public function register(): void
2121
{
2222
$this->config = $this->app->make(Config::class);
2323

@@ -31,7 +31,7 @@ public function register()
3131
/**
3232
* {@inheritdoc}
3333
*/
34-
public function boot()
34+
public function boot(): void
3535
{
3636
$this->publishes([
3737
$this->configFileLocation() => config_path('sql-reporter.php'),

src/SqlLogger.php

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,11 @@
22

33
namespace Onlime\LaravelSqlReporter;
44

5+
use Illuminate\Support\Collection;
56
use Illuminate\Support\Facades\DB;
67

78
class SqlLogger
89
{
9-
/**
10-
* Number of executed queries.
11-
*/
12-
private int $queryNumber = 0;
13-
1410
/**
1511
* SqlLogger constructor.
1612
*/
@@ -24,11 +20,22 @@ public function __construct(
2420
*/
2521
public function log(): void
2622
{
27-
foreach (DB::getRawQueryLog() as $query) {
28-
$this->writer->writeQuery(
29-
new SqlQuery(++$this->queryNumber, $query['raw_query'], $query['time'])
30-
);
31-
}
23+
$queryLog = DB::getQueryLog();
24+
25+
// getQueryLog() and getRawQueryLog() have the same keys
26+
// see \Illuminate\Database\Connection::getRawQueryLog()
27+
Collection::make(DB::getRawQueryLog())
28+
->map(fn (array $query, int $key) => new SqlQuery(
29+
$key + 1,
30+
$query['raw_query'],
31+
$query['time'],
32+
$queryLog[$key]['query'],
33+
$queryLog[$key]['bindings']
34+
))
35+
->each(function (SqlQuery $query) {
36+
$this->writer->writeQuery($query);
37+
});
38+
3239
$this->writer->report();
3340
}
3441
}

src/SqlQuery.php

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,24 @@
22

33
namespace Onlime\LaravelSqlReporter;
44

5-
class SqlQuery
5+
readonly class SqlQuery
66
{
77
public function __construct(
8-
private int $number,
9-
private string $rawQuery,
10-
private float $time
8+
public int $number,
9+
public string $rawQuery,
10+
public float $time,
11+
public string $query,
12+
public array $bindings = []
1113
) {
1214
}
1315

14-
/**
15-
* Get query number.
16-
*/
17-
public function number(): int
18-
{
19-
return $this->number;
20-
}
21-
22-
/**
23-
* Get raw SQL query with embedded bindings.
24-
*/
25-
public function rawQuery(): string
26-
{
27-
return $this->rawQuery;
28-
}
29-
30-
/**
31-
* Get query execution time.
32-
*/
33-
public function time(): float
34-
{
35-
return $this->time;
16+
public static function make(
17+
int $number,
18+
string $rawQuery,
19+
float $time,
20+
?string $query = null,
21+
array $bindings = []
22+
): self {
23+
return new self($number, $rawQuery, $time, $query ?? $rawQuery, $bindings);
3624
}
3725
}

src/Writer.php

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Onlime\LaravelSqlReporter;
44

5+
use Closure;
56
use Onlime\LaravelSqlReporter\Events\QueryLogWritten;
67

78
class Writer
@@ -22,21 +23,34 @@ class Writer
2223
*/
2324
private array $reportQueries = [];
2425

26+
/**
27+
* Callback to determine whether query should be reported.
28+
*/
29+
private static ?Closure $shouldReportQuery = null;
30+
2531
public function __construct(
26-
private Formatter $formatter,
27-
private Config $config,
28-
private FileName $fileName
32+
private readonly Formatter $formatter,
33+
private readonly Config $config,
34+
private readonly FileName $fileName
2935
) {
3036
}
3137

38+
/**
39+
* Set callback to determine whether query should be reported.
40+
*/
41+
public static function shouldReportQuery(callable $callback): void
42+
{
43+
self::$shouldReportQuery = $callback;
44+
}
45+
3246
/**
3347
* Write a query to log.
3448
*
3549
* @return bool true if query was written to log, false if skipped
3650
*/
3751
public function writeQuery(SqlQuery $query): bool
3852
{
39-
$this->createDirectoryIfNotExists($query->number());
53+
$this->createDirectoryIfNotExists($query->number);
4054

4155
if ($this->shouldLogQuery($query)) {
4256
if ($this->loggedQueryCount === 0) {
@@ -60,9 +74,15 @@ public function writeQuery(SqlQuery $query): bool
6074
/**
6175
* Verify whether query should be reported.
6276
*/
63-
private function shouldReportSqlQuery(SqlQuery $query): bool
77+
public function shouldReportSqlQuery(SqlQuery $query): bool
6478
{
65-
return preg_match($this->config->queriesReportPattern(), $query->rawQuery()) === 1;
79+
$pattern = $this->config->queriesReportPattern();
80+
81+
if ($pattern && preg_match($pattern, $query->rawQuery) !== 1) {
82+
return false;
83+
}
84+
85+
return call_user_func(self::$shouldReportQuery ?? fn () => true, $query);
6686
}
6787

6888
/**
@@ -92,9 +112,9 @@ protected function directory(): string
92112
protected function shouldLogQuery(SqlQuery $query): bool
93113
{
94114
return $this->config->queriesEnabled() &&
95-
$query->time() >= $this->config->queriesMinExecTime() &&
96-
preg_match($this->config->queriesIncludePattern(), $query->rawQuery()) &&
97-
! preg_match($this->config->queriesExcludePattern(), $query->rawQuery());
115+
$query->time >= $this->config->queriesMinExecTime() &&
116+
preg_match($this->config->queriesIncludePattern(), $query->rawQuery) &&
117+
! preg_match($this->config->queriesExcludePattern(), $query->rawQuery);
98118
}
99119

100120
/**

0 commit comments

Comments
 (0)