vendor/symfony/monolog-bridge/Formatter/ConsoleFormatter.php line 191

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\Bridge\Monolog\Formatter;
  11. use Monolog\Formatter\FormatterInterface;
  12. use Monolog\Logger;
  13. use Monolog\LogRecord;
  14. use Symfony\Component\Console\Formatter\OutputFormatter;
  15. use Symfony\Component\VarDumper\Cloner\Data;
  16. use Symfony\Component\VarDumper\Cloner\Stub;
  17. use Symfony\Component\VarDumper\Cloner\VarCloner;
  18. use Symfony\Component\VarDumper\Dumper\CliDumper;
  19. /**
  20.  * Formats incoming records for console output by coloring them depending on log level.
  21.  *
  22.  * @author Tobias Schultze <http://tobion.de>
  23.  * @author GrĂ©goire Pineau <lyrixx@lyrixx.info>
  24.  *
  25.  * @final since Symfony 6.1
  26.  */
  27. class ConsoleFormatter implements FormatterInterface
  28. {
  29.     use CompatibilityFormatter;
  30.     public const SIMPLE_FORMAT "%datetime% %start_tag%%level_name%%end_tag% <comment>[%channel%]</> %message%%context%%extra%\n";
  31.     public const SIMPLE_DATE 'H:i:s';
  32.     private const LEVEL_COLOR_MAP = [
  33.         Logger::DEBUG => 'fg=white',
  34.         Logger::INFO => 'fg=green',
  35.         Logger::NOTICE => 'fg=blue',
  36.         Logger::WARNING => 'fg=cyan',
  37.         Logger::ERROR => 'fg=yellow',
  38.         Logger::CRITICAL => 'fg=red',
  39.         Logger::ALERT => 'fg=red',
  40.         Logger::EMERGENCY => 'fg=white;bg=red',
  41.     ];
  42.     private array $options;
  43.     private VarCloner $cloner;
  44.     /**
  45.      * @var resource|null
  46.      */
  47.     private $outputBuffer;
  48.     private CliDumper $dumper;
  49.     /**
  50.      * Available options:
  51.      *   * format: The format of the outputted log string. The following placeholders are supported: %datetime%, %start_tag%, %level_name%, %end_tag%, %channel%, %message%, %context%, %extra%;
  52.      *   * date_format: The format of the outputted date string;
  53.      *   * colors: If true, the log string contains ANSI code to add color;
  54.      *   * multiline: If false, "context" and "extra" are dumped on one line.
  55.      */
  56.     public function __construct(array $options = [])
  57.     {
  58.         $this->options array_replace([
  59.             'format' => self::SIMPLE_FORMAT,
  60.             'date_format' => self::SIMPLE_DATE,
  61.             'colors' => true,
  62.             'multiline' => false,
  63.             'level_name_format' => '%-9s',
  64.             'ignore_empty_context_and_extra' => true,
  65.         ], $options);
  66.         if (class_exists(VarCloner::class)) {
  67.             $this->cloner = new VarCloner();
  68.             $this->cloner->addCasters([
  69.                 '*' => $this->castObject(...),
  70.             ]);
  71.             $this->outputBuffer fopen('php://memory''r+');
  72.             if ($this->options['multiline']) {
  73.                 $output $this->outputBuffer;
  74.             } else {
  75.                 $output $this->echoLine(...);
  76.             }
  77.             $this->dumper = new CliDumper($outputnullCliDumper::DUMP_LIGHT_ARRAY CliDumper::DUMP_COMMA_SEPARATOR);
  78.         }
  79.     }
  80.     /**
  81.      * {@inheritdoc}
  82.      */
  83.     public function formatBatch(array $records): mixed
  84.     {
  85.         foreach ($records as $key => $record) {
  86.             $records[$key] = $this->format($record);
  87.         }
  88.         return $records;
  89.     }
  90.     private function doFormat(array|LogRecord $record): mixed
  91.     {
  92.         if ($record instanceof LogRecord) {
  93.             $record $record->toArray();
  94.         }
  95.         $record $this->replacePlaceHolder($record);
  96.         if (!$this->options['ignore_empty_context_and_extra'] || !empty($record['context'])) {
  97.             $context = ($this->options['multiline'] ? "\n" ' ').$this->dumpData($record['context']);
  98.         } else {
  99.             $context '';
  100.         }
  101.         if (!$this->options['ignore_empty_context_and_extra'] || !empty($record['extra'])) {
  102.             $extra = ($this->options['multiline'] ? "\n" ' ').$this->dumpData($record['extra']);
  103.         } else {
  104.             $extra '';
  105.         }
  106.         $formatted strtr($this->options['format'], [
  107.             '%datetime%' => $record['datetime'] instanceof \DateTimeInterface
  108.                 $record['datetime']->format($this->options['date_format'])
  109.                 : $record['datetime'],
  110.             '%start_tag%' => sprintf('<%s>'self::LEVEL_COLOR_MAP[$record['level']]),
  111.             '%level_name%' => sprintf($this->options['level_name_format'], $record['level_name']),
  112.             '%end_tag%' => '</>',
  113.             '%channel%' => $record['channel'],
  114.             '%message%' => $this->replacePlaceHolder($record)['message'],
  115.             '%context%' => $context,
  116.             '%extra%' => $extra,
  117.         ]);
  118.         return $formatted;
  119.     }
  120.     /**
  121.      * @internal
  122.      */
  123.     public function echoLine(string $lineint $depthstring $indentPad)
  124.     {
  125.         if (-!== $depth) {
  126.             fwrite($this->outputBuffer$line);
  127.         }
  128.     }
  129.     /**
  130.      * @internal
  131.      */
  132.     public function castObject(mixed $v, array $aStub $sbool $isNested): array
  133.     {
  134.         if ($this->options['multiline']) {
  135.             return $a;
  136.         }
  137.         if ($isNested && !$v instanceof \DateTimeInterface) {
  138.             $s->cut = -1;
  139.             $a = [];
  140.         }
  141.         return $a;
  142.     }
  143.     private function replacePlaceHolder(array $record): array
  144.     {
  145.         $message $record['message'];
  146.         if (!str_contains($message'{')) {
  147.             return $record;
  148.         }
  149.         $context $record['context'];
  150.         $replacements = [];
  151.         foreach ($context as $k => $v) {
  152.             // Remove quotes added by the dumper around string.
  153.             $v trim($this->dumpData($vfalse), '"');
  154.             $v OutputFormatter::escape($v);
  155.             $replacements['{'.$k.'}'] = sprintf('<comment>%s</>'$v);
  156.         }
  157.         $record['message'] = strtr($message$replacements);
  158.         return $record;
  159.     }
  160.     private function dumpData(mixed $databool $colors null): string
  161.     {
  162.         if (!isset($this->dumper)) {
  163.             return '';
  164.         }
  165.         if (null === $colors) {
  166.             $this->dumper->setColors($this->options['colors']);
  167.         } else {
  168.             $this->dumper->setColors($colors);
  169.         }
  170.         if (!$data instanceof Data) {
  171.             $data $this->cloner->cloneVar($data);
  172.         }
  173.         $data $data->withRefHandles(false);
  174.         $this->dumper->dump($data);
  175.         $dump stream_get_contents($this->outputBuffer, -10);
  176.         rewind($this->outputBuffer);
  177.         ftruncate($this->outputBuffer0);
  178.         return rtrim($dump);
  179.     }
  180. }