268 lines
9 KiB
PHP
268 lines
9 KiB
PHP
<?php
|
|
namespace Consolidation\Log;
|
|
|
|
use Psr\Log\AbstractLogger;
|
|
use Psr\Log\InvalidArgumentException;
|
|
use Psr\Log\LogLevel;
|
|
use Symfony\Component\Console\Output\OutputInterface;
|
|
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
|
|
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
|
use Symfony\Component\Console\Input\StringInput;
|
|
|
|
/**
|
|
* Replacement for Symfony\Component\Console\Logger\ConsoleLogger.
|
|
* Each of the different log level messages are routed through the
|
|
* corresponding SymfonyStyle formatting method. Log messages are
|
|
* always sent to stderr if the provided output object implements
|
|
* ConsoleOutputInterface.
|
|
*
|
|
* Note that this class could extend ConsoleLogger if some methods
|
|
* of that class were declared 'protected' instead of 'private'.
|
|
*
|
|
* @author Greg Anderson <greg.1.anderson@greenknowe.org>
|
|
*/
|
|
class Logger extends AbstractLogger // extends ConsoleLogger
|
|
{
|
|
/**
|
|
* @var OutputInterface
|
|
*/
|
|
protected $output;
|
|
/**
|
|
* @var OutputInterface
|
|
*/
|
|
protected $error;
|
|
/**
|
|
* @var LogOutputStylerInterface
|
|
*/
|
|
protected $outputStyler;
|
|
/**
|
|
* @var OutputInterface|SymfonyStyle|other
|
|
*/
|
|
protected $outputStreamWrapper;
|
|
protected $errorStreamWrapper;
|
|
|
|
protected $formatFunctionMap = [
|
|
LogLevel::EMERGENCY => 'error',
|
|
LogLevel::ALERT => 'error',
|
|
LogLevel::CRITICAL => 'error',
|
|
LogLevel::ERROR => 'error',
|
|
LogLevel::WARNING => 'warning',
|
|
LogLevel::NOTICE => 'note',
|
|
LogLevel::INFO => 'note',
|
|
LogLevel::DEBUG => 'note',
|
|
ConsoleLogLevel::SUCCESS => 'success',
|
|
];
|
|
|
|
/**
|
|
* @param OutputInterface $output
|
|
* @param array $verbosityLevelMap
|
|
* @param array $formatLevelMap
|
|
* @param array $formatFunctionMap
|
|
*/
|
|
public function __construct(OutputInterface $output, array $verbosityLevelMap = array(), array $formatLevelMap = array(), array $formatFunctionMap = array())
|
|
{
|
|
$this->output = $output;
|
|
|
|
$this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap;
|
|
$this->formatLevelMap = $formatLevelMap + $this->formatLevelMap;
|
|
$this->formatFunctionMap = $formatFunctionMap + $this->formatFunctionMap;
|
|
}
|
|
|
|
public function setLogOutputStyler(LogOutputStylerInterface $outputStyler, array $formatFunctionMap = array())
|
|
{
|
|
$this->outputStyler = $outputStyler;
|
|
$this->formatFunctionMap = $formatFunctionMap + $this->formatFunctionMap;
|
|
$this->outputStreamWrapper = null;
|
|
$this->errorStreamWrapper = null;
|
|
}
|
|
|
|
public function getLogOutputStyler()
|
|
{
|
|
if (!isset($this->outputStyler)) {
|
|
$this->outputStyler = new SymfonyLogOutputStyler();
|
|
}
|
|
return $this->outputStyler;
|
|
}
|
|
|
|
protected function getOutputStream()
|
|
{
|
|
return $this->output;
|
|
}
|
|
|
|
protected function getErrorStream()
|
|
{
|
|
if (!isset($this->error)) {
|
|
$output = $this->getOutputStream();
|
|
if ($output instanceof ConsoleOutputInterface) {
|
|
$output = $output->getErrorOutput();
|
|
}
|
|
$this->error = $output;
|
|
}
|
|
return $this->error;
|
|
}
|
|
|
|
public function setOutputStream($output)
|
|
{
|
|
$this->output = $output;
|
|
$this->outputStreamWrapper = null;
|
|
}
|
|
|
|
public function setErrorStream($error)
|
|
{
|
|
$this->error = $error;
|
|
$this->errorStreamWrapper = null;
|
|
}
|
|
|
|
protected function getOutputStreamWrapper()
|
|
{
|
|
if (!isset($this->outputStreamWrapper)) {
|
|
$this->outputStreamWrapper = $this->getLogOutputStyler()->createOutputWrapper($this->getOutputStream());
|
|
}
|
|
return $this->outputStreamWrapper;
|
|
}
|
|
|
|
protected function getErrorStreamWrapper()
|
|
{
|
|
if (!isset($this->errorStreamWrapper)) {
|
|
$this->errorStreamWrapper = $this->getLogOutputStyler()->createOutputWrapper($this->getErrorStream());
|
|
}
|
|
return $this->errorStreamWrapper;
|
|
}
|
|
|
|
protected function getOutputStreamForLogLevel($level)
|
|
{
|
|
// Write to the error output if necessary and available.
|
|
// Usually, loggers that log to a terminal should send
|
|
// all log messages to stderr.
|
|
if (array_key_exists($level, $this->formatLevelMap) && ($this->formatLevelMap[$level] !== self::ERROR)) {
|
|
return $this->getOutputStreamWrapper();
|
|
}
|
|
return $this->getErrorStreamWrapper();
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function log($level, $message, array $context = array())
|
|
{
|
|
// We use the '_level' context variable to allow log messages
|
|
// to be logged at one level (e.g. NOTICE) and formatted at another
|
|
// level (e.g. SUCCESS). This helps in instances where we want
|
|
// to style log messages at a custom log level that might not
|
|
// be available in all loggers. If the logger does not recognize
|
|
// the log level, then it is treated like the original log level.
|
|
if (array_key_exists('_level', $context) && array_key_exists($context['_level'], $this->verbosityLevelMap)) {
|
|
$level = $context['_level'];
|
|
}
|
|
// It is a runtime error if someone logs at a log level that
|
|
// we do not recognize.
|
|
if (!isset($this->verbosityLevelMap[$level])) {
|
|
throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level));
|
|
}
|
|
|
|
// Write to the error output if necessary and available.
|
|
// Usually, loggers that log to a terminal should send
|
|
// all log messages to stderr.
|
|
$outputStreamWrapper = $this->getOutputStreamForLogLevel($level);
|
|
|
|
// Ignore messages that are not at the right verbosity level
|
|
if ($this->getOutputStream()->getVerbosity() >= $this->verbosityLevelMap[$level]) {
|
|
$this->doLog($outputStreamWrapper, $level, $message, $context);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Interpolate and style the message, and then send it to the log.
|
|
*/
|
|
protected function doLog($outputStreamWrapper, $level, $message, $context)
|
|
{
|
|
$formatFunction = 'log';
|
|
if (array_key_exists($level, $this->formatFunctionMap)) {
|
|
$formatFunction = $this->formatFunctionMap[$level];
|
|
}
|
|
$interpolated = $this->interpolate(
|
|
$message,
|
|
$this->getLogOutputStyler()->style($context)
|
|
);
|
|
$this->getLogOutputStyler()->$formatFunction(
|
|
$outputStreamWrapper,
|
|
$level,
|
|
$interpolated,
|
|
$context
|
|
);
|
|
}
|
|
|
|
public function success($message, array $context = array())
|
|
{
|
|
$this->log(ConsoleLogLevel::SUCCESS, $message, $context);
|
|
}
|
|
|
|
// The functions below could be eliminated if made `protected` intead
|
|
// of `private` in ConsoleLogger
|
|
|
|
const INFO = 'info';
|
|
const ERROR = 'error';
|
|
|
|
/**
|
|
* @var OutputInterface
|
|
*/
|
|
//private $output;
|
|
/**
|
|
* @var array
|
|
*/
|
|
private $verbosityLevelMap = [
|
|
LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL,
|
|
LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL,
|
|
LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL,
|
|
LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL,
|
|
LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL,
|
|
LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE,
|
|
LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE,
|
|
LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG,
|
|
ConsoleLogLevel::SUCCESS => OutputInterface::VERBOSITY_NORMAL,
|
|
];
|
|
|
|
/**
|
|
* @var array
|
|
*
|
|
* Send all log messages to stderr. Symfony should have the same default.
|
|
* See: https://en.wikipedia.org/wiki/Standard_streams
|
|
* "Standard error was added to Unix after several wasted phototypesetting runs ended with error messages being typeset instead of displayed on the user's terminal."
|
|
*/
|
|
private $formatLevelMap = [
|
|
LogLevel::EMERGENCY => self::ERROR,
|
|
LogLevel::ALERT => self::ERROR,
|
|
LogLevel::CRITICAL => self::ERROR,
|
|
LogLevel::ERROR => self::ERROR,
|
|
LogLevel::WARNING => self::ERROR,
|
|
LogLevel::NOTICE => self::ERROR,
|
|
LogLevel::INFO => self::ERROR,
|
|
LogLevel::DEBUG => self::ERROR,
|
|
ConsoleLogLevel::SUCCESS => self::ERROR,
|
|
];
|
|
|
|
/**
|
|
* Interpolates context values into the message placeholders.
|
|
*
|
|
* @author PHP Framework Interoperability Group
|
|
*
|
|
* @param string $message
|
|
* @param array $context
|
|
*
|
|
* @return string
|
|
*/
|
|
private function interpolate($message, array $context)
|
|
{
|
|
// build a replacement array with braces around the context keys
|
|
$replace = array();
|
|
foreach ($context as $key => $val) {
|
|
if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
|
|
$replace[sprintf('{%s}', $key)] = $val;
|
|
}
|
|
}
|
|
|
|
// interpolate replacement values into the message and return
|
|
return strtr($message, $replace);
|
|
}
|
|
}
|