Skip to content

Commit bc68a68

Browse files
committed
Merge remote-tracking branch 'origin/ACP2E-4094' into PR_2025_09_15_muntianu
2 parents 4baea6d + 238c3a7 commit bc68a68

File tree

11 files changed

+811
-46
lines changed

11 files changed

+811
-46
lines changed

app/code/Magento/Developer/Console/Command/QueryLogEnableCommand.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2017 Adobe
4+
* All Rights Reserved.
55
*/
66

77
namespace Magento\Developer\Console\Command;
@@ -37,6 +37,8 @@ class QueryLogEnableCommand extends Command
3737

3838
public const SUCCESS_MESSAGE = "DB query logging enabled.";
3939

40+
public const INPUT_ARG_LOG_INDEX_CHECK = 'include-index-check';
41+
4042
/**
4143
* @var Writer
4244
*/
@@ -85,6 +87,13 @@ protected function configure()
8587
'Include call stack. [true|false]',
8688
"true"
8789
),
90+
new InputOption(
91+
self::INPUT_ARG_LOG_INDEX_CHECK,
92+
null,
93+
InputOption::VALUE_OPTIONAL,
94+
'Include index check. Warning: may cause performance degradation. [true|false]',
95+
"false"
96+
)
8897
]
8998
);
9099

