Passed
Pull Request — master (#1729)
by
unknown
05:21 queued 02:30
created

LogManager::resolve()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

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

217
            $handlers = \array_merge($handlers, $this->channel($channel)->/** @scrutinizer ignore-call */ getHandlers());
Loading history...
218
        }
219
220 2
        if ($config['ignore_exceptions'] ?? false) {
221
            $handlers = [new WhatFailureGroupHandler($handlers)];
222
        }
223
224 2
        return new Monolog($this->parseChannel($config), $handlers);
225
    }
226
227
    /**
228
     * Create an instance of the single file log driver.
229
     *
230
     * @param array $config
231
     *
232
     * @return \Psr\Log\LoggerInterface
233
     *
234
     * @throws \Exception
235
     */
236 2
    protected function createSingleDriver(array $config)
237
    {
238 2
        return new Monolog($this->parseChannel($config), [
239 2
            $this->prepareHandler(new StreamHandler(
240 2
                $config['path'],
241 1
                $this->level($config),
242 1
                $config['bubble'] ?? true,
243 1
                $config['permission'] ?? null,
244 1
                $config['locking'] ?? false
245
            ), $config),
246
        ]);
247
    }
248
249
    /**
250
     * Create an instance of the daily file log driver.
251
     *
252
     * @param array $config
253
     *
254
     * @return \Psr\Log\LoggerInterface
255
     */
256 1
    protected function createDailyDriver(array $config)
257
    {
258 1
        return new Monolog($this->parseChannel($config), [
259 1
            $this->prepareHandler(new RotatingFileHandler(
260 1
                $config['path'],
261 1
                $config['days'] ?? 7,
262 1
                $this->level($config),
263 1
                $config['bubble'] ?? true,
264 1
                $config['permission'] ?? null,
265 1
                $config['locking'] ?? false
266
            ), $config),
267
        ]);
268
    }
269
270
    /**
271
     * Create an instance of the Slack log driver.
272
     *
273
     * @param array $config
274
     *
275
     * @return \Psr\Log\LoggerInterface
276
     */
277 1
    protected function createSlackDriver(array $config)
278
    {
279 1
        return new Monolog($this->parseChannel($config), [
280 1
            $this->prepareHandler(new SlackWebhookHandler(
281 1
                $config['url'],
282 1
                $config['channel'] ?? null,
283 1
                $config['username'] ?? 'EasyWeChat',
284 1
                $config['attachment'] ?? true,
285 1
                $config['emoji'] ?? ':boom:',
286 1
                $config['short'] ?? false,
287 1
                $config['context'] ?? true,
288 1
                $this->level($config),
289 1
                $config['bubble'] ?? true,
290 1
                $config['exclude_fields'] ?? []
291
            ), $config),
292
        ]);
293
    }
294
295
    /**
296
     * Create an instance of the syslog log driver.
297
     *
298
     * @param array $config
299
     *
300
     * @return \Psr\Log\LoggerInterface
301
     */
302 1
    protected function createSyslogDriver(array $config)
303
    {
304 1
        return new Monolog($this->parseChannel($config), [
305 1
            $this->prepareHandler(new SyslogHandler(
306 1
                'EasyWeChat',
307 1
                $config['facility'] ?? LOG_USER,
308 1
                $this->level($config)
309
            ), $config),
310
        ]);
311
    }
312
313
    /**
314
     * Create an instance of the "error log" log driver.
315
     *
316
     * @param array $config
317
     *
318
     * @return \Psr\Log\LoggerInterface
319
     */
320 2
    protected function createErrorlogDriver(array $config)
321
    {
322 2
        return new Monolog($this->parseChannel($config), [
323 2
            $this->prepareHandler(
324 2
                new ErrorLogHandler(
325 2
                    $config['type'] ?? ErrorLogHandler::OPERATING_SYSTEM,
326 2
                    $this->level($config)
327
                )
328
            ),
329
        ]);
330
    }
