Skip to content

Commit 7093ff4

Browse files
committed
1.1.0 - 2024-12-10 ADD Total Number of Queries: track the total number of SQL queries executed during a single HTTP request, Artisan command, or CLI execution.
1 parent 1f29be2 commit 7093ff4

8 files changed

+637
-24
lines changed

.env.example

+5
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,8 @@ QUERYMONITOR_BUILDER_MAX_EXECUTION_TIME=200
99
QUERYMONITOR_BUILDER_METHOD_REGEX='^.*$'
1010

1111
QUERYMONITOR_BUILDER_MAX_STACK_DEPTH=5
12+
13+
QUERYMONITOR_TOTAL_QUERIES_ATTIVA=true
14+
QUERYMONITOR_MAX_TOTAL_QUERIES=500
15+
QUERYMONITOR_TOTAL_QUERIES_REGEX=
16+

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
All Notable changes to `laravel-querymonitor` will be documented in this file
44

55

6+
## 1.1.0 - 2024-12-10
7+
8+
- ADD Total Number of Queries**: track the total number of SQL queries executed during a single HTTP request, Artisan command, or CLI execution.
9+
610
## 1.0.0 - 2024-11-29
711

812
- Initial release

README.md

+149-3
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@
22

33
![Laravel QueryMonitor](./resources/images/logo.webp)
44

