1<?php 2 3/* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Symfony\Component\HttpKernel\DataCollector; 13 14use Symfony\Component\Debug\Exception\SilencedErrorContext; 15use Symfony\Component\HttpFoundation\Request; 16use Symfony\Component\HttpFoundation\Response; 17use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; 18 19/** 20 * LogDataCollector. 21 * 22 * @author Fabien Potencier <fabien@symfony.com> 23 */ 24class LoggerDataCollector extends DataCollector implements LateDataCollectorInterface 25{ 26 private $logger; 27 private $containerPathPrefix; 28 29 public function __construct($logger = null, $containerPathPrefix = null) 30 { 31 if (null !== $logger && $logger instanceof DebugLoggerInterface) { 32 if (!method_exists($logger, 'clear')) { 33 @trigger_error(sprintf('Implementing "%s" without the "clear()" method is deprecated since Symfony 3.4 and will be unsupported in 4.0 for class "%s".', DebugLoggerInterface::class, \get_class($logger)), E_USER_DEPRECATED); 34 } 35 36 $this->logger = $logger; 37 } 38 39 $this->containerPathPrefix = $containerPathPrefix; 40 } 41 42 /** 43 * {@inheritdoc} 44 */ 45 public function collect(Request $request, Response $response, \Exception $exception = null) 46 { 47 // everything is done as late as possible 48 } 49 50 /** 51 * {@inheritdoc} 52 */ 53 public function reset() 54 { 55 if ($this->logger && method_exists($this->logger, 'clear')) { 56 $this->logger->clear(); 57 } 58 $this->data = array(); 59 } 60 61 /** 62 * {@inheritdoc} 63 */ 64 public function lateCollect() 65 { 66 if (null !== $this->logger) { 67 $containerDeprecationLogs = $this->getContainerDeprecationLogs(); 68 $this->data = $this->computeErrorsCount($containerDeprecationLogs); 69 $this->data['compiler_logs'] = $this->getContainerCompilerLogs(); 70 $this->data['logs'] = $this->sanitizeLogs(array_merge($this->logger->getLogs(), $containerDeprecationLogs)); 71 $this->data = $this->cloneVar($this->data); 72 } 73 } 74 75 /** 76 * Gets the logs. 77 * 78 * @return array An array of logs 79 */ 80 public function getLogs() 81 { 82 return isset($this->data['logs']) ? $this->data['logs'] : array(); 83 } 84 85 public function getPriorities() 86 { 87 return isset($this->data['priorities']) ? $this->data['priorities'] : array(); 88 } 89 90 public function countErrors() 91 { 92 return isset($this->data['error_count']) ? $this->data['error_count'] : 0; 93 } 94 95 public function countDeprecations() 96 { 97 return isset($this->data['deprecation_count']) ? $this->data['deprecation_count'] : 0; 98 } 99 100 public function countWarnings() 101 { 102 return isset($this->data['warning_count']) ? $this->data['warning_count'] : 0; 103 } 104 105 public function countScreams() 106 { 107 return isset($this->data['scream_count']) ? $this->data['scream_count'] : 0; 108 } 109 110 public function getCompilerLogs() 111 { 112 return isset($this->data['compiler_logs']) ? $this->data['compiler_logs'] : array(); 113 } 114 115 /** 116 * {@inheritdoc} 117 */ 118 public function getName() 119 { 120 return 'logger'; 121 } 122 123 private function getContainerDeprecationLogs() 124 { 125 if (null === $this->containerPathPrefix || !file_exists($file = $this->containerPathPrefix.'Deprecations.log')) { 126 return array(); 127 } 128 129 $bootTime = filemtime($file); 130 $logs = array(); 131 foreach (unserialize(file_get_contents($file)) as $log) { 132 $log['context'] = array('exception' => new SilencedErrorContext($log['type'], $log['file'], $log['line'], $log['trace'], $log['count'])); 133 $log['timestamp'] = $bootTime; 134 $log['priority'] = 100; 135 $log['priorityName'] = 'DEBUG'; 136 $log['channel'] = '-'; 137 $log['scream'] = false; 138 unset($log['type'], $log['file'], $log['line'], $log['trace'], $log['trace'], $log['count']); 139 $logs[] = $log; 140 } 141 142 return $logs; 143 } 144 145 private function getContainerCompilerLogs() 146 { 147 if (null === $this->containerPathPrefix || !file_exists($file = $this->containerPathPrefix.'Compiler.log')) { 148 return array(); 149 } 150 151 $logs = array(); 152 foreach (file($file, FILE_IGNORE_NEW_LINES) as $log) { 153 $log = explode(': ', $log, 2); 154 if (!isset($log[1]) || !preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)++$/', $log[0])) { 155 $log = array('Unknown Compiler Pass', implode(': ', $log)); 156 } 157 158 $logs[$log[0]][] = array('message' => $log[1]); 159 } 160 161 return $logs; 162 } 163 164 private function sanitizeLogs($logs) 165 { 166 $sanitizedLogs = array(); 167 $silencedLogs = array(); 168 169 foreach ($logs as $log) { 170 if (!$this->isSilencedOrDeprecationErrorLog($log)) { 171 $sanitizedLogs[] = $log; 172 173 continue; 174 } 175 176 $message = $log['message']; 177 $exception = $log['context']['exception']; 178 179 if ($exception instanceof SilencedErrorContext) { 180 if (isset($silencedLogs[$h = spl_object_hash($exception)])) { 181 continue; 182 } 183 $silencedLogs[$h] = true; 184 185 if (!isset($sanitizedLogs[$message])) { 186 $sanitizedLogs[$message] = $log + array( 187 'errorCount' => 0, 188 'scream' => true, 189 ); 190 } 191 $sanitizedLogs[$message]['errorCount'] += $exception->count; 192 193 continue; 194 } 195 196 $errorId = md5("{$exception->getSeverity()}/{$exception->getLine()}/{$exception->getFile()}\0{$message}", true); 197 198 if (isset($sanitizedLogs[$errorId])) { 199 ++$sanitizedLogs[$errorId]['errorCount']; 200 } else { 201 $log += array( 202 'errorCount' => 1, 203 'scream' => false, 204 ); 205 206 $sanitizedLogs[$errorId] = $log; 207 } 208 } 209 210 return array_values($sanitizedLogs); 211 } 212 213 private function isSilencedOrDeprecationErrorLog(array $log) 214 { 215 if (!isset($log['context']['exception'])) { 216 return false; 217 } 218 219 $exception = $log['context']['exception']; 220 221 if ($exception instanceof SilencedErrorContext) { 222 return true; 223 } 224 225 if ($exception instanceof \ErrorException && in_array($exception->getSeverity(), array(E_DEPRECATED, E_USER_DEPRECATED), true)) { 226 return true; 227 } 228 229 return false; 230 } 231 232 private function computeErrorsCount(array $containerDeprecationLogs) 233 { 234 $silencedLogs = array(); 235 $count = array( 236 'error_count' => $this->logger->countErrors(), 237 'deprecation_count' => 0, 238 'warning_count' => 0, 239 'scream_count' => 0, 240 'priorities' => array(), 241 ); 242 243 foreach ($this->logger->getLogs() as $log) { 244 if (isset($count['priorities'][$log['priority']])) { 245 ++$count['priorities'][$log['priority']]['count']; 246 } else { 247 $count['priorities'][$log['priority']] = array( 248 'count' => 1, 249 'name' => $log['priorityName'], 250 ); 251 } 252 if ('WARNING' === $log['priorityName']) { 253 ++$count['warning_count']; 254 } 255 256 if ($this->isSilencedOrDeprecationErrorLog($log)) { 257 $exception = $log['context']['exception']; 258 if ($exception instanceof SilencedErrorContext) { 259 if (isset($silencedLogs[$h = spl_object_hash($exception)])) { 260 continue; 261 } 262 $silencedLogs[$h] = true; 263 $count['scream_count'] += $exception->count; 264 } else { 265 ++$count['deprecation_count']; 266 } 267 } 268 } 269 270 foreach ($containerDeprecationLogs as $deprecationLog) { 271 $count['deprecation_count'] += $deprecationLog['context']['exception']->count; 272 } 273 274 ksort($count['priorities']); 275 276 return $count; 277 } 278} 279