331
332
    /**
333
     * Prepare the handlers for usage by Monolog.
334
     *
335
     * @param array $handlers
336
     *
337
     * @return array
338
     */
339 2
    protected function prepareHandlers(array $handlers)
340
    {
341 2
        foreach ($handlers as $key => $handler) {
342 2
            $handlers[$key] = $this->prepareHandler($handler);
343
        }
344
345 2
        return $handlers;
346
    }
347
348
    /**
349
     * Prepare the handler for usage by Monolog.
350
     *
351
     * @param \Monolog\Handler\HandlerInterface $handler
352
     *
353
     * @return \Monolog\Handler\HandlerInterface
354
     */
355 3
    protected function prepareHandler(HandlerInterface $handler, array $config = [])
356
    {
357 3
        if (!isset($config['formatter'])) {
358 3
            $handler->setFormatter($this->formatter());
0 ignored issues
show
Bug introduced by
The method setFormatter() does not exist on Monolog\Handler\HandlerInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Monolog\Handler\Handler or Monolog\Handler\AbstractHandler or Monolog\Handler\NullHandler or Monolog\Handler\NoopHandler. Are you sure you never get one of those? ( Ignorable by Annotation )

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

358
            $handler->/** @scrutinizer ignore-call */ 
359
                      setFormatter($this->formatter());
Loading history...
359
        }
360
361 3
        return $handler;
362
    }
363
364
    /**
365
     * Get a Monolog formatter instance.
366
     *
367
     * @return \Monolog\Formatter\FormatterInterface
368
     */
369 3
    protected function formatter()
370
    {
371 3
        $formatter = new LineFormatter(null, null, true, true);
372 3
        $formatter->includeStacktraces();
373
374 3
        return $formatter;
375
    }
376
377
    /**
378
     * Extract the log channel from the given configuration.
379
     *
380
     * @param array $config
381
     *
382
     * @return string
383
     */
384 2
    protected function parseChannel(array $config)
385
    {
386 2
        return $config['name'] ?? 'EasyWeChat';
387
    }
388
389
    /**
390
     * Parse the string level into a Monolog constant.
391
     *
392
     * @param array $config
393
     *
394
     * @return int
395
     *
396
     * @throws InvalidArgumentException
397
     */
398 4
    protected function level(array $config)
399
    {
400 4
        $level = $config['level'] ?? 'debug';
401
402 4
        if (isset($this->levels[$level])) {
403 3
            return $this->levels[$level];
404
        }
405
406 1
        throw new InvalidArgumentException('Invalid log level.');
407
    }
408
409
    /**
410
     * Get the default log driver name.
411
     *
412
     * @return string
413
     */
414 3
    public function getDefaultDriver()
415
    {
416 3
        return $this->app['config']['log.default'];
417
    }
418
419
    /**
420
     * Set the default log driver name.
421
     *
422
     * @param string $name
423
     */
424 1
    public function setDefaultDriver($name)
425
    {
426 1
        $this->app['config']['log.default'] = $name;
427 1
    }
428
429
    /**
430
     * Register a custom driver creator Closure.
431
     *
432
     * @param string   $driver
433
     * @param \Closure $callback
434
     *
435
     * @return $this
436
     */
437 1
    public function extend($driver, \Closure $callback)
438
    {
439 1
        $this->customCreators[$driver] = $callback->bindTo($this, $this);
440
441 1
        return $this;
442
    }
443
444
    /**
445
     * System is unusable.
446
     *
447
     * @param string $message
448
     * @param array  $context
449
     *
450
     * @return mixed
451
     *
452
     * @throws \Exception
453
     */
454 1
    public function emergency($message, array $context = [])
455
    {
456 1
        return $this->driver()->emergency($message, $context);
0 ignored issues
show
Bug introduced by
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...
457
    }