@@ -94,7 +103,7 @@ protected function configure()
94103
/**
95104
* @inheritdoc
96105
*
97-
* @throws InvalidArgumentException
106+
* @throws InvalidArgumentException|\Magento\Framework\Exception\FileSystemException
98107
*/
99108
protected function execute(InputInterface $input, OutputInterface $output)
100109
{
@@ -103,10 +112,12 @@ protected function execute(InputInterface $input, OutputInterface $output)
103112
$logAllQueries = $input->getOption(self::INPUT_ARG_LOG_ALL_QUERIES);
104113
$logQueryTime = $input->getOption(self::INPUT_ARG_LOG_QUERY_TIME);
105114
$logCallStack = $input->getOption(self::INPUT_ARG_LOG_CALL_STACK);
115+
$logIndexCheck = $input->getOption(self::INPUT_ARG_LOG_INDEX_CHECK);
106116

107117
$data[LoggerProxy::PARAM_LOG_ALL] = (int)($logAllQueries != 'false');
108118
$data[LoggerProxy::PARAM_QUERY_TIME] = number_format($logQueryTime, 3);
109119
$data[LoggerProxy::PARAM_CALL_STACK] = (int)($logCallStack != 'false');
120+
$data[LoggerProxy::PARAM_INDEX_CHECK] = (int)($logIndexCheck != 'false');
110121

111122
$configGroup[LoggerProxy::CONF_GROUP_NAME] = $data;
112123

app/etc/di.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@
216216
<preference for="Magento\Framework\Interception\ConfigLoaderInterface" type="Magento\Framework\Interception\PluginListGenerator" />
217217
<preference for="Magento\Framework\Interception\ConfigWriterInterface" type="Magento\Framework\Interception\PluginListGenerator" />
218218
<preference for="Magento\Framework\Mview\View\SubscriptionStatementPostprocessorInterface" type="Magento\Framework\Mview\View\CompositeSubscriptionStatementPostprocessor" />
219+
<preference for="Magento\Framework\DB\Logger\QueryAnalyzerInterface" type="Magento\Framework\DB\Logger\QueryIndexAnalyzer" />
219220
<type name="Magento\Framework\Model\ResourceModel\Db\TransactionManager" shared="false" />
220221
<type name="Magento\Framework\Acl\Data\Cache">
221222
<arguments>
@@ -1455,6 +1456,7 @@
14551456
<argument name="logAllQueries" xsi:type="init_parameter">Magento\Framework\Config\ConfigOptionsListConstants::CONFIG_PATH_DB_LOGGER_LOG_EVERYTHING</argument>
14561457
<argument name="logQueryTime" xsi:type="init_parameter">Magento\Framework\Config\ConfigOptionsListConstants::CONFIG_PATH_DB_LOGGER_QUERY_TIME_THRESHOLD</argument>
14571458
<argument name="logCallStack" xsi:type="init_parameter">Magento\Framework\Config\ConfigOptionsListConstants::CONFIG_PATH_DB_LOGGER_INCLUDE_STACKTRACE</argument>
1459+
<argument name="logIndexCheck" xsi:type="init_parameter">Magento\Framework\Config\ConfigOptionsListConstants::CONFIG_PATH_DB_LOGGER_INCLUDE_INDEX_CHECK</argument>
14581460
</arguments>
14591461
</type>
14601462
<type name="Magento\Framework\App\Config\MetadataConfigTypeProcessor">

lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class ConfigOptionsListConstants
4040
public const CONFIG_PATH_DB_LOGGER_LOG_EVERYTHING = 'db_logger/log_everything';
4141
public const CONFIG_PATH_DB_LOGGER_QUERY_TIME_THRESHOLD = 'db_logger/query_time_threshold';
4242
public const CONFIG_PATH_DB_LOGGER_INCLUDE_STACKTRACE = 'db_logger/include_stacktrace';
43+
public const CONFIG_PATH_DB_LOGGER_INCLUDE_INDEX_CHECK = 'db_logger/include_index_check';
4344
/**#@-*/
4445

4546
/**

lib/internal/Magento/Framework/DB/Logger/File.php

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2014 Adobe
4+
* All Rights Reserved.
55
*/
66
namespace Magento\Framework\DB\Logger;
77

88
use Magento\Framework\App\Filesystem\DirectoryList;
9+
use Magento\Framework\Exception\FileSystemException;
910
use Magento\Framework\Filesystem;
1011
use Magento\Framework\Filesystem\Directory\WriteInterface;
1112

@@ -32,21 +33,26 @@ class File extends LoggerAbstract
3233
* @param bool $logAllQueries
3334
* @param float $logQueryTime
3435
* @param bool $logCallStack
36+
* @param bool $logIndexCheck
37+
* @param QueryAnalyzerInterface|null $queryAnalyzer
38+
* @throws FileSystemException
3539
*/
3640
public function __construct(
3741
Filesystem $filesystem,
3842
$debugFile = 'debug/db.log',
3943
$logAllQueries = false,
4044
$logQueryTime = 0.05,
41-
$logCallStack = false
45+
$logCallStack = false,
46+
$logIndexCheck = false,
47+
?QueryAnalyzerInterface $queryAnalyzer = null,
4248
) {
43-
parent::__construct($logAllQueries, $logQueryTime, $logCallStack);
49+
parent::__construct($logAllQueries, $logQueryTime, $logCallStack, $logIndexCheck, $queryAnalyzer);
4450
$this->dir = $filesystem->getDirectoryWrite(DirectoryList::VAR_DIR);
4551
$this->debugFile = $debugFile;
4652
}
4753

4854
/**
49-
* {@inheritdoc}
55+
* @inheritDoc
5056
*/
5157
public function log($str)
5258
{
@@ -60,7 +66,7 @@ public function log($str)
6066
}
6167

6268
/**
63-
* {@inheritdoc}
69+
* @inheritDoc
6470
*/
6571
public function logStats($type, $sql, $bind = [], $result = null)
6672
{
@@ -71,7 +77,7 @@ public function logStats($type, $sql, $bind = [], $result = null)
7177
}
7278

7379
/**
74-
* {@inheritdoc}
80+
* @inheritDoc
7581
*/
7682
public function critical(\Exception $e)
7783
{

lib/internal/Magento/Framework/DB/Logger/LoggerAbstract.php

Lines changed: 124 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2014 Adobe
4+
* All Rights Reserved.
55
*/
66
namespace Magento\Framework\DB\Logger;
77

8+
use Magento\Framework\App\ObjectManager;
89
use Magento\Framework\DB\LoggerInterface;
910
use Magento\Framework\Debug;
11+
use Zend_Db_Statement_Pdo;
1012

1113
abstract class LoggerAbstract implements LoggerInterface
1214
{
15+
private const LINE_DELIMITER = "\n";
16+
1317
/**
1418
* @var int
1519
*/
@@ -30,20 +34,40 @@ abstract class LoggerAbstract implements LoggerInterface
3034
*/
3135
private $logCallStack;
3236

37+
/**
38+
* @var bool
39+
*/
40+
private bool $logIndexCheck;
41+
42+
/**
43+
* @var QueryAnalyzerInterface
44+
*/
45+
private QueryAnalyzerInterface $queryAnalyzer;
46+
3347
/**
3448
* @param bool $logAllQueries
3549
* @param float $logQueryTime
3650
* @param bool $logCallStack
51+
* @param bool $logIndexCheck
52+
* @param QueryAnalyzerInterface|null $queryAnalyzer
3753
*/
38-
public function __construct($logAllQueries = false, $logQueryTime = 0.05, $logCallStack = false)
39-
{
54+
public function __construct(
55+
$logAllQueries = false,
56+
$logQueryTime = 0.05,
57+
$logCallStack = false,
58+
$logIndexCheck = false,
59+
?QueryAnalyzerInterface $queryAnalyzer = null,
60+
) {
4061
$this->logAllQueries = $logAllQueries;
4162
$this->logQueryTime = $logQueryTime;
4263
$this->logCallStack = $logCallStack;
64+
$this->logIndexCheck = $logIndexCheck;
65+
$this->queryAnalyzer = $queryAnalyzer
66+
?: ObjectManager::getInstance()->get(QueryAnalyzerInterface::class);
4367
}
4468

4569
/**
46-
* {@inheritdoc}
70+
* @inheritDoc
4771
*/
4872
public function startTimer()
4973
{
@@ -62,39 +86,123 @@ public function startTimer()
6286
*/
6387
public function getStats($type, $sql, $bind = [], $result = null)
6488
{
65-
$message = '## ' . getmypid() . ' ## ';
66-
$nl = "\n";
6789
$time = sprintf('%.4f', microtime(true) - $this->timer);
6890

6991
if (!$this->logAllQueries && $time < $this->logQueryTime) {
7092
return '';
7193
}
94+
95+
if ($this->isExplainQuery($sql)) {
96+
return '';
97+
}
98+
99+
return $this->buildDebugMessage($type, $sql, $bind, $result, $time);
100+
}
101+
102+
/**
103+
* Check if query already contains 'explain' keyword
104+
*
105+
* @param string $query
106+
* @return bool
107+
*/
108+
private function isExplainQuery(string $query): bool
109+
{
110+
// Remove leading/trailing whitespace and normalize case
111+
$cleaned = ltrim($query);
112+
113+
// Strip comments
114+
while (preg_match('/^(--[^\n]*\n|\/\*.*?\*\/\s*)/s', $cleaned, $matches)) {
115+
$cleaned = ltrim(substr($cleaned, strlen($matches[0])));
116+
}
117+
118+
// Check if it starts with EXPLAIN
119+
return (bool) preg_match('/^EXPLAIN\b/i', $cleaned);
120+
}
121+
122+
/**
123+
* Build log message based on query type
124+
*
125+
* @param string $type
126+
* @param string $sql
127+
* @param array $bind
128+
* @param Zend_Db_Statement_Pdo|null $result
129+
* @param string $time
130+
* @return string
131+
* @throws \Zend_Db_Statement_Exception
132+
*/
133+
private function buildDebugMessage(
134+
string $type,
135+
string $sql,
136+
array $bind,
137+
?Zend_Db_Statement_Pdo $result,
138+
string $time
139+
): string {
140+
$message = '## ' . getmypid() . ' ## ';
141+
72142
switch ($type) {
73143
case self::TYPE_CONNECT:
74-
$message .= 'CONNECT' . $nl;
144+
$message .= 'CONNECT' . self::LINE_DELIMITER;
75145
break;
76146
case self::TYPE_TRANSACTION:
77-
$message .= 'TRANSACTION ' . $sql . $nl;
147+
$message .= 'TRANSACTION ' . $sql . self::LINE_DELIMITER;
78148
break;
79149
case self::TYPE_QUERY:
80-
$message .= 'QUERY' . $nl;
81-
$message .= 'SQL: ' . $sql . $nl;
150+
$message .= 'QUERY' . self::LINE_DELIMITER;
151+
$message .= 'SQL: ' . $sql . self::LINE_DELIMITER;
82152
if ($bind) {
83-
$message .= 'BIND: ' . var_export($bind, true) . $nl;
153+
$message .= 'BIND: ' . var_export($bind, true) . self::LINE_DELIMITER;
84154
}
85155
if ($result instanceof \Zend_Db_Statement_Pdo) {
86-
$message .= 'AFF: ' . $result->rowCount() . $nl;
156+
$message .= 'AFF: ' . $result->rowCount() . self::LINE_DELIMITER;
157+
}
158+
if ($this->logIndexCheck) {
159+
try {
160+
$message .= $this->processIndexCheck($sql, $bind) . self::LINE_DELIMITER;
161+
} catch (QueryAnalyzerException $e) {
162+
$message .= 'INDEX CHECK: ' . strtoupper($e->getMessage()) . self::LINE_DELIMITER;
163+
}
87164
}
88165
break;
89166
}
90-
$message .= 'TIME: ' . $time . $nl;
167+
$message .= 'TIME: ' . $time . self::LINE_DELIMITER;
91168

92169
if ($this->logCallStack) {
93-
$message .= 'TRACE: ' . Debug::backtrace(true, false) . $nl;
170+
$message .= $this->getCallStack();
94171
}
95172

96-
$message .= $nl;
173+
$message .= self::LINE_DELIMITER;
97174

98175
return $message;
99176
}
177+
178+
/**
179+
* Get potential index issues
180+
*
181+
* @param string $sql
182+
* @param array $bind
183+
* @return string
184+
* @throws QueryAnalyzerException
185+
*/
186+
private function processIndexCheck(string $sql, array $bind): string
187+
{
188+
$message = '';
189+
$issues = $this->queryAnalyzer->process($sql, $bind);
190+
if (!empty($issues)) {
191+
$message .= 'INDEX CHECK: POTENTIAL ISSUES - ' . implode(', ', array_unique($issues));
192+
} else {
193+
$message .= 'INDEX CHECK: USING INDEX';
194+
}
195+
196+
return $message;
197+
}
198+
199+
/**
200+
* Get call stack debug message
201+
*
202+
* @return string
203+
*/
204+
private function getCallStack(): string
205+
{
206+
return 'TRACE: ' . Debug::backtrace(true, false) . self::LINE_DELIMITER;
207+
}
100208
}

0 commit comments

Comments
 (0)