vendor/symfony/http-kernel/DataCollector/LoggerDataCollector.php line 74

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\HttpKernel\DataCollector;
  11. use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext;
  12. use Symfony\Component\HttpFoundation\Request;
  13. use Symfony\Component\HttpFoundation\RequestStack;
  14. use Symfony\Component\HttpFoundation\Response;
  15. use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
  16. /**
  17.  * @author Fabien Potencier <fabien@symfony.com>
  18.  *
  19.  * @final
  20.  */
  21. class LoggerDataCollector extends DataCollector implements LateDataCollectorInterface
  22. {
  23.     private $logger;
  24.     private $containerPathPrefix;
  25.     private $currentRequest;
  26.     private $requestStack;
  27.     private $processedLogs;
  28.     public function __construct(?object $logger null, ?string $containerPathPrefix null, ?RequestStack $requestStack null)
  29.     {
  30.         if (null !== $logger && $logger instanceof DebugLoggerInterface) {
  31.             $this->logger $logger;
  32.         }
  33.         $this->containerPathPrefix $containerPathPrefix;
  34.         $this->requestStack $requestStack;
  35.     }
  36.     /**
  37.      * {@inheritdoc}
  38.      */
  39.     public function collect(Request $requestResponse $response, ?\Throwable $exception null)
  40.     {
  41.         $this->currentRequest $this->requestStack && $this->requestStack->getMainRequest() !== $request $request null;
  42.     }
  43.     /**
  44.      * {@inheritdoc}
  45.      */
  46.     public function reset()
  47.     {
  48.         if ($this->logger instanceof DebugLoggerInterface) {
  49.             $this->logger->clear();
  50.         }
  51.         $this->data = [];
  52.     }
  53.     /**
  54.      * {@inheritdoc}
  55.      */
  56.     public function lateCollect()
  57.     {
  58.         if (null !== $this->logger) {
  59.             $containerDeprecationLogs $this->getContainerDeprecationLogs();
  60.             $this->data $this->computeErrorsCount($containerDeprecationLogs);
  61.             // get compiler logs later (only when they are needed) to improve performance
  62.             $this->data['compiler_logs'] = [];
  63.             $this->data['compiler_logs_filepath'] = $this->containerPathPrefix.'Compiler.log';
  64.             $this->data['logs'] = $this->sanitizeLogs(array_merge($this->logger->getLogs($this->currentRequest), $containerDeprecationLogs));
  65.             $this->data $this->cloneVar($this->data);
  66.         }
  67.         $this->currentRequest null;
  68.     }
  69.     public function getLogs()
  70.     {
  71.         return $this->data['logs'] ?? [];
  72.     }
  73.     public function getProcessedLogs()
  74.     {
  75.         if (null !== $this->processedLogs) {
  76.             return $this->processedLogs;
  77.         }
  78.         $rawLogs $this->getLogs();
  79.         if ([] === $rawLogs) {
  80.             return $this->processedLogs $rawLogs;
  81.         }
  82.         $logs = [];
  83.         foreach ($this->getLogs()->getValue() as $rawLog) {
  84.             $rawLogData $rawLog->getValue();
  85.             if ($rawLogData['priority']->getValue() > 300) {
  86.                 $logType 'error';
  87.             } elseif (isset($rawLogData['scream']) && false === $rawLogData['scream']->getValue()) {
  88.                 $logType 'deprecation';
  89.             } elseif (isset($rawLogData['scream']) && true === $rawLogData['scream']->getValue()) {
  90.                 $logType 'silenced';
  91.             } else {
  92.                 $logType 'regular';
  93.             }
  94.             $logs[] = [
  95.                 'type' => $logType,
  96.                 'errorCount' => $rawLog['errorCount'] ?? 1,
  97.                 'timestamp' => $rawLogData['timestamp_rfc3339']->getValue(),
  98.                 'priority' => $rawLogData['priority']->getValue(),
  99.                 'priorityName' => $rawLogData['priorityName']->getValue(),
  100.                 'channel' => $rawLogData['channel']->getValue(),
  101.                 'message' => $rawLogData['message'],
  102.                 'context' => $rawLogData['context'],
  103.             ];
  104.         }
  105.         // sort logs from oldest to newest
  106.         usort($logs, static function ($logA$logB) {
  107.             return $logA['timestamp'] <=> $logB['timestamp'];
  108.         });
  109.         return $this->processedLogs $logs;
  110.     }
  111.     public function getFilters()
  112.     {
  113.         $filters = [
  114.             'channel' => [],
  115.             'priority' => [
  116.                 'Debug' => 100,
  117.                 'Info' => 200,
  118.                 'Notice' => 250,
  119.                 'Warning' => 300,
  120.                 'Error' => 400,
  121.                 'Critical' => 500,
  122.                 'Alert' => 550,
  123.                 'Emergency' => 600,
  124.             ],
  125.         ];
  126.         $allChannels = [];
  127.         foreach ($this->getProcessedLogs() as $log) {
  128.             if ('' === trim($log['channel'] ?? '')) {
  129.                 continue;
  130.             }
  131.             $allChannels[] = $log['channel'];
  132.         }
  133.         $channels array_unique($allChannels);
  134.         sort($channels);
  135.         $filters['channel'] = $channels;
  136.         return $filters;
  137.     }
  138.     public function getPriorities()
  139.     {
  140.         return $this->data['priorities'] ?? [];
  141.     }
  142.     public function countErrors()
  143.     {
  144.         return $this->data['error_count'] ?? 0;
  145.     }
  146.     public function countDeprecations()
  147.     {
  148.         return $this->data['deprecation_count'] ?? 0;
  149.     }
  150.     public function countWarnings()
  151.     {
  152.         return $this->data['warning_count'] ?? 0;
  153.     }
  154.     public function countScreams()
  155.     {
  156.         return $this->data['scream_count'] ?? 0;
  157.     }
  158.     public function getCompilerLogs()
  159.     {
  160.         return $this->cloneVar($this->getContainerCompilerLogs($this->data['compiler_logs_filepath'] ?? null));
  161.     }
  162.     /**
  163.      * {@inheritdoc}
  164.      */
  165.     public function getName(): string
  166.     {
  167.         return 'logger';
  168.     }
  169.     private function getContainerDeprecationLogs(): array
  170.     {
  171.         if (null === $this->containerPathPrefix || !is_file($file $this->containerPathPrefix.'Deprecations.log')) {
  172.             return [];
  173.         }
  174.         if ('' === $logContent trim(file_get_contents($file))) {
  175.             return [];
  176.         }
  177.         $bootTime filemtime($file);
  178.         $logs = [];
  179.         foreach (unserialize($logContent) as $log) {
  180.             $log['context'] = ['exception' => new SilencedErrorContext($log['type'], $log['file'], $log['line'], $log['trace'], $log['count'])];
  181.             $log['timestamp'] = $bootTime;
  182.             $log['timestamp_rfc3339'] = (new \DateTimeImmutable())->setTimestamp($bootTime)->format(\DateTimeInterface::RFC3339_EXTENDED);
  183.             $log['priority'] = 100;
  184.             $log['priorityName'] = 'DEBUG';
  185.             $log['channel'] = null;
  186.             $log['scream'] = false;
  187.             unset($log['type'], $log['file'], $log['line'], $log['trace'], $log['trace'], $log['count']);
  188.             $logs[] = $log;
  189.         }
  190.         return $logs;
  191.     }
  192.     private function getContainerCompilerLogs(?string $compilerLogsFilepath null): array
  193.     {
  194.         if (!$compilerLogsFilepath || !is_file($compilerLogsFilepath)) {
  195.             return [];
  196.         }
  197.         $logs = [];
  198.         foreach (file($compilerLogsFilepath\FILE_IGNORE_NEW_LINES) as $log) {
  199.             $log explode(': '$log2);
  200.             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])) {
  201.                 $log = ['Unknown Compiler Pass'implode(': '$log)];
  202.             }
  203.             $logs[$log[0]][] = ['message' => $log[1]];
  204.         }
  205.         return $logs;
  206.     }
  207.     private function sanitizeLogs(array $logs)
  208.     {
  209.         $sanitizedLogs = [];
  210.         $silencedLogs = [];
  211.         foreach ($logs as $log) {
  212.             if (!$this->isSilencedOrDeprecationErrorLog($log)) {
  213.                 $sanitizedLogs[] = $log;
  214.                 continue;
  215.             }
  216.             $message '_'.$log['message'];
  217.             $exception $log['context']['exception'];
  218.             if ($exception instanceof SilencedErrorContext) {
  219.                 if (isset($silencedLogs[$h spl_object_hash($exception)])) {
  220.                     continue;
  221.                 }
  222.                 $silencedLogs[$h] = true;
  223.                 if (!isset($sanitizedLogs[$message])) {
  224.                     $sanitizedLogs[$message] = $log + [
  225.                         'errorCount' => 0,
  226.                         'scream' => true,
  227.                     ];
  228.                 }
  229.                 $sanitizedLogs[$message]['errorCount'] += $exception->count;
  230.                 continue;
  231.             }
  232.             $errorId md5("{$exception->getSeverity()}/{$exception->getLine()}/{$exception->getFile()}\0{$message}"true);
  233.             if (isset($sanitizedLogs[$errorId])) {
  234.                 ++$sanitizedLogs[$errorId]['errorCount'];
  235.             } else {
  236.                 $log += [
  237.                     'errorCount' => 1,
  238.                     'scream' => false,
  239.                 ];
  240.                 $sanitizedLogs[$errorId] = $log;
  241.             }
  242.         }
  243.         return array_values($sanitizedLogs);
  244.     }
  245.     private function isSilencedOrDeprecationErrorLog(array $log): bool
  246.     {
  247.         if (!isset($log['context']['exception'])) {
  248.             return false;
  249.         }
  250.         $exception $log['context']['exception'];
  251.         if ($exception instanceof SilencedErrorContext) {
  252.             return true;
  253.         }
  254.         if ($exception instanceof \ErrorException && \in_array($exception->getSeverity(), [\E_DEPRECATED\E_USER_DEPRECATED], true)) {
  255.             return true;
  256.         }
  257.         return false;
  258.     }
  259.     private function computeErrorsCount(array $containerDeprecationLogs): array
  260.     {
  261.         $silencedLogs = [];
  262.         $count = [
  263.             'error_count' => $this->logger->countErrors($this->currentRequest),
  264.             'deprecation_count' => 0,
  265.             'warning_count' => 0,
  266.             'scream_count' => 0,
  267.             'priorities' => [],
  268.         ];
  269.         foreach ($this->logger->getLogs($this->currentRequest) as $log) {
  270.             if (isset($count['priorities'][$log['priority']])) {
  271.                 ++$count['priorities'][$log['priority']]['count'];
  272.             } else {
  273.                 $count['priorities'][$log['priority']] = [
  274.                     'count' => 1,
  275.                     'name' => $log['priorityName'],
  276.                 ];
  277.             }
  278.             if ('WARNING' === $log['priorityName']) {
  279.                 ++$count['warning_count'];
  280.             }
  281.             if ($this->isSilencedOrDeprecationErrorLog($log)) {
  282.                 $exception $log['context']['exception'];
  283.                 if ($exception instanceof SilencedErrorContext) {
  284.                     if (isset($silencedLogs[$h spl_object_hash($exception)])) {
  285.                         continue;
  286.                     }
  287.                     $silencedLogs[$h] = true;
  288.                     $count['scream_count'] += $exception->count;
  289.                 } else {
  290.                     ++$count['deprecation_count'];
  291.                 }
  292.             }
  293.         }
  294.         foreach ($containerDeprecationLogs as $deprecationLog) {
  295.             $count['deprecation_count'] += $deprecationLog['context']['exception']->count;
  296.         }
  297.         ksort($count['priorities']);
  298.         return $count;
  299.     }
  300. }