458
459
    /**
460
     * Action must be taken immediately.
461
     *
462
     * Example: Entire website down, database unavailable, etc. This should
463
     * trigger the SMS alerts and wake you up.
464
     *
465
     * @param string $message
466
     * @param array  $context
467
     *
468
     * @return mixed
469
     *
470
     * @throws \Exception
471
     */
472 1
    public function alert($message, array $context = [])
473
    {
474 1
        return $this->driver()->alert($message, $context);
0 ignored issues
show
Bug introduced by
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...
475
    }
476
477
    /**
478
     * Critical conditions.
479
     *
480
     * Example: Application component unavailable, unexpected exception.
481
     *
482
     * @param string $message
483
     * @param array  $context
484
     *
485
     * @return mixed
486
     *
487
     * @throws \Exception
488
     */
489 1
    public function critical($message, array $context = [])
490
    {
491 1
        return $this->driver()->critical($message, $context);
0 ignored issues
show
Bug introduced by
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...
492
    }
493
494
    /**
495
     * Runtime errors that do not require immediate action but should typically
496
     * be logged and monitored.
497
     *
498
     * @param string $message
499
     * @param array  $context
500
     *
501
     * @return mixed
502
     *
503
     * @throws \Exception
504
     */
505 1
    public function error($message, array $context = [])
506
    {
507 1
        return $this->driver()->error($message, $context);
0 ignored issues
show
Bug introduced by
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...
508
    }
509
510
    /**
511
     * Exceptional occurrences that are not errors.
512
     *
513
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
514
     * that are not necessarily wrong.
515
     *
516
     * @param string $message
517
     * @param array  $context
518
     *
519
     * @return mixed
520
     *
521
     * @throws \Exception
522
     */
523 1
    public function warning($message, array $context = [])
524
    {
525 1
        return $this->driver()->warning($message, $context);
0 ignored issues
show
Bug introduced by
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...
526
    }
527
528
    /**
529
     * Normal but significant events.
530
     *
531
     * @param string $message
532
     * @param array  $context
533
     *
534
     * @return mixed
535
     *
536
     * @throws \Exception
537
     */
538 1
    public function notice($message, array $context = [])
539
    {
540 1
        return $this->driver()->notice($message, $context);
0 ignored issues
show
Bug introduced by
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...
541
    }
542
543
    /**
544
     * Interesting events.
545
     *
546
     * Example: User logs in, SQL logs.
547
     *
548
     * @param string $message
549
     * @param array  $context
550
     *
551
     * @return mixed
552
     *
553
     * @throws \Exception
554
     */
555 1
    public function info($message, array $context = [])
556
    {
557 1
        return $this->driver()->info($message, $context);
0 ignored issues
show
Bug introduced by
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...
558
    }
559
560
    /**
561
     * Detailed debug information.
562
     *
563
     * @param string $message
564
     * @param array  $context
565
     *
566
     * @return mixed
567
     *
568
     * @throws \Exception
569
     */
570 2
    public function debug($message, array $context = [])
571
    {
572 2
        return $this->driver()->debug($message, $context);
0 ignored issues
show
Bug introduced by
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...
573
    }
574
575
    /**
576
     * Logs with an arbitrary level.
577
     *
578
     * @param mixed  $level
579
     * @param string $message
580
     * @param array  $context
581
     *
582
     * @return mixed
583
     *
584
     * @throws \Exception
585
     */
586 1
    public function log($level, $message, array $context = [])
587
    {
588 1
        return $this->driver()->log($level, $message, $context);
0 ignored issues
show
Bug introduced by
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...
589
    }
590
591
    /**
592
     * Dynamically call the default driver instance.
593
     *
594
     * @param string $method
595
     * @param array  $parameters
596
     *
597
     * @return mixed
598
     *
599
     * @throws \Exception
600
     */
601 1
    public function __call($method, $parameters)
602
    {
603 1
        return $this->driver()->$method(...$parameters);
604
    }
605
}
606