Passed
Pull Request — master (#1729)
by
unknown
04:06 queued 52s
created

LogManager::emergency()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 3
rs 10
ccs 2
cts 2
cp 1
crap 1
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
        } elseif ($config['formatter'] !== 'default') {
360
            $handler->setFormatter($this->app->make($config['formatter'], $config['formatter_with'] ?? []));
0 ignored issues
show
Bug introduced by
The method make() does not exist on EasyWeChat\Kernel\ServiceContainer. It seems like you code against a sub-type of said class. However, the method does not exist in EasyWeChat\OfficialAccount\Application or EasyWeChat\Tests\Kernel\...ontainerForProviderTest or EasyWeChat\BasicService\Application or EasyWeChat\OpenPlatform\...cialAccount\Application. 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

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