Issues (31)

src/Kernel/Log/LogManager.php (10 issues)

Labels
1
<?php
2
3
namespace EasyIM\Kernel\Log;
4
5
use EasyIM\Kernel\ServiceContainer;
6
use InvalidArgumentException;
7
use Monolog\Formatter\LineFormatter;
8
use Monolog\Handler\ErrorLogHandler;
9
use Monolog\Handler\FormattableHandlerInterface;
10
use Monolog\Handler\HandlerInterface;
11
use Monolog\Handler\RotatingFileHandler;
12
use Monolog\Handler\SlackWebhookHandler;
13
use Monolog\Handler\StreamHandler;
14
use Monolog\Handler\SyslogHandler;
15
use Monolog\Handler\WhatFailureGroupHandler;
16
use Monolog\Logger as Monolog;
17
use Psr\Log\LoggerInterface;
18
19
/**
20
 * Class LogManager.
21
 */
22
class LogManager implements LoggerInterface
23
{
24
    /**
25
     * @var \EasyIM\Kernel\ServiceContainer
26
     */
27
    protected $app;
28
29
    /**
30
     * The array of resolved channels.
31
     *
32
     * @var array
33
     */
34
    protected $channels = [];
35
36
    /**
37
     * The registered custom driver creators.
38
     *
39
     * @var array
40
     */
41
    protected $customCreators = [];
42
43
    /**
44
     * The Log levels.
45
     *
46
     * @var array
47
     */
48
    protected $levels = [
49
        'debug' => Monolog::DEBUG,
50
        'info' => Monolog::INFO,
51
        'notice' => Monolog::NOTICE,
52
        'warning' => Monolog::WARNING,
53
        'error' => Monolog::ERROR,
54
        'critical' => Monolog::CRITICAL,
55
        'alert' => Monolog::ALERT,
56
        'emergency' => Monolog::EMERGENCY,
57
    ];
58
59
    /**
60
     * LogManager constructor.
61
     *
62
     * @param \EasyIM\Kernel\ServiceContainer $app
63
     */
64 9
    public function __construct(ServiceContainer $app)
65
    {
66 9
        $this->app = $app;
67 9
    }
68
69
    /**
70
     * Create a new, on-demand aggregate logger instance.
71
     *
72
     * @param array       $channels
73
     * @param string|null $channel
74
     *
75
     * @return \Psr\Log\LoggerInterface
76
     *
77
     * @throws \Exception
78
     */
79 1
    public function stack(array $channels, $channel = null)
80
    {
81 1
        return $this->createStackDriver(compact('channels', 'channel'));
82
    }
83
84
    /**
85
     * Get a log channel instance.
86
     *
87
     * @param string|null $channel
88
     *
89
     * @return mixed
90
     *
91
     * @throws \Exception
92
     */
93 2
    public function channel($channel = null)
94
    {
95 2
        return $this->driver($channel);
96
    }
97
98
    /**
99
     * Get a log driver instance.
100
     *
101
     * @param string|null $driver
102
     *
103
     * @return mixed
104
     *
105
     * @throws \Exception
106
     */
107 7
    public function driver($driver = null)
108
    {
109 7
        return $this->get($driver ?? $this->getDefaultDriver());
110
    }
111
112
    /**
113
     * Attempt to get the log from the local cache.
114
     *
115
     * @param string $name
116
     *
117
     * @return \Psr\Log\LoggerInterface
118
     *
119
     * @throws \Exception
120
     */
121 7
    protected function get($name)
122
    {
123
        try {
124 7
            return $this->channels[$name] ?? ($this->channels[$name] = $this->resolve($name));
125 4
        } catch (\Throwable $e) {
126 4
            $logger = $this->createEmergencyLogger();
127
128 4
            $logger->emergency('Unable to create configured logger. Using emergency logger.', [
129 4
                'exception' => $e,
130
            ]);
131
132 4
            return $logger;
133
        }
134
    }
135
136
    /**
137
     * Resolve the given log instance by name.
138
     *
139
     * @param string $name
140
     *
141
     * @return \Psr\Log\LoggerInterface
142
     *
143
     * @throws InvalidArgumentException
144
     */
145 7
    protected function resolve($name)
146
    {
147 7
        $config = $this->app['config']->get(\sprintf('log.channels.%s', $name));
148
149 7
        if (is_null($config)) {
150 2
            throw new InvalidArgumentException(\sprintf('Log [%s] is not defined.', $name));
151
        }
152
153 5
        if (isset($this->customCreators[$config['driver']])) {
154 1
            return $this->callCustomCreator($config);
155
        }
156
157 4
        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';
158
159 4
        if (method_exists($this, $driverMethod)) {
160 3
            return $this->{$driverMethod}($config);
161
        }
162
163 1
        throw new InvalidArgumentException(\sprintf('Driver [%s] is not supported.', $config['driver']));
164
    }
165
166
    /**
167
     * Create an emergency log handler to avoid white screens of death.
168
     *
169
     * @return \Monolog\Logger
170
     *
171
     * @throws \Exception
172
     */
173 2
    protected function createEmergencyLogger()
174
    {
175 2
        return new Monolog('EasyIM', $this->prepareHandlers([new StreamHandler(
176 2
            \sys_get_temp_dir().'/EasyIM/EasyIM.log',
177 2
            $this->level(['level' => 'debug'])
178
        )]));
179
    }
180
181
    /**
182
     * Call a custom driver creator.
183
     *
184
     * @param array $config
185
     *
186
     * @return mixed
187
     */
188 1
    protected function callCustomCreator(array $config)
189
    {
190 1
        return $this->customCreators[$config['driver']]($this->app, $config);
191
    }
192
193
    /**
194
     * Create an aggregate log driver instance.
195
     *
196
     * @param array $config
197
     *
198
     * @return \Monolog\Logger
199
     *
200
     * @throws \Exception
201
     */
202 2
    protected function createStackDriver(array $config)
203
    {
204 2
        $handlers = [];
205
206 2
        foreach ($config['channels'] ?? [] as $channel) {
207 2
            $handlers = \array_merge($handlers, $this->channel($channel)->getHandlers());
0 ignored issues
show
The method getHandlers() does not exist on Psr\Log\LoggerInterface. It seems like you code against a sub-type of Psr\Log\LoggerInterface such as EasyIM\Kernel\Log\LogManager or Monolog\Logger or Psr\Log\Test\TestLogger. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

207
            $handlers = \array_merge($handlers, $this->channel($channel)->/** @scrutinizer ignore-call */ getHandlers());
Loading history...
208
        }
209
210 2
        if ($config['ignore_exceptions'] ?? false) {
211
            $handlers = [new WhatFailureGroupHandler($handlers)];
212
        }
213
214 2
        return new Monolog($this->parseChannel($config), $handlers);
215
    }
216
217
    /**
218
     * Create an instance of the single file log driver.
219
     *
220
     * @param array $config
221
     *
222
     * @return \Psr\Log\LoggerInterface
223
     *
224
     * @throws \Exception
225
     */
226 2
    protected function createSingleDriver(array $config)
227
    {
228 2
        return new Monolog($this->parseChannel($config), [
229 2
            $this->prepareHandler(new StreamHandler(
230 2
                $config['path'],
231 1
                $this->level($config),
232 1
                $config['bubble'] ?? true,
233 1
                $config['permission'] ?? null,
234 1
                $config['locking'] ?? false
235 1
            ), $config),
236
        ]);
237
    }
238
239
    /**
240
     * Create an instance of the daily file log driver.
241
     *
242
     * @param array $config
243
     *
244
     * @return \Psr\Log\LoggerInterface
245
     */
246 1
    protected function createDailyDriver(array $config)
247
    {
248 1
        return new Monolog($this->parseChannel($config), [
249 1
            $this->prepareHandler(new RotatingFileHandler(
250 1
                $config['path'],
251 1
                $config['days'] ?? 7,
252 1
                $this->level($config),
253 1
                $config['bubble'] ?? true,
254 1
                $config['permission'] ?? null,
255 1
                $config['locking'] ?? false
256 1
            ), $config),
257
        ]);
258
    }
259
260
    /**
261
     * Create an instance of the Slack log driver.
262
     *
263
     * @param array $config
264
     *
265
     * @return \Psr\Log\LoggerInterface
266
     */
267 1
    protected function createSlackDriver(array $config)
268
    {
269 1
        return new Monolog($this->parseChannel($config), [
270 1
            $this->prepareHandler(new SlackWebhookHandler(
271 1
                $config['url'],
272 1
                $config['channel'] ?? null,
273 1
                $config['username'] ?? 'EasyIM',
274 1
                $config['attachment'] ?? true,
275 1
                $config['emoji'] ?? ':boom:',
276 1
                $config['short'] ?? false,
277 1
                $config['context'] ?? true,
278 1
                $this->level($config),
279 1
                $config['bubble'] ?? true,
280 1
                $config['exclude_fields'] ?? []
281 1
            ), $config),
282
        ]);
283
    }
284
285
    /**
286
     * Create an instance of the syslog log driver.
287
     *
288
     * @param array $config
289
     *
290
     * @return \Psr\Log\LoggerInterface
291
     */
292 1
    protected function createSyslogDriver(array $config)
293
    {
294 1
        return new Monolog($this->parseChannel($config), [
295 1
            $this->prepareHandler(new SyslogHandler(
296 1
                'EasyIM',
297 1
                $config['facility'] ?? LOG_USER,
298 1
                $this->level($config)
299 1
            ), $config),
300
        ]);
301
    }
302
303
    /**
304
     * Create an instance of the "error log" log driver.
305
     *
306
     * @param array $config
307
     *
308
     * @return \Psr\Log\LoggerInterface
309
     */
310 2
    protected function createErrorlogDriver(array $config)
311
    {
312 2
        return new Monolog($this->parseChannel($config), [
313 2
            $this->prepareHandler(
314 2
                new ErrorLogHandler(
315 2
                    $config['type'] ?? ErrorLogHandler::OPERATING_SYSTEM,
316 2
                    $this->level($config)
317
                )
318
            ),
319
        ]);
320
    }
321
322
    /**
323
     * Prepare the handlers for usage by Monolog.
324
     *
325
     * @param array $handlers
326
     *
327
     * @return array
328
     */
329 2
    protected function prepareHandlers(array $handlers)
330
    {
331 2
        foreach ($handlers as $key => $handler) {
332 2
            $handlers[$key] = $this->prepareHandler($handler);
333
        }
334
335 2
        return $handlers;
336
    }
337
338
    /**
339
     * Prepare the handler for usage by Monolog.
340
     *
341
     * @param \Monolog\Handler\HandlerInterface $handler
342
     *
343
     * @return \Monolog\Handler\HandlerInterface
344
     */
345 3
    protected function prepareHandler(HandlerInterface $handler, array $config = [])
346
    {
347 3
        if (!isset($config['formatter'])) {
348 3
            if ($handler instanceof FormattableHandlerInterface) {
349 3
                $handler->setFormatter($this->formatter());
350
            }
351
        }
352
353 3
        return $handler;
354
    }
355
356
    /**
357
     * Get a Monolog formatter instance.
358
     *
359
     * @return \Monolog\Formatter\FormatterInterface
360
     */
361 3
    protected function formatter()
362
    {
363 3
        $formatter = new LineFormatter(null, null, true, true);
364 3
        $formatter->includeStacktraces();
365
366 3
        return $formatter;
367
    }
368
369
    /**
370
     * Extract the log channel from the given configuration.
371
     *
372
     * @param array $config
373
     *
374
     * @return string
375
     */
376 2
    protected function parseChannel(array $config)
377
    {
378 2
        return $config['name'] ?? 'EasyIM';
379
    }
380
381
    /**
382
     * Parse the string level into a Monolog constant.
383
     *
384
     * @param array $config
385
     *
386
     * @return int
387
     *
388
     * @throws InvalidArgumentException
389
     */
390 4
    protected function level(array $config)
391
    {
392 4
        $level = $config['level'] ?? 'debug';
393
394 4
        if (isset($this->levels[$level])) {
395 3
            return $this->levels[$level];
396
        }
397
398 1
        throw new InvalidArgumentException('Invalid log level.');
399
    }
400
401
    /**
402
     * Get the default log driver name.
403
     *
404
     * @return string
405
     */
406 2
    public function getDefaultDriver()
407
    {
408 2
        return $this->app['config']['log.default'];
409
    }
410
411
    /**
412
     * Set the default log driver name.
413
     *
414
     * @param string $name
415
     */
416 1
    public function setDefaultDriver($name)
417
    {
418 1
        $this->app['config']['log.default'] = $name;
419 1
    }
420
421
    /**
422
     * Register a custom driver creator Closure.
423
     *
424
     * @param string   $driver
425
     * @param \Closure $callback
426
     *
427
     * @return $this
428
     */
429 1
    public function extend($driver, \Closure $callback)
430
    {
431 1
        $this->customCreators[$driver] = $callback->bindTo($this, $this);
432
433 1
        return $this;
434
    }
435
436
    /**
437
     * System is unusable.
438
     *
439
     * @param string $message
440
     * @param array  $context
441
     *
442
     * @return mixed
443
     *
444
     * @throws \Exception
445
     */
446
    public function emergency($message, array $context = [])
447
    {
448
        return $this->driver()->emergency($message, $context);
0 ignored issues
show
Are you sure the usage of $this->driver()->emergency($message, $context) targeting Psr\Log\LoggerInterface::emergency() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
449
    }
450
451
    /**
452
     * Action must be taken immediately.
453
     *
454
     * Example: Entire website down, database unavailable, etc. This should
455
     * trigger the SMS alerts and wake you up.
456
     *
457
     * @param string $message
458
     * @param array  $context
459
     *
460
     * @return mixed
461
     *
462
     * @throws \Exception
463
     */
464
    public function alert($message, array $context = [])
465
    {
466
        return $this->driver()->alert($message, $context);
0 ignored issues
show
Are you sure the usage of $this->driver()->alert($message, $context) targeting Psr\Log\LoggerInterface::alert() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
467
    }
468
469
    /**
470
     * Critical conditions.
471
     *
472
     * Example: Application component unavailable, unexpected exception.
473
     *
474
     * @param string $message
475
     * @param array  $context
476
     *
477
     * @return mixed
478
     *
479
     * @throws \Exception
480
     */
481
    public function critical($message, array $context = [])
482
    {
483
        return $this->driver()->critical($message, $context);
0 ignored issues
show
Are you sure the usage of $this->driver()->critical($message, $context) targeting Psr\Log\LoggerInterface::critical() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
484
    }
485
486
    /**
487
     * Runtime errors that do not require immediate action but should typically
488
     * be logged and monitored.
489
     *
490
     * @param string $message
491
     * @param array  $context
492
     *
493
     * @return mixed
494
     *
495
     * @throws \Exception
496
     */
497
    public function error($message, array $context = [])
498
    {
499
        return $this->driver()->error($message, $context);
0 ignored issues
show
Are you sure the usage of $this->driver()->error($message, $context) targeting Psr\Log\LoggerInterface::error() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
500
    }
501
502
    /**
503
     * Exceptional occurrences that are not errors.
504
     *
505
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
506
     * that are not necessarily wrong.
507
     *
508
     * @param string $message
509
     * @param array  $context
510
     *
511
     * @return mixed
512
     *
513
     * @throws \Exception
514
     */
515
    public function warning($message, array $context = [])
516
    {
517
        return $this->driver()->warning($message, $context);
0 ignored issues
show
Are you sure the usage of $this->driver()->warning($message, $context) targeting Psr\Log\LoggerInterface::warning() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
518
    }
519
520
    /**
521
     * Normal but significant events.
522
     *
523
     * @param string $message
524
     * @param array  $context
525
     *
526
     * @return mixed
527
     *
528
     * @throws \Exception
529
     */
530
    public function notice($message, array $context = [])
531
    {
532
        return $this->driver()->notice($message, $context);
0 ignored issues
show
Are you sure the usage of $this->driver()->notice($message, $context) targeting Psr\Log\LoggerInterface::notice() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
533
    }
534
535
    /**
536
     * Interesting events.
537
     *
538
     * Example: User logs in, SQL logs.
539
     *
540
     * @param string $message
541
     * @param array  $context
542
     *
543
     * @return mixed
544
     *
545
     * @throws \Exception
546
     */
547
    public function info($message, array $context = [])
548
    {
549
        return $this->driver()->info($message, $context);
0 ignored issues
show
Are you sure the usage of $this->driver()->info($message, $context) targeting Psr\Log\LoggerInterface::info() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
550
    }
551
552
    /**
553
     * Detailed debug information.
554
     *
555
     * @param string $message
556
     * @param array  $context
557
     *
558
     * @return mixed
559
     *
560
     * @throws \Exception
561
     */
562 1
    public function debug($message, array $context = [])
563
    {
564 1
        return $this->driver()->debug($message, $context);
0 ignored issues
show
Are you sure the usage of $this->driver()->debug($message, $context) targeting Psr\Log\LoggerInterface::debug() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
565
    }
566
567
    /**
568
     * Logs with an arbitrary level.
569
     *
570
     * @param mixed  $level
571
     * @param string $message
572
     * @param array  $context
573
     *
574
     * @return mixed
575
     *
576
     * @throws \Exception
577
     */
578
    public function log($level, $message, array $context = [])
579
    {
580
        return $this->driver()->log($level, $message, $context);
0 ignored issues
show
Are you sure the usage of $this->driver()->log($level, $message, $context) targeting Psr\Log\LoggerInterface::log() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
581
    }
582
583
    /**
584
     * Dynamically call the default driver instance.
585
     *
586
     * @param string $method
587
     * @param array  $parameters
588
     *
589
     * @return mixed
590
     *
591
     * @throws \Exception
592
     */
593 1
    public function __call($method, $parameters)
594
    {
595 1
        return $this->driver()->$method(...$parameters);
596
    }
597
}
598