2 namespace Consolidation\Log;
4 use Psr\Log\AbstractLogger;
5 use Psr\Log\InvalidArgumentException;
7 use Symfony\Component\Console\Output\OutputInterface;
8 use Symfony\Component\Console\Output\ConsoleOutputInterface;
10 use Symfony\Component\Console\Style\SymfonyStyle;
11 use Symfony\Component\Console\Input\StringInput;
14 * Replacement for Symfony\Component\Console\Logger\ConsoleLogger.
15 * Each of the different log level messages are routed through the
16 * corresponding SymfonyStyle formatting method. Log messages are
17 * always sent to stderr if the provided output object implements
18 * ConsoleOutputInterface.
20 * Note that this class could extend ConsoleLogger if some methods
21 * of that class were declared 'protected' instead of 'private'.
23 * @author Greg Anderson <greg.1.anderson@greenknowe.org>
25 class Logger extends AbstractLogger // extends ConsoleLogger
28 * @var OutputInterface
32 * @var OutputInterface
36 * @var LogOutputStylerInterface
38 protected $outputStyler;
40 * @var OutputInterface|SymfonyStyle|other
42 protected $outputStreamWrapper;
43 protected $errorStreamWrapper;
45 protected $formatFunctionMap = [
46 LogLevel::EMERGENCY => 'error',
47 LogLevel::ALERT => 'error',
48 LogLevel::CRITICAL => 'error',
49 LogLevel::ERROR => 'error',
50 LogLevel::WARNING => 'warning',
51 LogLevel::NOTICE => 'note',
52 LogLevel::INFO => 'note',
53 LogLevel::DEBUG => 'note',
54 ConsoleLogLevel::SUCCESS => 'success',
58 * @param OutputInterface $output
59 * @param array $verbosityLevelMap
60 * @param array $formatLevelMap
61 * @param array $formatFunctionMap
63 public function __construct(OutputInterface $output, array $verbosityLevelMap = array(), array $formatLevelMap = array(), array $formatFunctionMap = array())
65 $this->output = $output;
67 $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap;
68 $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap;
69 $this->formatFunctionMap = $formatFunctionMap + $this->formatFunctionMap;
72 public function setLogOutputStyler(LogOutputStylerInterface $outputStyler, array $formatFunctionMap = array())
74 $this->outputStyler = $outputStyler;
75 $this->formatFunctionMap = $formatFunctionMap + $this->formatFunctionMap;
76 $this->outputStreamWrapper = null;
77 $this->errorStreamWrapper = null;
80 public function getLogOutputStyler()
82 if (!isset($this->outputStyler)) {
83 $this->outputStyler = new SymfonyLogOutputStyler();
85 return $this->outputStyler;
88 protected function getOutputStream()
93 protected function getErrorStream()
95 if (!isset($this->error)) {
96 $output = $this->getOutputStream();
97 if ($output instanceof ConsoleOutputInterface) {
98 $output = $output->getErrorOutput();
100 $this->error = $output;
105 public function setOutputStream($output)
107 $this->output = $output;
108 $this->outputStreamWrapper = null;
111 public function setErrorStream($error)
113 $this->error = $error;
114 $this->errorStreamWrapper = null;
117 protected function getOutputStreamWrapper()
119 if (!isset($this->outputStreamWrapper)) {
120 $this->outputStreamWrapper = $this->getLogOutputStyler()->createOutputWrapper($this->getOutputStream());
122 return $this->outputStreamWrapper;
125 protected function getErrorStreamWrapper()
127 if (!isset($this->errorStreamWrapper)) {
128 $this->errorStreamWrapper = $this->getLogOutputStyler()->createOutputWrapper($this->getErrorStream());
130 return $this->errorStreamWrapper;
133 protected function getOutputStreamForLogLevel($level)
135 // Write to the error output if necessary and available.
136 // Usually, loggers that log to a terminal should send
137 // all log messages to stderr.
138 if (array_key_exists($level, $this->formatLevelMap) && ($this->formatLevelMap[$level] !== self::ERROR)) {
139 return $this->getOutputStreamWrapper();
141 return $this->getErrorStreamWrapper();
147 public function log($level, $message, array $context = array())
149 // We use the '_level' context variable to allow log messages
150 // to be logged at one level (e.g. NOTICE) and formatted at another
151 // level (e.g. SUCCESS). This helps in instances where we want
152 // to style log messages at a custom log level that might not
153 // be available in all loggers. If the logger does not recognize
154 // the log level, then it is treated like the original log level.
155 if (array_key_exists('_level', $context) && array_key_exists($context['_level'], $this->verbosityLevelMap)) {
156 $level = $context['_level'];
158 // It is a runtime error if someone logs at a log level that
159 // we do not recognize.
160 if (!isset($this->verbosityLevelMap[$level])) {
161 throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level));
164 // Write to the error output if necessary and available.
165 // Usually, loggers that log to a terminal should send
166 // all log messages to stderr.
167 $outputStreamWrapper = $this->getOutputStreamForLogLevel($level);
169 // Ignore messages that are not at the right verbosity level
170 if ($this->getOutputStream()->getVerbosity() >= $this->verbosityLevelMap[$level]) {
171 $this->doLog($outputStreamWrapper, $level, $message, $context);
176 * Interpolate and style the message, and then send it to the log.
178 protected function doLog($outputStreamWrapper, $level, $message, $context)
180 $formatFunction = 'log';
181 if (array_key_exists($level, $this->formatFunctionMap)) {
182 $formatFunction = $this->formatFunctionMap[$level];
184 $interpolated = $this->interpolate(
186 $this->getLogOutputStyler()->style($context)
188 $this->getLogOutputStyler()->$formatFunction(
189 $outputStreamWrapper,
196 public function success($message, array $context = array())
198 $this->log(ConsoleLogLevel::SUCCESS, $message, $context);
201 // The functions below could be eliminated if made `protected` intead
202 // of `private` in ConsoleLogger
205 const ERROR = 'error';
208 * @var OutputInterface
214 private $verbosityLevelMap = [
215 LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL,
216 LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL,
217 LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL,
218 LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL,
219 LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL,
220 LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE,
221 LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE,
222 LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG,
223 ConsoleLogLevel::SUCCESS => OutputInterface::VERBOSITY_NORMAL,
229 * Send all log messages to stderr. Symfony should have the same default.
230 * See: https://en.wikipedia.org/wiki/Standard_streams
231 * "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."
233 private $formatLevelMap = [
234 LogLevel::EMERGENCY => self::ERROR,
235 LogLevel::ALERT => self::ERROR,
236 LogLevel::CRITICAL => self::ERROR,
237 LogLevel::ERROR => self::ERROR,
238 LogLevel::WARNING => self::ERROR,
239 LogLevel::NOTICE => self::ERROR,
240 LogLevel::INFO => self::ERROR,
241 LogLevel::DEBUG => self::ERROR,
242 ConsoleLogLevel::SUCCESS => self::ERROR,
246 * Interpolates context values into the message placeholders.
248 * @author PHP Framework Interoperability Group
250 * @param string $message
251 * @param array $context
255 private function interpolate($message, array $context)
257 // build a replacement array with braces around the context keys
259 foreach ($context as $key => $val) {
260 if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
261 $replace[sprintf('{%s}', $key)] = $val;
265 // interpolate replacement values into the message and return
266 return strtr($message, $replace);