vendor/symfony/http-kernel/Log/Logger.php line 138

  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\Log;
  11. use Psr\Log\AbstractLogger;
  12. use Psr\Log\InvalidArgumentException;
  13. use Psr\Log\LogLevel;
  14. use Symfony\Component\HttpFoundation\Request;
  15. use Symfony\Component\HttpFoundation\RequestStack;
  16. /**
  17.  * Minimalist PSR-3 logger designed to write in stderr or any other stream.
  18.  *
  19.  * @author Kévin Dunglas <dunglas@gmail.com>
  20.  */
  21. class Logger extends AbstractLogger implements DebugLoggerInterface
  22. {
  23.     private const LEVELS = [
  24.         LogLevel::DEBUG => 0,
  25.         LogLevel::INFO => 1,
  26.         LogLevel::NOTICE => 2,
  27.         LogLevel::WARNING => 3,
  28.         LogLevel::ERROR => 4,
  29.         LogLevel::CRITICAL => 5,
  30.         LogLevel::ALERT => 6,
  31.         LogLevel::EMERGENCY => 7,
  32.     ];
  33.     private const PRIORITIES = [
  34.         LogLevel::DEBUG => 100,
  35.         LogLevel::INFO => 200,
  36.         LogLevel::NOTICE => 250,
  37.         LogLevel::WARNING => 300,
  38.         LogLevel::ERROR => 400,
  39.         LogLevel::CRITICAL => 500,
  40.         LogLevel::ALERT => 550,
  41.         LogLevel::EMERGENCY => 600,
  42.     ];
  43.     private int $minLevelIndex;
  44.     private \Closure $formatter;
  45.     private bool $debug false;
  46.     private array $logs = [];
  47.     private array $errorCount = [];
  48.     /** @var resource|null */
  49.     private $handle;
  50.     /**
  51.      * @param string|resource|null $output
  52.      */
  53.     public function __construct(string $minLevel null$output null, callable $formatter null, private readonly ?RequestStack $requestStack null)
  54.     {
  55.         if (null === $minLevel) {
  56.             $minLevel null === $output || 'php://stdout' === $output || 'php://stderr' === $output LogLevel::ERROR LogLevel::WARNING;
  57.             if (isset($_ENV['SHELL_VERBOSITY']) || isset($_SERVER['SHELL_VERBOSITY'])) {
  58.                 $minLevel = match ((int) ($_ENV['SHELL_VERBOSITY'] ?? $_SERVER['SHELL_VERBOSITY'])) {
  59.                     -=> LogLevel::ERROR,
  60.                     => LogLevel::NOTICE,
  61.                     => LogLevel::INFO,
  62.                     => LogLevel::DEBUG,
  63.                     default => $minLevel,
  64.                 };
  65.             }
  66.         }
  67.         if (!isset(self::LEVELS[$minLevel])) {
  68.             throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.'$minLevel));
  69.         }
  70.         $this->minLevelIndex self::LEVELS[$minLevel];
  71.         $this->formatter null !== $formatter $formatter(...) : $this->format(...);
  72.         if ($output && false === $this->handle \is_resource($output) ? $output : @fopen($output'a')) {
  73.             throw new InvalidArgumentException(sprintf('Unable to open "%s".'$output));
  74.         }
  75.     }
  76.     public function enableDebug(): void
  77.     {
  78.         $this->debug true;
  79.     }
  80.     public function log($level$message, array $context = []): void
  81.     {
  82.         if (!isset(self::LEVELS[$level])) {
  83.             throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.'$level));
  84.         }
  85.         if (self::LEVELS[$level] < $this->minLevelIndex) {
  86.             return;
  87.         }
  88.         $formatter $this->formatter;
  89.         if ($this->handle) {
  90.             @fwrite($this->handle$formatter($level$message$context).\PHP_EOL);
  91.         } else {
  92.             error_log($formatter($level$message$contextfalse));
  93.         }
  94.         if ($this->debug && $this->requestStack) {
  95.             $this->record($level$message$context);
  96.         }
  97.     }
  98.     public function getLogs(Request $request null): array
  99.     {
  100.         if ($request) {
  101.             return $this->logs[spl_object_id($request)] ?? [];
  102.         }
  103.         return array_merge(...array_values($this->logs));
  104.     }
  105.     public function countErrors(Request $request null): int
  106.     {
  107.         if ($request) {
  108.             return $this->errorCount[spl_object_id($request)] ?? 0;
  109.         }
  110.         return array_sum($this->errorCount);
  111.     }
  112.     public function clear(): void
  113.     {
  114.         $this->logs = [];
  115.         $this->errorCount = [];
  116.     }
  117.     private function format(string $levelstring $message, array $contextbool $prefixDate true): string
  118.     {
  119.         if (str_contains($message'{')) {
  120.             $replacements = [];
  121.             foreach ($context as $key => $val) {
  122.                 if (null === $val || \is_scalar($val) || $val instanceof \Stringable) {
  123.                     $replacements["{{$key}}"] = $val;
  124.                 } elseif ($val instanceof \DateTimeInterface) {
  125.                     $replacements["{{$key}}"] = $val->format(\DateTimeInterface::RFC3339);
  126.                 } elseif (\is_object($val)) {
  127.                     $replacements["{{$key}}"] = '[object '.$val::class.']';
  128.                 } else {
  129.                     $replacements["{{$key}}"] = '['.\gettype($val).']';
  130.                 }
  131.             }
  132.             $message strtr($message$replacements);
  133.         }
  134.         $log sprintf('[%s] %s'$level$message);
  135.         if ($prefixDate) {
  136.             $log date(\DateTimeInterface::RFC3339).' '.$log;
  137.         }
  138.         return $log;
  139.     }
  140.     private function record($level$message, array $context): void
  141.     {
  142.         $request $this->requestStack->getCurrentRequest();
  143.         $key $request spl_object_id($request) : '';
  144.         $this->logs[$key][] = [
  145.             'channel' => null,
  146.             'context' => $context,
  147.             'message' => $message,
  148.             'priority' => self::PRIORITIES[$level],
  149.             'priorityName' => $level,
  150.             'timestamp' => time(),
  151.             'timestamp_rfc3339' => date(\DATE_RFC3339_EXTENDED),
  152.         ];
  153.         $this->errorCount[$key] ??= 0;
  154.         switch ($level) {
  155.             case LogLevel::ERROR:
  156.             case LogLevel::CRITICAL:
  157.             case LogLevel::ALERT:
  158.             case LogLevel::EMERGENCY:
  159.                 ++$this->errorCount[$key];
  160.         }
  161.     }
  162. }