5-
Laravel QueryMonitor is a package for Laravel that allows you to monitor and log:
6-
75
[![Latest Version on Packagist](https://img.shields.io/packagist/v/padosoft/laravel-querymonitor.svg?style=flat-square)](https://packagist.org/packages/padosoft/laravel-querymonitor)
86
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md)
97
[![CircleCI](https://circleci.com/gh/padosoft/laravel-querymonitor.svg?style=shield)](https://circleci.com/gh/padosoft/laravel-querymonitor)
108
[![Quality Score](https://img.shields.io/scrutinizer/g/padosoft/laravel-querymonitor.svg?style=flat-square)](https://scrutinizer-ci.com/g/padosoft/laravel-querymonitor)
119
[![Total Downloads](https://img.shields.io/packagist/dt/padosoft/laravel-querymonitor.svg?style=flat-square)](https://packagist.org/packages/padosoft/laravel-querymonitor)
1210

13-
11+
Laravel QueryMonitor is a package for Laravel that allows you to monitor and log:
12+
-
1413
- **Slow SQL Queries**: Monitors the actual execution time of SQL queries on the database.
1514
- **Slow Eloquent Methods**: Monitors the total time taken by Eloquent methods, including PHP processing.
15+
- **Total Number of Queries**: track the total number of SQL queries executed during a single HTTP request, Artisan command, or CLI execution.
1616

1717
## Requirements
1818
- "php": ">=8.1",
@@ -51,13 +51,37 @@ return [
5151
'methodRegEx' => env('QUERYMONITOR_BUILDER_METHOD_REGEX', '^(get|first)$'),
5252
],
5353

54+
'total_queries' => [
55+
56+
/*
57+
* Whether to enable total query monitoring.
58+
*/
59+
'attiva' => env('QUERYMONITOR_TOTAL_QUERIES_ATTIVA', true),
60+
61+
/*
62+
* Maximum allowed total queries per request/command.
63+
* If this threshold is exceeded, a warning is logged.
64+
*/
65+
'maxTotalQueries' => env('QUERYMONITOR_MAX_TOTAL_QUERIES', 500),
66+
67+
/*
68+
* A regex to filter which contexts to monitor.
69+
* - For HTTP requests, this regex will be matched against the full URL (including query string).
70+
* - For Artisan commands, it will be matched against the command name.
71+
* - For CLI contexts, it can be matched against the script name.
72+
* If unset or empty, all contexts are monitored.
73+
* Example: '^/api/.*$' to monitor only requests under /api/
74+
*/
75+
'traceRegEx' => env('QUERYMONITOR_TOTAL_QUERIES_REGEX', null),
76+
],
5477
];
5578
```
5679

5780

5881
## Usage
5982

6083
Once installed and configured, the package will automatically monitor and log SQL queries and Eloquent methods based on the provided settings.
84+
If you setting query total count to true, the package will automatically monitor and log total queries count.
6185

6286
### Monitoring SQL Queries
6387
- **What it monitors**: The execution time of SQL queries on the database.
@@ -135,6 +159,128 @@ $users = User::all();
135159
- **Action**: Consider using pagination or limiting the selected fields.
136160

137161

162+
## Monitoring the Total Number of Queries
163+
In addition to monitoring slow queries and slow Eloquent methods, laravel-querymonitor allows you to track the total number of SQL queries executed during a single HTTP request, Artisan command, or CLI execution.
164+
This helps you identify cases where, although each individual query may be performant, the total number of queries is excessively high, potentially causing performance bottlenecks.
165+
166+
**Why Monitor Total Queries?**
167+
Even if every single query is fast, executing too many queries per request or command can cause unnecessary overhead.
168+
By monitoring the total query count, you can quickly identify scenarios where your application issues an excessive number of queries (for example, 2,000 queries in a single request),
169+
pinpointing areas that need optimization (e.g., using eager loading, caching, or refining data retrieval logic).
170+
171+
**How It Works**
172+
- For HTTP requests, a middleware hooks into the Laravel request lifecycle. It resets a query counter at the start of the request and increments it every time a query is executed.
173+
At the end of the request, if the total number of queries exceeds the configured threshold, a warning is logged.
174+
- For Artisan commands, the package listens to the CommandStarting and CommandFinished events.
175+
It resets the counter before the command runs and checks the final count after the command completes.
176+
- For CLI contexts (other non-command CLI scripts), you can manually integrate by resetting and checking counts in your own script logic.
177+
178+
### Configuration
179+
All configuration options are defined in the querymonitor.php config file under the total_queries key:
180+
```php
181+
'total_queries' => [
182+
183+
/*
184+
* Whether to enable total query monitoring.
185+
*/
186+
'attiva' => env('QUERYMONITOR_TOTAL_QUERIES_ATTIVA', true),
187+
188+
/*
189+
* Maximum allowed total queries per request/command.
190+
* If this threshold is exceeded, a warning is logged.
191+
*/
192+
'maxTotalQueries' => env('QUERYMONITOR_MAX_TOTAL_QUERIES', 500),
193+
194+
/*
195+
* A regex to filter which contexts to monitor.
196+
* - For HTTP requests, this regex will be matched against the full URL (including query string).
197+
* - For Artisan commands, it will be matched against the command name.
198+
* - For CLI contexts, it can be matched against the script name.
199+
* If unset or empty, all contexts are monitored.
200+
* Example: '^/api/.*$' to monitor only requests under /api/
201+
*/
202+
'traceRegEx' => env('QUERYMONITOR_TOTAL_QUERIES_REGEX', null),
203+
],
204+
```
205+
206+
### Enabling Total Query Monitoring for HTTP Requests
207+
To track the total number of queries for HTTP requests, the package provides a `TrackTotalQueriesMiddleware`.
208+
This middleware must be added as the first middleware in the global middleware stack.
209+
210+
By doing so, it can:
211+
212+
- **Set up tracking before any other middleware or controllers run**, ensuring that all queries executed during the request lifecycle are counted.
213+
- **Perform a final check after the response is generated**, running last in the outbound cycle, so that it includes queries made by all subsequent middleware, controllers, and operations performed downstream.
214+
215+
**How to add it:**
216+
217+
In your `app/Http/Kernel.php`, ensure that `\Padosoft\QueryMonitor\Middleware\TrackTotalQueriesMiddleware::class` appears at the top of the `$middleware` array:
218+
219+
```php
220+
protected $middleware = [
221+
\Padosoft\QueryMonitor\Middleware\TrackTotalQueriesMiddleware::class,
222+
// ... other global middleware ...
223+
];
224+
```
225+
226+
By placing the TrackTotalQueriesMiddleware first, you guarantee comprehensive coverage of every query executed during the request lifecycle.
227+
Once the request is fully processed, the middleware checks the total query count and logs a warning if it exceeds the configured threshold.
228+
229+
230+
### Examples of Logs
231+
- **1. HTTP Request Exceeding the Query Limit**
232+
233+
If a request executes more than the allowed number of queries, a log entry is created after the response is generated:
234+
235+
```text
236+
[2024-12-09 10:23:45] local.WARNING: Exceeded maximum total queries: 2020 queries (max: 500). {"context":"request","url":"https://example.com/products?category=shoes","method":"GET"}
237+
```
238+
- context: request
239+
- url: The full URL of the request, including query parameters.
240+
- method: The HTTP method (e.g., GET, POST).
241+
242+
- **2. Artisan Command Exceeding the Query Limit**
243+
244+
If an Artisan command triggers more queries than allowed, a warning is logged once the command finishes:
245+
246+
```text
247+
[2024-12-09 10:25:10] local.WARNING: Exceeded maximum total queries: 1200 queries (max: 500). {"context":"command","command":"cache:clear","arguments":["artisan","cache:clear"]}
248+
```
249+
- context: command
250+
- command: The Artisan command name.
251+
- arguments: The arguments passed to the command.
252+
253+
- **3. CLI Context Exceeding the Query Limit**
254+
255+
For CLI contexts (non-Artisan commands), if you set up tracking manually and the query count is exceeded, you'll see a log like:
256+
257+
```text
258+
[2024-12-09 10:26:00] local.WARNING: Exceeded maximum total queries: 3000 queries (max: 500). {"context":"cli-service","script":"myscript.php","arguments":["--option","value"]}
259+
```
260+
- context: cli-service
261+
- script: The name of the CLI script being executed.
262+
- arguments: The arguments passed to the script.
263+
264+
**Using the Regex Filter**
265+
If you provide a traceRegEx:
266+
267+
- For HTTP requests, the package only monitors requests whose URLs match the regex.
268+
- For Artisan commands, only the commands that match the regex pattern are monitored.
269+
- For CLI contexts, only scripts whose name matches the regex are tracked.
270+
271+
For example, if you set:
272+
273+
```php
274+
'traceRegEx' => '^/api/.*$',
275+
```
276+
only requests matching /api/... URLs are monitored.
277+
278+
**Summary**
279+
By configuring and enabling total query monitoring, you gain deeper insights into your application's performance, identifying excessive query usage patterns that can be addressed to improve overall efficiency.
280+
This is especially useful in complex, large-scale projects where minor optimizations in query counts can lead to significant performance gains.
281+
282+
283+
138284
## Final Notes
139285
- **Performance Optimization**: Remember that enabling monitoring can impact performance. It's advisable to use it in development environments or carefully monitor the impact in production.
140286
- **Dynamic Configuration**: You can modify the settings in real-time using environment variables or by updating the configuration file.

config/querymonitor.php

+33
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,39 @@
7878
'methodRegEx' => env('QUERYMONITOR_BUILDER_METHOD_REGEX', '^.*$'),
7979
],
8080

81+
/*
82+
|--------------------------------------------------------------------------
83+
| Total Number of Queries
84+
|--------------------------------------------------------------------------
85+
|
86+
| These settings track the total number of SQL queries executed during
87+
| a single HTTP request, Artisan command, or CLI execution..
88+
|
89+
*/
90+
'total_queries' => [
91+
92+
/*
93+
* Whether to enable total query monitoring.
94+
*/
95+
'attiva' => env('QUERYMONITOR_TOTAL_QUERIES_ATTIVA', false),
96+
97+
/*
98+
* Maximum allowed total queries per request/command.
99+
* If this threshold is exceeded, a warning is logged.
100+
*/
101+
'maxTotalQueries' => env('QUERYMONITOR_MAX_TOTAL_QUERIES', 500),
102+
103+
/*
104+
* A regex to filter which contexts to monitor.
105+
* - For HTTP requests, this regex will be matched against the full URL (including query string).
106+
* - For Artisan commands, it will be matched against the command name.
107+
* - For CLI contexts, it can be matched against the script name.
108+
* If unset or empty, all contexts are monitored.
109+
* Example: '^/api/.*$' to monitor only requests under /api/
110+
*/
111+
'traceRegEx' => env('QUERYMONITOR_TOTAL_QUERIES_REGEX', null),
112+
],
113+
81114
/*
82115
|--------------------------------------------------------------------------
83116
| Miscellaneous Settings
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
namespace Padosoft\QueryMonitor\Middleware;
4+
5+
use Closure;
6+
use Padosoft\QueryMonitor\QueryCounter;
7+
use Illuminate\Support\Facades\Config;
8+
use Illuminate\Support\Facades\Log;
9+
10+
class TrackTotalQueriesMiddleware
11+
{
12+
protected function logExcessiveQueries(int $count, int $max, array $context): void
13+
{
14+
$message = "Exceeded maximum total queries: {$count} queries (max: {$max}).";
15+
$extra = [
16+
'context' => 'request',
17+
'url' => $context['url'] ?? 'unknown',
18+
'method' => $context['method'] ?? 'unknown',
19+
];
20+
21+
Log::warning($message, $extra);
22+
}
23+
24+
public function handle($request, Closure $next)
25+
{
26+
$totalQueriesConfig = Config::get('querymonitor.total_queries', []);
27+
28+
if (empty($totalQueriesConfig['attiva']) || $totalQueriesConfig['attiva'] === false) {
29+
return $next($request);
30+
}
31+
32+
$traceRegEx = $totalQueriesConfig['traceRegEx'] ?? null;
33+
34+
$url = $request->fullUrl();
35+
36+
// Se c'è una regex e la url non match, non tracciamo questa request
37+
if ($traceRegEx && !preg_match("/{$traceRegEx}/", $url)) {
38+
// Non attiviamo il tracking, quindi niente reset
39+
return $next($request);
40+
}
41+
42+
QueryCounter::reset([
43+
'type' => 'request',
44+
'url' => $url,
45+
'method' => $request->method(),
46+
]);
47+
48+
return $next($request);
49+
}
50+
51+
public function terminate($request, $response)
52+
{
53+
$totalQueriesConfig = Config::get('querymonitor.total_queries', []);
54+
55+
if (empty($totalQueriesConfig['attiva']) || $totalQueriesConfig['attiva'] === false) {
56+
// Il tracking non è attivo, non facciamo nulla
57+
return;
58+
}
59+
60+
// Controlla se il tracking è stato attivato controllando se getCount > 0 o se QueryCounter::getContextInfo() non è vuoto
61+
$context = QueryCounter::getContextInfo();
62+
if (empty($context)) {
63+
// Non è stato attivato nessun tracking per questa request
64+
return;
65+
}
66+
67+
$count = QueryCounter::getCount();
68+
$max = $totalQueriesConfig['maxTotalQueries'] ?? 500;
69+
70+
if ($count <= $max) {
71+
// Non superiamo il limite, non facciamo nulla
72+
return;
73+
}
74+
75+
$this->logExcessiveQueries($count, $max, $context);
76+
}
77+
}

src/QueryCounter.php

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
namespace Padosoft\QueryMonitor;
4+
5+
class QueryCounter
6+
{
7+
protected static int $queryCount = 0;
8+
protected static array $contextInfo = [];
9+
10+
/**
11+
* Resetta il contatore query e memorizza il contesto (es: request info, command info)
12+
*/
13+
public static function reset(array $contextInfo = []): void
14+
{
15+
self::$queryCount = 0;
16+
self::$contextInfo = $contextInfo;
17+
}
18+
19+
/**
20+
* Incrementa il contatore delle query
21+
*/
22+
public static function increment(): void
23+
{
24+
self::$queryCount++;
25+
}
26+
27+
/**
28+
* Restituisce il numero totale di query eseguite
29+
*/
30+
public static function getCount(): int
31+
{
32+
return self::$queryCount;
33+
}
34+
35+
/**
36+
* Restituisce le info di contesto salvate (url, metodo, command, ecc.)
37+
*/
38+
public static function getContextInfo(): array
39+
{
40+
return self::$contextInfo;
41+
}
42+
}

0 commit comments

Comments
 (0)