Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
14.29% covered (danger)
14.29%
1 / 7
CRAP
50.00% covered (danger)
50.00%
35 / 70
ConsoleFormatter
0.00% covered (danger)
0.00%
0 / 1
14.29% covered (danger)
14.29%
1 / 7
110.50
50.00% covered (danger)
50.00%
35 / 70
 __construct
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
14 / 14
 formatBatch
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 3
 format
0.00% covered (danger)
0.00%
0 / 1
8.06
90.00% covered (success)
90.00%
18 / 20
 echoLine
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 3
 castObject
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 6
 replacePlaceHolder
0.00% covered (danger)
0.00%
0 / 1
6.46
27.27% covered (danger)
27.27%
3 / 11
 dumpData
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 13
<?php
/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Symfony\Bridge\Monolog\Formatter;
use Monolog\Formatter\FormatterInterface;
use Monolog\Logger;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\Stub;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
/**
 * Formats incoming records for console output by coloring them depending on log level.
 *
 * @author Tobias Schultze <http://tobion.de>
 * @author GrĂ©goire Pineau <lyrixx@lyrixx.info>
 */
class ConsoleFormatter implements FormatterInterface
{
    const SIMPLE_FORMAT = "%datetime% %start_tag%%level_name%%end_tag% <comment>[%channel%]</> %message%%context%%extra%\n";
    const SIMPLE_DATE = 'H:i:s';
    private static $levelColorMap = [
        Logger::DEBUG => 'fg=white',
        Logger::INFO => 'fg=green',
        Logger::NOTICE => 'fg=blue',
        Logger::WARNING => 'fg=cyan',
        Logger::ERROR => 'fg=yellow',
        Logger::CRITICAL => 'fg=red',
        Logger::ALERT => 'fg=red',
        Logger::EMERGENCY => 'fg=white;bg=red',
    ];
    private $options;
    private $cloner;
    private $outputBuffer;
    private $dumper;
    /**
     * Available options:
     *   * format: The format of the outputted log string. The following placeholders are supported: %datetime%, %start_tag%, %level_name%, %end_tag%, %channel%, %message%, %context%, %extra%;
     *   * date_format: The format of the outputted date string;
     *   * colors: If true, the log string contains ANSI code to add color;
     *   * multiline: If false, "context" and "extra" are dumped on one line.
     */
    public function __construct(array $options = [])
    {
        $this->options = array_replace([
            'format' => self::SIMPLE_FORMAT,
            'date_format' => self::SIMPLE_DATE,
            'colors' => true,
            'multiline' => false,
            'level_name_format' => '%-9s',
            'ignore_empty_context_and_extra' => true,
        ], $options);
        if (class_exists(VarCloner::class)) {
            $this->cloner = new VarCloner();
            $this->cloner->addCasters([
                '*' => [$this, 'castObject'],
            ]);
            $this->outputBuffer = fopen('php://memory', 'r+b');
            if ($this->options['multiline']) {
                $output = $this->outputBuffer;
            } else {
                $output = [$this, 'echoLine'];
            }
            $this->dumper = new CliDumper($output, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR);
        }
    }
    /**
     * {@inheritdoc}
     */
    public function formatBatch(array $records)
    {
        foreach ($records as $key => $record) {
            $records[$key] = $this->format($record);
        }
        return $records;
    }
    /**
     * {@inheritdoc}
     */
    public function format(array $record)
    {
        $record = $this->replacePlaceHolder($record);
        $levelColor = self::$levelColorMap[$record['level']];
        if (!$this->options['ignore_empty_context_and_extra'] || !empty($record['context'])) {
            $context = ($this->options['multiline'] ? "\n" : ' ').$this->dumpData($record['context']);
        } else {
            $context = '';
        }
        if (!$this->options['ignore_empty_context_and_extra'] || !empty($record['extra'])) {
            $extra = ($this->options['multiline'] ? "\n" : ' ').$this->dumpData($record['extra']);
        } else {
            $extra = '';
        }
        $formatted = strtr($this->options['format'], [
            '%datetime%' => $record['datetime'] instanceof \DateTimeInterface
                ? $record['datetime']->format($this->options['date_format'])
                : $record['datetime'],
            '%start_tag%' => sprintf('<%s>', $levelColor),
            '%level_name%' => sprintf($this->options['level_name_format'], $record['level_name']),
            '%end_tag%' => '</>',
            '%channel%' => $record['channel'],
            '%message%' => $this->replacePlaceHolder($record)['message'],
            '%context%' => $context,
            '%extra%' => $extra,
        ]);
        return $formatted;
    }
    /**
     * @internal
     */
    public function echoLine(string $line, int $depth, string $indentPad)
    {
        if (-1 !== $depth) {
            fwrite($this->outputBuffer, $line);
        }
    }
    /**
     * @internal
     */
    public function castObject($v, array $a, Stub $s, bool $isNested): array
    {
        if ($this->options['multiline']) {
            return $a;
        }
        if ($isNested && !$v instanceof \DateTimeInterface) {
            $s->cut = -1;
            $a = [];
        }
        return $a;
    }
    private function replacePlaceHolder(array $record): array
    {
        $message = $record['message'];
        if (false === strpos($message, '{')) {
            return $record;
        }
        $context = $record['context'];
        $replacements = [];
        foreach ($context as $k => $v) {
            // Remove quotes added by the dumper around string.
            $v = trim($this->dumpData($v, false), '"');
            $v = OutputFormatter::escape($v);
            $replacements['{'.$k.'}'] = sprintf('<comment>%s</>', $v);
        }
        $record['message'] = strtr($message, $replacements);
        return $record;
    }
    private function dumpData($data, bool $colors = null): string
    {
        if (null === $this->dumper) {
            return '';
        }
        if (null === $colors) {
            $this->dumper->setColors($this->options['colors']);
        } else {
            $this->dumper->setColors($colors);
        }
        if (!$data instanceof Data) {
            $data = $this->cloner->cloneVar($data);
        }
        $data = $data->withRefHandles(false);
        $this->dumper->dump($data);
        $dump = stream_get_contents($this->outputBuffer, -1, 0);
        rewind($this->outputBuffer);
        ftruncate($this->outputBuffer, 0);
        return rtrim($dump);
    }
}