Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\HttpKernel\DataCollector; Chris@0: Chris@0: use Symfony\Component\Debug\Exception\SilencedErrorContext; Chris@0: use Symfony\Component\HttpFoundation\Request; Chris@0: use Symfony\Component\HttpFoundation\Response; Chris@0: use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; Chris@0: Chris@0: /** Chris@0: * LogDataCollector. Chris@0: * Chris@0: * @author Fabien Potencier Chris@0: */ Chris@0: class LoggerDataCollector extends DataCollector implements LateDataCollectorInterface Chris@0: { Chris@0: private $logger; Chris@14: private $containerPathPrefix; Chris@0: Chris@14: public function __construct($logger = null, $containerPathPrefix = null) Chris@0: { Chris@0: if (null !== $logger && $logger instanceof DebugLoggerInterface) { Chris@14: if (!method_exists($logger, 'clear')) { Chris@14: @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); Chris@14: } Chris@14: Chris@0: $this->logger = $logger; Chris@0: } Chris@14: Chris@14: $this->containerPathPrefix = $containerPathPrefix; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function collect(Request $request, Response $response, \Exception $exception = null) Chris@0: { Chris@0: // everything is done as late as possible Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@14: public function reset() Chris@14: { Chris@14: if ($this->logger && method_exists($this->logger, 'clear')) { Chris@14: $this->logger->clear(); Chris@14: } Chris@17: $this->data = []; Chris@14: } Chris@14: Chris@14: /** Chris@14: * {@inheritdoc} Chris@14: */ Chris@0: public function lateCollect() Chris@0: { Chris@0: if (null !== $this->logger) { Chris@14: $containerDeprecationLogs = $this->getContainerDeprecationLogs(); Chris@14: $this->data = $this->computeErrorsCount($containerDeprecationLogs); Chris@14: $this->data['compiler_logs'] = $this->getContainerCompilerLogs(); Chris@14: $this->data['logs'] = $this->sanitizeLogs(array_merge($this->logger->getLogs(), $containerDeprecationLogs)); Chris@14: $this->data = $this->cloneVar($this->data); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the logs. Chris@0: * Chris@0: * @return array An array of logs Chris@0: */ Chris@0: public function getLogs() Chris@0: { Chris@17: return isset($this->data['logs']) ? $this->data['logs'] : []; Chris@0: } Chris@0: Chris@0: public function getPriorities() Chris@0: { Chris@17: return isset($this->data['priorities']) ? $this->data['priorities'] : []; Chris@0: } Chris@0: Chris@0: public function countErrors() Chris@0: { Chris@0: return isset($this->data['error_count']) ? $this->data['error_count'] : 0; Chris@0: } Chris@0: Chris@0: public function countDeprecations() Chris@0: { Chris@0: return isset($this->data['deprecation_count']) ? $this->data['deprecation_count'] : 0; Chris@0: } Chris@0: Chris@0: public function countWarnings() Chris@0: { Chris@0: return isset($this->data['warning_count']) ? $this->data['warning_count'] : 0; Chris@0: } Chris@0: Chris@0: public function countScreams() Chris@0: { Chris@0: return isset($this->data['scream_count']) ? $this->data['scream_count'] : 0; Chris@0: } Chris@0: Chris@14: public function getCompilerLogs() Chris@14: { Chris@17: return isset($this->data['compiler_logs']) ? $this->data['compiler_logs'] : []; Chris@14: } Chris@14: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getName() Chris@0: { Chris@0: return 'logger'; Chris@0: } Chris@0: Chris@14: private function getContainerDeprecationLogs() Chris@14: { Chris@14: if (null === $this->containerPathPrefix || !file_exists($file = $this->containerPathPrefix.'Deprecations.log')) { Chris@17: return []; Chris@14: } Chris@14: Chris@18: if ('' === $logContent = trim(file_get_contents($file))) { Chris@18: return []; Chris@18: } Chris@18: Chris@14: $bootTime = filemtime($file); Chris@17: $logs = []; Chris@18: foreach (unserialize($logContent) as $log) { Chris@17: $log['context'] = ['exception' => new SilencedErrorContext($log['type'], $log['file'], $log['line'], $log['trace'], $log['count'])]; Chris@14: $log['timestamp'] = $bootTime; Chris@14: $log['priority'] = 100; Chris@14: $log['priorityName'] = 'DEBUG'; Chris@14: $log['channel'] = '-'; Chris@14: $log['scream'] = false; Chris@14: unset($log['type'], $log['file'], $log['line'], $log['trace'], $log['trace'], $log['count']); Chris@14: $logs[] = $log; Chris@14: } Chris@14: Chris@14: return $logs; Chris@14: } Chris@14: Chris@14: private function getContainerCompilerLogs() Chris@14: { Chris@14: if (null === $this->containerPathPrefix || !file_exists($file = $this->containerPathPrefix.'Compiler.log')) { Chris@17: return []; Chris@14: } Chris@14: Chris@17: $logs = []; Chris@14: foreach (file($file, FILE_IGNORE_NEW_LINES) as $log) { Chris@14: $log = explode(': ', $log, 2); Chris@14: 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])) { Chris@17: $log = ['Unknown Compiler Pass', implode(': ', $log)]; Chris@14: } Chris@14: Chris@17: $logs[$log[0]][] = ['message' => $log[1]]; Chris@14: } Chris@14: Chris@14: return $logs; Chris@14: } Chris@14: Chris@0: private function sanitizeLogs($logs) Chris@0: { Chris@17: $sanitizedLogs = []; Chris@17: $silencedLogs = []; Chris@0: Chris@0: foreach ($logs as $log) { Chris@0: if (!$this->isSilencedOrDeprecationErrorLog($log)) { Chris@0: $sanitizedLogs[] = $log; Chris@0: Chris@0: continue; Chris@0: } Chris@0: Chris@14: $message = $log['message']; Chris@0: $exception = $log['context']['exception']; Chris@14: Chris@14: if ($exception instanceof SilencedErrorContext) { Chris@14: if (isset($silencedLogs[$h = spl_object_hash($exception)])) { Chris@14: continue; Chris@14: } Chris@14: $silencedLogs[$h] = true; Chris@14: Chris@14: if (!isset($sanitizedLogs[$message])) { Chris@17: $sanitizedLogs[$message] = $log + [ Chris@14: 'errorCount' => 0, Chris@14: 'scream' => true, Chris@17: ]; Chris@14: } Chris@14: $sanitizedLogs[$message]['errorCount'] += $exception->count; Chris@14: Chris@14: continue; Chris@14: } Chris@14: Chris@14: $errorId = md5("{$exception->getSeverity()}/{$exception->getLine()}/{$exception->getFile()}\0{$message}", true); Chris@0: Chris@0: if (isset($sanitizedLogs[$errorId])) { Chris@0: ++$sanitizedLogs[$errorId]['errorCount']; Chris@0: } else { Chris@17: $log += [ Chris@0: 'errorCount' => 1, Chris@14: 'scream' => false, Chris@17: ]; Chris@0: Chris@0: $sanitizedLogs[$errorId] = $log; Chris@0: } Chris@0: } Chris@0: Chris@0: return array_values($sanitizedLogs); Chris@0: } Chris@0: Chris@0: private function isSilencedOrDeprecationErrorLog(array $log) Chris@0: { Chris@0: if (!isset($log['context']['exception'])) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: $exception = $log['context']['exception']; Chris@0: Chris@0: if ($exception instanceof SilencedErrorContext) { Chris@0: return true; Chris@0: } Chris@0: Chris@17: if ($exception instanceof \ErrorException && \in_array($exception->getSeverity(), [E_DEPRECATED, E_USER_DEPRECATED], true)) { Chris@0: return true; Chris@0: } Chris@0: Chris@0: return false; Chris@0: } Chris@0: Chris@14: private function computeErrorsCount(array $containerDeprecationLogs) Chris@0: { Chris@17: $silencedLogs = []; Chris@17: $count = [ Chris@0: 'error_count' => $this->logger->countErrors(), Chris@0: 'deprecation_count' => 0, Chris@0: 'warning_count' => 0, Chris@0: 'scream_count' => 0, Chris@17: 'priorities' => [], Chris@17: ]; Chris@0: Chris@0: foreach ($this->logger->getLogs() as $log) { Chris@0: if (isset($count['priorities'][$log['priority']])) { Chris@0: ++$count['priorities'][$log['priority']]['count']; Chris@0: } else { Chris@17: $count['priorities'][$log['priority']] = [ Chris@0: 'count' => 1, Chris@0: 'name' => $log['priorityName'], Chris@17: ]; Chris@0: } Chris@0: if ('WARNING' === $log['priorityName']) { Chris@0: ++$count['warning_count']; Chris@0: } Chris@0: Chris@0: if ($this->isSilencedOrDeprecationErrorLog($log)) { Chris@14: $exception = $log['context']['exception']; Chris@14: if ($exception instanceof SilencedErrorContext) { Chris@14: if (isset($silencedLogs[$h = spl_object_hash($exception)])) { Chris@14: continue; Chris@14: } Chris@14: $silencedLogs[$h] = true; Chris@14: $count['scream_count'] += $exception->count; Chris@0: } else { Chris@0: ++$count['deprecation_count']; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@14: foreach ($containerDeprecationLogs as $deprecationLog) { Chris@14: $count['deprecation_count'] += $deprecationLog['context']['exception']->count; Chris@14: } Chris@14: Chris@0: ksort($count['priorities']); Chris@0: Chris@0: return $count; Chris@0: } Chris@0: }