Completed
Push — master ( 46caef...03c918 )
by Greg
02:18
created

Logger::getOutputStreamForLogLevel()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 10
rs 9.4285
cc 3
eloc 4
nc 2
nop 1
1
<?php
2
namespace Consolidation\Log;
3
4
use Psr\Log\AbstractLogger;
5
use Psr\Log\InvalidArgumentException;
6
use Psr\Log\LogLevel;
7
use Symfony\Component\Console\Output\OutputInterface;
8
use Symfony\Component\Console\Output\ConsoleOutputInterface;
9
10
use Symfony\Component\Console\Style\SymfonyStyle;
11
use Symfony\Component\Console\Input\StringInput;
12
13
/**
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.
19
 *
20
 * Note that this class could extend ConsoleLogger if some methods
21
 * of that class were declared 'protected' instead of 'private'.
22
 *
23
 * @author Greg Anderson <[email protected]>
24
 */
25
class Logger extends AbstractLogger // extends ConsoleLogger
26
{
27
    /**
28
     * @var OutputInterface
29
     */
30
    protected $output;
31
    /**
32
     * @var OutputInterface
33
     */
34
    protected $error;
35
    /**
36
     * @var LogOutputStylerInterface
37
     */
38
    protected $outputStyler;
39
    /**
40
     * @var OutputInterface|SymfonyStyle|other
41
     */
42
    protected $outputStreamWrapper;
43
    protected $errorStreamWrapper;
44
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',
55
    ];
56
57
    /**
58
     * @param OutputInterface $output
59
     * @param array           $verbosityLevelMap
60
     * @param array           $formatLevelMap
61
     * @param array           $formatFunctionMap
62
     */
63
    public function __construct(OutputInterface $output, array $verbosityLevelMap = array(), array $formatLevelMap = array(), array $formatFunctionMap = array())
64
    {
65
        $this->output = $output;
66
67
        $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap;
68
        $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap;
69
        $this->formatFunctionMap = $formatFunctionMap + $this->formatFunctionMap;
70
    }
71
72
    public function setLogOutputStyler(LogOutputStylerInterface $outputStyler, array $formatFunctionMap = array())
73
    {
74
        $this->outputStyler = $outputStyler;
75
        $this->formatFunctionMap = $formatFunctionMap + $this->formatFunctionMap;
76
        $this->outputStreamWrapper = null;
77
        $this->errorStreamWrapper = null;
78
    }
79
80
    public function getLogOutputStyler()
81
    {
82
        if (!isset($this->outputStyler)) {
83
            $this->outputStyler = new SymfonyLogOutputStyler();
84
        }
85
        return $this->outputStyler;
86
    }
87
88
    protected function getOutputStream()
89
    {
90
        return $this->output;
91
    }
92
93
    protected function getErrorStream()
94
    {
95
        if (!isset($this->error)) {
96
            $output = $this->getOutputStream();
97
            if ($output instanceof ConsoleOutputInterface) {
98
                $output = $output->getErrorOutput();
99
            }
100
            $this->error = $output;
101
        }
102
        return $this->error;
103
    }
104
105
    public function setOutputStream($output)
106
    {
107
        $this->output = $output;
108
        $this->outputStreamWrapper = null;
109
    }
110
111
    public function setErrorStream($error)
112
    {
113
        $this->error = $error;
114
        $this->errorStreamWrapper = null;
115
    }
116
117
    protected function getOutputStreamWrapper()
118
    {
119
        if (!isset($this->outputStreamWrapper)) {
120
            $this->outputStreamWrapper = $this->getLogOutputStyler()->createOutputWrapper($this->getOutputStream());
121
        }
122
        return $this->outputStreamWrapper;
123
    }
124
125
    protected function getErrorStreamWrapper()
126
    {
127
        if (!isset($this->errorStreamWrapper)) {
128
            $this->errorStreamWrapper = $this->getLogOutputStyler()->createOutputWrapper($this->getErrorStream());
129
        }
130
        return $this->errorStreamWrapper;
131
    }
132
133
    protected function getOutputStreamForLogLevel($level)
134
    {
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();
140
        }
141
        return $this->getErrorStreamWrapper();
142
    }
143
144
    /**
145
     * {@inheritdoc}
146
     */
147
    public function log($level, $message, array $context = array())
148
    {
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'];
157
        }
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));
162
        }
163
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);
168
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);
172
        }
173
    }
174
175
    /**
176
     * Interpolate and style the message, and then send it to the log.
177
     */
178
    protected function doLog($outputStreamWrapper, $level, $message, $context)
179
    {
180
        $formatFunction = 'log';
181
        if (array_key_exists($level, $this->formatFunctionMap)) {
182
            $formatFunction = $this->formatFunctionMap[$level];
183
        }
184
        $interpolated = $this->interpolate(
185
            $message,
186
            $this->getLogOutputStyler()->style($context)
187
        );
188
        $this->getLogOutputStyler()->$formatFunction(
189
            $outputStreamWrapper,
190
            $level,
191
            $interpolated,
192
            $context
193
        );
194
    }
195
196
    public function success($message, array $context = array())
197
    {
198
        $this->log(ConsoleLogLevel::SUCCESS, $message, $context);
199
    }
200
201
    // The functions below could be eliminated if made `protected` intead
202
    // of `private` in ConsoleLogger
203
204
    const INFO = 'info';
205
    const ERROR = 'error';
206
207
    /**
208
     * @var OutputInterface
209
     */
210
    //private $output;
211
    /**
212
     * @var array
213
     */
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,
224
    ];
225
226
    /**
227
     * @var array
228
     *
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."
232
     *
233
     */
234
    private $formatLevelMap = [
235
        LogLevel::EMERGENCY => self::ERROR,
236
        LogLevel::ALERT => self::ERROR,
237
        LogLevel::CRITICAL => self::ERROR,
238
        LogLevel::ERROR => self::ERROR,
239
        LogLevel::WARNING => self::ERROR,
240
        LogLevel::NOTICE => self::ERROR,
241
        LogLevel::INFO => self::ERROR,
242
        LogLevel::DEBUG => self::ERROR,
243
        ConsoleLogLevel::SUCCESS => self::ERROR,
244
    ];
245
246
    /**
247
     * Interpolates context values into the message placeholders.
248
     *
249
     * @author PHP Framework Interoperability Group
250
     *
251
     * @param string $message
252
     * @param array  $context
253
     *
254
     * @return string
255
     */
256
    private function interpolate($message, array $context)
257
    {
258
        // build a replacement array with braces around the context keys
259
        $replace = array();
260
        foreach ($context as $key => $val) {
261
            if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
262
                $replace[sprintf('{%s}', $key)] = $val;
263
            }
264
        }
265
266
        // interpolate replacement values into the message and return
267
        return strtr($message, $replace);
268
    }
269
}
270