Completed
Push — master ( c98062...d39dc8 )
by Terry
04:22
created

Logger::log()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 7
Bugs 0 Features 2
Metric Value
c 7
b 0
f 2
dl 0
loc 20
rs 9.2
cc 4
eloc 12
nc 6
nop 3
1
<?php
2
3
namespace Terah\ColourLog;
4
5
use Psr\Log\AbstractLogger;
6
use Psr\Log\LogLevel;
7
8
class Logger extends AbstractLogger
9
{
10
    /**  @var resource $resource The file handle */
11
    protected $resource         = null;
12
13
    /** @var string $level */
14
    protected $level            = LogLevel::INFO;
15
16
    /** @var bool $closeLocally */
17
    protected $closeLocally     = false;
18
19
    protected $addDate          = true;
20
21
    protected $separator        = ' | ';
22
23
    protected $formatter        = null;
24
25
    protected $lastLogEntry     = '';
26
27
    protected $gzipFile         = null;
28
29
    protected $useLocking       = false;
30
31
    /**
32
     * @var array $logLevels List of supported levels
33
     */
34
    protected $logLevels       = [
35
        LogLevel::EMERGENCY => [1, 'white', 'red', 'default', 'EMERG'],
36
        LogLevel::ALERT     => [2, 'white', 'yellow', 'default', 'ALERT'],
37
        LogLevel::CRITICAL  => [3,'red', 'default', 'bold' , 'CRIT'],
38
        LogLevel::ERROR     => [4,'red', 'default', 'default', 'ERROR'],
39
        LogLevel::WARNING   => [5, 'yellow', 'default', 'default', 'WARN'],
40
        LogLevel::NOTICE    => [6, 'cyan', 'default', 'default', 'NOTE'],
41
        LogLevel::INFO      => [7, 'green', 'default', 'default', 'INFO'],
42
        LogLevel::DEBUG     => [8, 'dark_gray', 'default', 'default', 'DEBUG'],
43
    ];
44
45
    /**
46
     * @var array
47
     */
48
    static protected $colours   = [
49
        'fore' => [
50
            'black'         => '0;30',
51
            'dark_gray'     => '1;30',
52
            'blue'          => '0;34',
53
            'light_blue'    => '1;34',
54
            'green'         => '0;32',
55
            'light_green'   => '1;32',
56
            'cyan'          => '0;36',
57
            'light_cyan'    => '1;36',
58
            'red'           => '0;31',
59
            'light_red'     => '1;31',
60
            'purple'        => '0;35',
61
            'light_purple'  => '1;35',
62
            'brown'         => '0;33',
63
            'yellow'        => '1;33',
64
            'magenta'       => '0;35',
65
            'light_gray'    => '0;37',
66
            'white'         => '1;37',
67
        ],
68
        'back'  => [
69
            'default'       => '49',
70
            'black'         => '40',
71
            'red'           => '41',
72
            'green'         => '42',
73
            'yellow'        => '43',
74
            'blue'          => '44',
75
            'magenta'       => '45',
76
            'cyan'          => '46',
77
            'light_gray'    => '47',
78
        ],
79
        'bold' => [],
80
    ];
81
82
    /**
83
     * @param mixed  $resource
84
     * @param string $level
85
     * @param bool   $useLocking
86
     * @param bool   $gzipFile
87
     * @param bool   $addDate
88
     */
89
    public function __construct($resource, $level=LogLevel::INFO, $useLocking=false, $gzipFile=false, $addDate=true)
90
    {
91
        $this->resource     = $resource;
92
        $this->setLogLevel($level);
93
        $this->useLocking   = $useLocking;
94
        $this->gzipFile     = $gzipFile;
95
        $this->addDate      = $addDate;
96
    }
97
98
    /**
99
     * @param $resource
100
     * @return $this
101
     */
102
    public function setLogFile($resource)
103
    {
104
        $this->resource     = $resource;
105
        return $this;
106
    }
107
108
    /**
109
     * @param      $string
110
     * @param null $foregroundColor
111
     * @param null $backgroundColor
112
     * @param bool $bold
113
     * @return string
114
     */
115
    public static function addColour($string, $foregroundColor=null, $backgroundColor=null, $bold=false)
0 ignored issues
show
Unused Code introduced by
The parameter $bold is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
116
    {
117
        $coloredString = '';
118
        // Check if given foreground color found
119
        if ( isset(static::$colours['fore'][$foregroundColor]) )
120
        {
121
            $coloredString .= "\033[" . static::$colours['fore'][$foregroundColor] . "m";
122
        }
123
        // Check if given background color found
124
        if ( isset(static::$colours['back'][$backgroundColor]) )
125
        {
126
            $coloredString .= "\033[" . static::$colours['back'][$backgroundColor] . "m";
127
        }
128
        // Add string and end coloring
129
        $coloredString .=  $string . "\033[0m";
130
        return $coloredString;
131
    }
132
133
    /**
134
     * @param            $string
135
     * @param null       $foregroundColor
136
     * @param null       $backgroundColor
137
     * @param bool|false $bold
138
     * @return string
139
     */
140
    public function colourize($string, $foregroundColor = null, $backgroundColor = null, $bold=false)
141
    {
142
        return static::addColour($string, $foregroundColor, $backgroundColor, $bold);
143
    }
144
145
    /**
146
     * @param string $level Ignore logging attempts at a level less the $level
147
     * @return static
148
     */
149
    public function setLogLevel($level)
150
    {
151
        if ( ! isset($this->logLevels[$level]) )
152
        {
153
            throw new \InvalidArgumentException("Log level is invalid");
154
        }
155
        $this->level = $this->logLevels[$level][0];
156
        return $this;
157
    }
158
159
    /**
160
     * @return $this
161
     */
162
    public function lock()
163
    {
164
        $this->useLocking = true;
165
        return $this;
166
    }
167
168
    /**
169
     * @return $this
170
     */
171
    public function gzipped()
172
    {
173
        $this->gzipFile = true;
174
        return $this;
175
    }
176
177
    /**
178
     * @param callable $fnFormatter
179
     *
180
     * @return $this
181
     */
182
    public function formatter(callable $fnFormatter)
183
    {
184
        $this->formatter = $fnFormatter;
185
        return $this;
186
    }
187
188
    /**
189
     * Log messages to resource
190
     *
191
     * @param mixed          $level    The level of the log message
192
     * @param string|object  $message  If an object is passed it must implement __toString()
193
     * @param array          $context  Placeholders to be substituted in the message
194
     *
195
     * @return static
196
     */
197
    public function log($level, $message, array $context = [])
198
    {
199
        $level = isset($this->logLevels[$level]) ? $level : LogLevel::INFO;
200
        list($logLevel, $fore, $back, $style) = $this->logLevels[$level];
201
        if ( $logLevel > $this->level )
202
        {
203
            return $this;
204
        }
205
        if ( is_callable($this->formatter) )
206
        {
207
            $message = $this->formatter->__invoke($this->logLevels[$level][4], $message, $context);
0 ignored issues
show
Bug introduced by
The method __invoke cannot be called on $this->formatter (of type callable).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
208
        }
209
        else
210
        {
211
            $message = $this->formatMessage($level, $message, $context);
212
        }
213
        $this->lastLogEntry = $message;
214
        $this->write($this->colourize($message, $fore, $back, $style) . PHP_EOL);
215
        return $this;
216
    }
217
218
    /**
219
     * @param       $level
220
     * @param       $message
221
     * @param array $context
222
     * @return string
223
     */
224
    protected function formatMessage($level, $message, array $context = [])
225
    {
226
        # Handle objects implementing __toString
227
        $message            = (string) $message;
228
        $message            .= empty($context) ? '' : PHP_EOL . json_encode($context, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
229
        $data               = $this->addDate ? ['date' => date('Y-m-d H:i:s')] : [];
230
        $data['level']      = strtoupper(str_pad($this->logLevels[$level][4], 5, ' ', STR_PAD_RIGHT));
231
        $data['message']    = $message;
232
        return implode($this->separator, $data);
233
    }
234
235
    /**
236
     * Write the content to the stream
237
     *
238
     * @param  string $content
239
     */
240
    public function write($content)
241
    {
242
        $resource = $this->getResource();
243
        if ( $this->useLocking )
244
        {
245
            flock($resource, LOCK_EX);
246
        }
247
        gzwrite($resource, $content);
248
        if ( $this->useLocking )
249
        {
250
            flock($resource, LOCK_UN);
251
        }
252
    }
253
254
    /**
255
     * @return mixed|resource
256
     * @throws \Exception
257
     */
258
    protected function getResource()
259
    {
260
        if ( is_resource($this->resource) )
261
        {
262
            return $this->resource;
263
        }
264
        $fileName = $this->resource;
265
        $this->closeLocally = true;
266
        if ( ! in_array($fileName, ['php://stdout', 'php://stderr']) && ! is_writable($fileName) )
267
        {
268
            throw new \Exception("The resource [{$fileName}] is not writable");
269
        }
270
        $this->resource = $this->openResource();
271
        if ( ! is_resource($this->resource) )
272
        {
273
            throw new \Exception("The resource ({$fileName}) could not be opened");
274
        }
275
        return $this->resource;
276
    }
277
278
    /**
279
     * @return string
280
     */
281
    public function getLastLogEntry()
282
    {
283
        return $this->lastLogEntry;
284
    }
285
286
    /**
287
     * @return resource
288
     */
289
    protected function openResource()
290
    {
291
        if ( $this->gzipFile )
292
        {
293
            return gzopen($this->resource, 'a');
294
        }
295
        return fopen($this->resource, 'a');
296
    }
297
298
    public function __destruct()
299
    {
300
        if ($this->closeLocally)
301
        {
302
            gzclose($this->getResource());
303
        }
304
    }
305
}
306
307