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 */
66namespace Magento \Framework \DB \Logger ;
77
8+ use Magento \Framework \App \ObjectManager ;
89use Magento \Framework \DB \LoggerInterface ;
910use Magento \Framework \Debug ;
11+ use Zend_Db_Statement_Pdo ;
1012
1113abstract 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