1
1
<?php
2
2
/**
3
- * Copyright © Magento, Inc. All rights reserved.
4
- * See COPYING.txt for license details .
3
+ * Copyright 2014 Adobe
4
+ * All Rights Reserved .
5
5
*/
6
6
namespace Magento \Framework \DB \Logger ;
7
7
8
+ use Magento \Framework \App \ObjectManager ;
8
9
use Magento \Framework \DB \LoggerInterface ;
9
10
use Magento \Framework \Debug ;
11
+ use Zend_Db_Statement_Pdo ;
10
12
11
13
abstract class LoggerAbstract implements LoggerInterface
12
14
{
15
+ private const LINE_DELIMITER = "\n" ;
16
+
13
17
/**
14
18
* @var int
15
19
*/
@@ -30,20 +34,40 @@ abstract class LoggerAbstract implements LoggerInterface
30
34
*/
31
35
private $ logCallStack ;
32
36
37
+ /**
38
+ * @var bool
39
+ */
40
+ private bool $ logIndexCheck ;
41
+
42
+ /**
43
+ * @var QueryAnalyzerInterface
44
+ */
45
+ private QueryAnalyzerInterface $ queryAnalyzer ;
46
+
33
47
/**
34
48
* @param bool $logAllQueries
35
49
* @param float $logQueryTime
36
50
* @param bool $logCallStack
51
+ * @param bool $logIndexCheck
52
+ * @param QueryAnalyzerInterface|null $queryAnalyzer
37
53
*/
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
+ ) {
40
61
$ this ->logAllQueries = $ logAllQueries ;
41
62
$ this ->logQueryTime = $ logQueryTime ;
42
63
$ this ->logCallStack = $ logCallStack ;
64
+ $ this ->logIndexCheck = $ logIndexCheck ;
65
+ $ this ->queryAnalyzer = $ queryAnalyzer
66
+ ?: ObjectManager::getInstance ()->get (QueryAnalyzerInterface::class);
43
67
}
44
68
45
69
/**
46
- * {@inheritdoc}
70
+ * @inheritDoc
47
71
*/
48
72
public function startTimer ()
49
73
{
@@ -62,39 +86,123 @@ public function startTimer()
62
86
*/
63
87
public function getStats ($ type , $ sql , $ bind = [], $ result = null )
64
88
{
65
- $ message = '## ' . getmypid () . ' ## ' ;
66
- $ nl = "\n" ;
67
89
$ time = sprintf ('%.4f ' , microtime (true ) - $ this ->timer );
68
90
69
91
if (!$ this ->logAllQueries && $ time < $ this ->logQueryTime ) {
70
92
return '' ;
71
93
}
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
+
72
142
switch ($ type ) {
73
143
case self ::TYPE_CONNECT :
74
- $ message .= 'CONNECT ' . $ nl ;
144
+ $ message .= 'CONNECT ' . self :: LINE_DELIMITER ;
75
145
break ;
76
146
case self ::TYPE_TRANSACTION :
77
- $ message .= 'TRANSACTION ' . $ sql . $ nl ;
147
+ $ message .= 'TRANSACTION ' . $ sql . self :: LINE_DELIMITER ;
78
148
break ;
79
149
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 ;
82
152
if ($ bind ) {
83
- $ message .= 'BIND: ' . var_export ($ bind , true ) . $ nl ;
153
+ $ message .= 'BIND: ' . var_export ($ bind , true ) . self :: LINE_DELIMITER ;
84
154
}
85
155
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
+ }
87
164
}
88
165
break ;
89
166
}
90
- $ message .= 'TIME: ' . $ time . $ nl ;
167
+ $ message .= 'TIME: ' . $ time . self :: LINE_DELIMITER ;
91
168
92
169
if ($ this ->logCallStack ) {
93
- $ message .= ' TRACE: ' . Debug:: backtrace ( true , false ) . $ nl ;
170
+ $ message .= $ this -> getCallStack () ;
94
171
}
95
172
96
- $ message .= $ nl ;
173
+ $ message .= self :: LINE_DELIMITER ;
97
174
98
175
return $ message ;
99
176
}
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
+ }
100
208
}
0 commit comments