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 nullstring $containerPathPrefix nullRequestStack $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.                 'errorCounter' => isset($rawLogData['errorCounter']) ? $rawLogData['errorCounter']->getValue() : 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.                 'Warning' => 300,
  119.                 'Error' => 400,
  120.                 'Critical' => 500,
  121.                 'Alert' => 550,
  122.                 'Emergency' => 600,
  123.             ],
  124.         ];
  125.         $allChannels = [];
  126.         foreach ($this->getProcessedLogs() as $log) {
  127.             if ('' === trim($log['channel'])) {
  128.                 continue;
  129.             }
  130.             $allChannels[] = $log['channel'];
  131.         }
  132.         $channels array_unique($allChannels);
  133.         sort($channels);
  134.         $filters['channel'] = $channels;
  135.         return $filters;
  136.     }
  137.     public function getPriorities()
  138.     {
  139.         return $this->data['priorities'] ?? [];
  140.     }
  141.     public function countErrors()
  142.     {
  143.         return $this->data['error_count'] ?? 0;
  144.     }
  145.     public function countDeprecations()
  146.     {
  147.         return $this->data['deprecation_count'] ?? 0;
  148.     }
  149.     public function countWarnings()
  150.     {
  151.         return $this->data['warning_count'] ?? 0;
  152.     }
  153.     public function countScreams()
  154.     {
  155.         return $this->data['scream_count'] ?? 0;
  156.     }
  157.     public function getCompilerLogs()
  158.     {
  159.         return $this->cloneVar($this->getContainerCompilerLogs($this->data['compiler_logs_filepath'] ?? null));
  160.     }
  161.     /**
  162.      * {@inheritdoc}
  163.      */
  164.     public function getName(): string
  165.     {
  166.         return 'logger';
  167.     }
  168.     private function getContainerDeprecationLogs(): array
  169.     {
  170.         if (null === $this->containerPathPrefix || !is_file($file $this->containerPathPrefix.'Deprecations.log')) {
  171.             return [];
  172.         }
  173.         if ('' === $logContent trim(file_get_contents($file))) {
  174.             return [];
  175.         }
  176.         $bootTime filemtime($file);
  177.         $logs = [];
  178.         foreach (unserialize($logContent) as $log) {
  179.             $log['context'] = ['exception' => new SilencedErrorContext($log['type'], $log['file'], $log['line'], $log['trace'], $log['count'])];
  180.             $log['timestamp'] = $bootTime;
  181.             $log['timestamp_rfc3339'] = (new \DateTimeImmutable())->setTimestamp($bootTime)->format(\DateTimeInterface::RFC3339_EXTENDED);
  182.             $log['priority'] = 100;
  183.             $log['priorityName'] = 'DEBUG';
  184.             $log['channel'] = null;
  185.             $log['scream'] = false;
  186.             unset($log['type'], $log['file'], $log['line'], $log['trace'], $log['trace'], $log['count']);
  187.             $logs[] = $log;
  188.         }
  189.         return $logs;
  190.     }
  191.     private function getContainerCompilerLogs(string $compilerLogsFilepath null): array
  192.     {
  193.         if (!is_file($compilerLogsFilepath)) {
  194.             return [];
  195.         }
  196.         $logs = [];
  197.         foreach (file($compilerLogsFilepath\FILE_IGNORE_NEW_LINES) as $log) {
  198.             $log explode(': '$log2);
  199.             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])) {
  200.                 $log = ['Unknown Compiler Pass'implode(': '$log)];
  201.             }
  202.             $logs[$log[0]][] = ['message' => $log[1]];
  203.         }
  204.         return $logs;
  205.     }
  206.     private function sanitizeLogs(array $logs)
  207.     {
  208.         $sanitizedLogs = [];
  209.         $silencedLogs = [];
  210.         foreach ($logs as $log) {
  211.             if (!$this->isSilencedOrDeprecationErrorLog($log)) {
  212.                 $sanitizedLogs[] = $log;
  213.                 continue;
  214.             }
  215.             $message '_'.$log['message'];
  216.             $exception $log['context']['exception'];
  217.             if ($exception instanceof SilencedErrorContext) {
  218.                 if (isset($silencedLogs[$h spl_object_hash($exception)])) {
  219.                     continue;
  220.                 }
  221.                 $silencedLogs[$h] = true;
  222.                 if (!isset($sanitizedLogs[$message])) {
  223.                     $sanitizedLogs[$message] = $log + [
  224.                         'errorCount' => 0,
  225.                         'scream' => true,
  226.                     ];
  227.                 }
  228.                 $sanitizedLogs[$message]['errorCount'] += $exception->count;
  229.                 continue;
  230.             }
  231.             $errorId md5("{$exception->getSeverity()}/{$exception->getLine()}/{$exception->getFile()}\0{$message}"true);
  232.             if (isset($sanitizedLogs[$errorId])) {
  233.                 ++$sanitizedLogs[$errorId]['errorCount'];
  234.             } else {
  235.                 $log += [
  236.                     'errorCount' => 1,
  237.                     'scream' => false,
  238.                 ];
  239.                 $sanitizedLogs[$errorId] = $log;
  240.             }
  241.         }
  242.         return array_values($sanitizedLogs);
  243.     }
  244.     private function isSilencedOrDeprecationErrorLog(array $log): bool
  245.     {
  246.         if (!isset($log['context']['exception'])) {
  247.             return false;
  248.         }
  249.         $exception $log['context']['exception'];
  250.         if ($exception instanceof SilencedErrorContext) {
  251.             return true;
  252.         }
  253.         if ($exception instanceof \ErrorException && \in_array($exception->getSeverity(), [\E_DEPRECATED\E_USER_DEPRECATED], true)) {
  254.             return true;
  255.         }
  256.         return false;
  257.     }
  258.     private function computeErrorsCount(array $containerDeprecationLogs): array
  259.     {
  260.         $silencedLogs = [];
  261.         $count = [
  262.             'error_count' => $this->logger->countErrors($this->currentRequest),
  263.             'deprecation_count' => 0,
  264.             'warning_count' => 0,
  265.             'scream_count' => 0,
  266.             'priorities' => [],
  267.         ];
  268.         foreach ($this->logger->getLogs($this->currentRequest) as $log) {
  269.             if (isset($count['priorities'][$log['priority']])) {
  270.                 ++$count['priorities'][$log['priority']]['count'];
  271.             } else {
  272.                 $count['priorities'][$log['priority']] = [
  273.                     'count' => 1,
  274.                     'name' => $log['priorityName'],
  275.                 ];
  276.             }
  277.             if ('WARNING' === $log['priorityName']) {
  278.                 ++$count['warning_count'];
  279.             }
  280.             if ($this->isSilencedOrDeprecationErrorLog($log)) {
  281.                 $exception $log['context']['exception'];
  282.                 if ($exception instanceof SilencedErrorContext) {
  283.                     if (isset($silencedLogs[$h spl_object_hash($exception)])) {
  284.                         continue;
  285.                     }
  286.                     $silencedLogs[$h] = true;
  287.                     $count['scream_count'] += $exception->count;
  288.                 } else {
  289.                     ++$count['deprecation_count'];
  290.                 }
  291.             }
  292.         }
  293.         foreach ($containerDeprecationLogs as $deprecationLog) {
  294.             $count['deprecation_count'] += $deprecationLog['context']['exception']->count;
  295.         }
  296.         ksort($count['priorities']);
  297.         return $count;
  298.     }
  299. }