├── README.md └── querylog ├── QueryLogPlugin.php ├── variables └── QueryLogVariable.php └── templates └── index.twig /README.md: -------------------------------------------------------------------------------- 1 | # Query Log Plugin for Craft CMS 2 | 3 | Show a log of database queries in your front-end templates. 4 | 5 | ![Screenshot](https://raw.githubusercontent.com/wiki/rsanchez/craft-query-log/screenshot.png) 6 | 7 | ## Usage 8 | 9 | Add this to the bottom of your template: 10 | 11 | ``` 12 | {{ craft.querylog | raw }} 13 | ``` 14 | 15 | The ideal location is in your master layout, just before the final closing `` tag. 16 | 17 | When Craft `devMode` is set to `true`, this will add a Query Log button to the bottom of your page. Click it to reveal a list of all queries executed on the page. -------------------------------------------------------------------------------- /querylog/QueryLogPlugin.php: -------------------------------------------------------------------------------- 1 | getLogs(CLogger::LEVEL_PROFILE, 'system.db.CDbCommand.query'); 26 | 27 | $queries = array(); 28 | 29 | $startTimes = array(); 30 | 31 | foreach ($logs as $log) { 32 | $msg = $log[0]; 33 | $time = $log[3]; 34 | $query = new \stdClass(); 35 | 36 | if (strpos($msg, 'begin:') === 0) { 37 | $sql = substr($msg, 6 + 27, -1); 38 | $startTimes[$sql] = $time; 39 | } elseif (strpos($msg, 'end:') === 0) { 40 | $query->sql = $this->parseSqlBindings(substr($msg, 4 + 27, -1)); 41 | $query->duration = number_format(($time - $startTimes[$sql]) * 1000, 2).'ms'; 42 | $queries[] = $query; 43 | } 44 | } 45 | 46 | return $queries; 47 | } 48 | 49 | public function __toString() 50 | { 51 | $this->hackAddTemplatePath(); 52 | 53 | $queries = $this->queries(); 54 | 55 | if (!$queries) { 56 | return ''; 57 | } 58 | 59 | return craft()->templates->render('querylog/index', compact('queries')); 60 | } 61 | 62 | /** 63 | * Parse a sql statement and interpolate any bindings 64 | * @param string $sql 65 | * @return string 66 | */ 67 | protected function parseSqlBindings($query) 68 | { 69 | if (preg_match('/^(.*?)\. Bound with (.*?)$/s', $query, $match)) { 70 | $query = $match[1]; 71 | $bindings = $match[2]; 72 | 73 | preg_match_all('/:(.*?)=(.*?)(,|$)/', $bindings, $matches); 74 | 75 | foreach ($matches[1] as $i => $column) { 76 | $value = $matches[2][$i]; 77 | 78 | $query = str_replace(':'.$column, $value, $query); 79 | } 80 | } 81 | 82 | return $query.';'; 83 | } 84 | 85 | /** 86 | * Template service doesn't normally let you load plugin templates on a non CP/ non action request 87 | * So we inject our querylog/index template path here 88 | * @return void 89 | */ 90 | protected function hackAddTemplatePath() 91 | { 92 | // add our template path 93 | $key = craft()->path->getSiteTemplatesPath().':querylog/index'; 94 | 95 | $path = craft()->path->getPluginsPath().'querylog/templates/index.twig'; 96 | 97 | $reflectionProperty = new ReflectionProperty(craft()->templates, '_templatePaths'); 98 | 99 | $reflectionProperty->setAccessible(true); 100 | 101 | $templatePaths = $reflectionProperty->getValue(craft()->templates); 102 | 103 | $templatePaths[$key] = $path; 104 | 105 | $reflectionProperty->setValue(craft()->templates, $templatePaths); 106 | 107 | $reflectionProperty->setAccessible(false); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /querylog/templates/index.twig: -------------------------------------------------------------------------------- 1 | 133 | 134 |
135 | 138 | 139 |
140 |
{{ queries|length }} total queries.
141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | {% for query in queries %} 150 | 151 | 152 | 153 | 154 | {% endfor %} 155 | 156 |
QueryDuration
{{ query.sql }}{{ query.duration }}
157 |
158 |
159 | 160 | 161 | 162 | 163 | --------------------------------------------------------------------------------