Logger::openResource()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 0
cts 0
cp 0
rs 9.6666
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 0
crap 6
1
<?php declare(strict_types=1);
2
3
namespace Terah\ColourLog;
4
5
use Psr\Log\AbstractLogger;
6
use Psr\Log\LoggerInterface;
7
use Psr\Log\LogLevel;
8
9
class Logger extends AbstractLogger
10
{
11
    const BLACK         = 'black';
12
    const DARK_GRAY     = 'dark_gray';
13
    const BLUE          = 'blue';
14
    const LIGHT_BLUE    = 'light_blue';
15
    const GREEN         = 'green';
16
    const LIGHT_GREEN   = 'light_green';
17
    const CYAN          = 'cyan';
18
    const LIGHT_CYAN    = 'light_cyan';
19
    const RED           = 'red';
20
    const LIGHT_RED     = 'light_red';
21
    const PURPLE        = 'purple';
22
    const LIGHT_PURPLE  = 'light_purple';
23
    const BROWN         = 'brown';
24
    const YELLOW        = 'yellow';
25
    const MAGENTA       = 'magenta';
26
    const LIGHT_GRAY    = 'light_gray';
27
    const WHITE         = 'white';
28
    const DEFAULT       = 'default';
29
    const BOLD          = 'bold';
30
31
    /**  @var resource $resource The file handle */
32
    protected $resource         = null;
33
34
    /** @var string $level */
35
    protected $level            = LogLevel::INFO;
36
37
    /** @var bool $closeLocally */
38
    protected $closeLocally     = false;
39
40
    /** @var bool */
41
    protected $addDate          = true;
42
43
    /** @var string  */
44
    protected $separator        = ' | ';
45
46
    /** @var \Closure */
47
    protected $formatter        = null;
48
49
    /** @var string  */
50
    protected $lastLogEntry     = '';
51
52
    /** @var bool|null  */
53
    protected $gzipFile         = null;
54
55
    /** @var bool  */
56
    protected $useLocking       = false;
57
58
    /**
59
     * @var array $logLevels List of supported levels
60
     */
61
    static protected $logLevels       = [
62
        LogLevel::EMERGENCY => [1, self::WHITE,       self::RED,      self::DEFAULT,  'EMERG'],
63
        LogLevel::ALERT     => [2, self::WHITE,       self::YELLOW,   self::DEFAULT,  'ALERT'],
64
        LogLevel::CRITICAL  => [3, self::RED,         self::DEFAULT,  self::BOLD ,    'CRIT'],
65
        LogLevel::ERROR     => [4, self::RED,         self::DEFAULT,  self::DEFAULT,  'ERROR'],
66
        LogLevel::WARNING   => [5, self::YELLOW,      self::DEFAULT,  self::DEFAULT,  'WARN'],
67
        LogLevel::NOTICE    => [6, self::CYAN,        self::DEFAULT,  self::DEFAULT,  'NOTE'],
68
        LogLevel::INFO      => [7, self::GREEN,       self::DEFAULT,  self::DEFAULT,  'INFO'],
69
        LogLevel::DEBUG     => [8, self::LIGHT_GRAY,  self::DEFAULT,  self::DEFAULT,  'DEBUG'],
70
    ];
71
72
    /**
73
     * @var array
74
     */
75
    static protected $colours   = [
76
        'fore' => [
77
            self::BLACK         => '0;30',
78
            self::DARK_GRAY     => '1;30',
79
            self::BLUE          => '0;34',
80
            self::LIGHT_BLUE    => '1;34',
81
            self::GREEN         => '0;32',
82
            self::LIGHT_GREEN   => '1;32',
83
            self::CYAN          => '0;36',
84
            self::LIGHT_CYAN    => '1;36',
85
            self::RED           => '0;31',
86
            self::LIGHT_RED     => '1;31',
87
            self::PURPLE        => '0;35',
88
            self::LIGHT_PURPLE  => '1;35',
89
            self::BROWN         => '0;33',
90
            self::YELLOW        => '1;33',
91
            self::MAGENTA       => '0;35',
92
            self::LIGHT_GRAY    => '0;37',
93
            self::WHITE         => '1;37',
94
        ],
95 33
        'back'  => [
96
            self::DEFAULT       => '49',
97 33
            self::BLACK         => '40',
98 33
            self::RED           => '41',
99 33
            self::GREEN         => '42',
100 33
            self::YELLOW        => '43',
101 33
            self::BLUE          => '44',
102 33
            self::MAGENTA       => '45',
103
            self::CYAN          => '46',
104
            self::LIGHT_GRAY    => '47',
105
        ],
106
        self::BOLD => [],
107
    ];
108
109
    /**
110
     * @param mixed  $resource
111
     * @param string $level
112
     * @param bool   $useLocking
113
     * @param bool   $gzipFile
114
     * @param bool   $addDate
115
     */
116
    public function __construct($resource, string $level=LogLevel::INFO, bool $useLocking=false, bool $gzipFile=false, bool $addDate=true)
117
    {
118
        $this->resource     = $resource;
119
        $this->setLogLevel($level);
120
        $this->useLocking   = $useLocking;
121 27
        $this->gzipFile     = $gzipFile;
122
        $this->addDate      = $addDate;
123
    }
124 27
125 27
    /**
126
     * @param $resource
127 27
     * @return LoggerInterface
128 18
     */
129 24
    public function setLogFile($resource) : LoggerInterface
130 16
    {
131
        $this->resource     = $resource;
132 27
133 18
        return $this;
134 24
    }
135 16
136
    /**
137 27
     * @param string $string
138 27
     * @param string $foregroundColor
139
     * @param string $backgroundColor
140
     * @param bool $bold
141
     * @return string
142
     */
143
    public static function addColour(string $string, string $foregroundColor='', string $backgroundColor='', bool $bold=false) : string
144
    {
145
        // todo: support bold
146
        unset($bold);
147
        $coloredString = '';
148 27
        // Check if given foreground color found
149
        if ( isset(static::$colours['fore'][$foregroundColor]) )
150 27
        {
151
            $coloredString .= "\033[" . static::$colours['fore'][$foregroundColor] . "m";
152
        }
153
        // Check if given background color found
154
        if ( isset(static::$colours['back'][$backgroundColor]) )
155
        {
156
            $coloredString .= "\033[" . static::$colours['back'][$backgroundColor] . "m";
157 33
        }
158
        // Add string and end coloring
159 33
        $coloredString .=  $string . "\033[0m";
160 22
161
        return $coloredString;
162
    }
163 33
164 33
    /**
165
     * @param string    $string
166
     * @param string    $foregroundColor
167
     * @param string    $backgroundColor
168
     * @param bool      $bold
169
     * @return string
170
     */
171
    public function colourize(string $string, string $foregroundColor='', string $backgroundColor='', bool $bold=false) : string
172
    {
173
        return static::addColour($string, $foregroundColor, $backgroundColor, $bold);
174
    }
175
176
    /**
177
     * @param string $level Ignore logging attempts at a level less the $level
178
     * @return LoggerInterface
179 3
     */
180
    public function setLogLevel(string $level) : LoggerInterface
181 3
    {
182 3
        if ( ! isset(static::$logLevels[$level]) )
183
        {
184
            throw new \InvalidArgumentException("Log level is invalid");
185
        }
186
        $this->level = static::$logLevels[$level][0];
187
188
        return $this;
189
    }
190
191
    /**
192
     * @return LoggerInterface
193
     */
194
    public function lock() : LoggerInterface
195
    {
196
        $this->useLocking = true;
197
198
        return $this;
199
    }
200
201
    /**
202
     * @return LoggerInterface
203
     */
204
    public function gzipped() : LoggerInterface
205 24
    {
206
        $this->gzipFile = true;
207 24
208 24
        return $this;
209 24
    }
210 16
211 18
    /**
212
     * @param callable $fnFormatter
213 24
     *
214 16
     * @return LoggerInterface
215
     */
216
    public function formatter(callable $fnFormatter) : LoggerInterface
217
    {
218
        $this->formatter = $fnFormatter;
0 ignored issues
show
Documentation Bug introduced by
It seems like $fnFormatter of type callable is incompatible with the declared type object<Closure> of property $formatter.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
219 24
220
        return $this;
221 24
    }
222 24
223 24
    /**
224
     * Log messages to resource
225
     *
226
     * @param mixed          $level    The level of the log message
227
     * @param string|object  $message  If an object is passed it must implement __toString()
228
     * @param array          $context  Placeholders to be substituted in the message
229
     *
230
     * @return LoggerInterface
231
     */
232 24
    public function log($level, $message, array $context=[]) : LoggerInterface
233
    {
234
        $level = isset(static::$logLevels[$level]) ? $level : LogLevel::INFO;
235 24
        list($logLevel, $fore, $back, $style) = static::$logLevels[$level];
236 24
        unset($style);
237 24
        if ( $logLevel > $this->level )
238 24
        {
239 24
            return $this;
240 24
        }
241
        if ( is_callable($this->formatter) )
242
        {
243
            $message = $this->formatter->__invoke(static::$logLevels[$level][4], $message, $context);
244
        }
245
        else
246
        {
247
            $message = $this->formatMessage($level, $message, $context);
0 ignored issues
show
Bug introduced by
It seems like $message can also be of type object; however, Terah\ColourLog\Logger::formatMessage() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
248 27
        }
249
        $this->lastLogEntry = $message;
250 27
        $this->write($this->colourize($message, $fore, $back) . PHP_EOL);
251 27
252 18
        return $this;
253
    }
254
255 27
    /**
256 27
     * @param string $style
257 18
     * @param string $message
258
     * @return string
259
     */
260 27
    public static function style(string $style, string $message) : string
261
    {
262
        $style = isset(static::$logLevels[$style]) ? $style : LogLevel::INFO;
263
        list($logLevel, $fore, $back, $style) = static::$logLevels[$style];
264
        unset($logLevel, $style);
265
266 27
        return static::addColour($message, $fore, $back);
267
    }
268 27
269 18
    /**
270 27
     * @param string $level
271
     * @param string $message
272
     * @param array  $context
273
     * @return string
274
     */
275
    protected function formatMessage(string $level, string $message, array $context=[]) : string
276
    {
277
        # Handle objects implementing __toString
278
        $message            = (string) $message;
279
        $message            .= empty($context) ? '' : PHP_EOL . json_encode($context, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
280
        $data               = $this->addDate ? ['date' => date('Y-m-d H:i:s')] : [];
281
        $data['level']      = strtoupper(str_pad(static::$logLevels[$level][4], 5, ' ', STR_PAD_RIGHT));
282
        $data['message']    = $message;
283
284
        return implode($this->separator, $data);
285
    }
286
287
    /**
288
     * Write the content to the stream
289
     *
290
     * @param  string $content
291
     */
292
    public function write(string $content)
293
    {
294
        $resource = $this->getResource();
295
        if ( $this->useLocking )
296
        {
297
            flock($resource, LOCK_EX);
298
        }
299
        gzwrite($resource, $content);
300
        if ( $this->useLocking )
301
        {
302
            flock($resource, LOCK_UN);
303
        }
304
    }
305
306
    /**
307
     * @return mixed|resource
308
     * @throws \Exception
309
     */
310
    protected function getResource()
311
    {
312
        if ( is_resource($this->resource) )
313
        {
314
            return $this->resource;
315
        }
316
        $fileName               = $this->resource;
317
        $this->closeLocally     = true;
318
        $this->resource         = $this->openResource();
319
        if ( ! is_resource($this->resource) )
320
        {
321
            throw new \Exception("The resource ({$fileName}) could not be opened");
322
        }
323
324
        return $this->resource;
325
    }
326
327
    /**
328
     * @return string
329
     */
330
    public function getLastLogEntry() : string
331
    {
332
        return $this->lastLogEntry;
333
    }
334
335
    /**
336
     * @return resource
337
     */
338
    protected function openResource()
339
    {
340
        if ( $this->gzipFile )
341
        {
342
            return gzopen($this->resource, 'a');
343
        }
344
345
        return fopen($this->resource, 'a');
346
    }
347
348
    public function __destruct()
349
    {
350
        if ($this->closeLocally)
351
        {
352
            gzclose($this->getResource());
353
        }
354
    }
355
}
356
357