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

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