LogManager::notice()   A
last analyzed

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

218
            $handlers = \array_merge($handlers, $this->channel($channel)->/** @scrutinizer ignore-call */ getHandlers());
Loading history...
219
        }
220
221 2
        if ($config['ignore_exceptions'] ?? false) {
222
            $handlers = [new WhatFailureGroupHandler($handlers)];
223
        }
224
225 2
        return new Monolog($this->parseChannel($config), $handlers);
226
    }
227
228
    /**
229
     * Create an instance of the single file log driver.
230
     *
231
     * @param array $config
232
     *
233
     * @return \Psr\Log\LoggerInterface
234
     *
235
     * @throws \Exception
236
     */
237 2
    protected function createSingleDriver(array $config)
238
    {
239 2
        return new Monolog($this->parseChannel($config), [
240 2
            $this->prepareHandler(new StreamHandler(
241 2
                $config['path'],
242 1
                $this->level($config),
243 1
                $config['bubble'] ?? true,
244 1
                $config['permission'] ?? null,
245 1
                $config['locking'] ?? false
246
            ), $config),
247
        ]);
248
    }
249
250
    /**
251
     * Create an instance of the daily file log driver.
252
     *
253
     * @param array $config
254
     *
255
     * @return \Psr\Log\LoggerInterface
256
     */
257 1
    protected function createDailyDriver(array $config)
258
    {
259 1
        return new Monolog($this->parseChannel($config), [
260 1
            $this->prepareHandler(new RotatingFileHandler(
261 1
                $config['path'],
262 1
                $config['days'] ?? 7,
263 1
                $this->level($config),
264 1
                $config['bubble'] ?? true,
265 1
                $config['permission'] ?? null,
266 1
                $config['locking'] ?? false
267
            ), $config),
268
        ]);
269
    }
270
271
    /**
272
     * Create an instance of the Slack log driver.
273
     *
274
     * @param array $config
275
     *
276
     * @return \Psr\Log\LoggerInterface
277
     */
278 1
    protected function createSlackDriver(array $config)
279
    {
280 1
        return new Monolog($this->parseChannel($config), [
281 1
            $this->prepareHandler(new SlackWebhookHandler(
282 1
                $config['url'],
283 1
                $config['channel'] ?? null,
284 1
                $config['username'] ?? 'EasyWeChat',
285 1
                $config['attachment'] ?? true,
286 1
                $config['emoji'] ?? ':boom:',
287 1
                $config['short'] ?? false,
288 1
                $config['context'] ?? true,
289 1
                $this->level($config),
290 1
                $config['bubble'] ?? true,
291 1
                $config['exclude_fields'] ?? []
292
            ), $config),
293
        ]);
294
    }
295
296
    /**
297
     * Create an instance of the syslog log driver.
298
     *
299
     * @param array $config
300
     *
301
     * @return \Psr\Log\LoggerInterface
302
     */
303 1
    protected function createSyslogDriver(array $config)
304
    {
305 1
        return new Monolog($this->parseChannel($config), [
306 1
            $this->prepareHandler(new SyslogHandler(
307 1
                'EasyWeChat',
308 1
                $config['facility'] ?? LOG_USER,
309 1
                $this->level($config)
310
            ), $config),
311
        ]);
312
    }
313
314
    /**
315
     * Create an instance of the "error log" log driver.
316
     *
317
     * @param array $config
318
     *
319
     * @return \Psr\Log\LoggerInterface
320
     */
321 2
    protected function createErrorlogDriver(array $config)
322
    {
323 2
        return new Monolog($this->parseChannel($config), [
324 2
            $this->prepareHandler(
325 2
                new ErrorLogHandler(
326 2
                    $config['type'] ?? ErrorLogHandler::OPERATING_SYSTEM,
327 2
                    $this->level($config)
328
                )
329
            ),
330
        ]);
331
    }
332
333
    /**
334
     * Prepare the handlers for usage by Monolog.
335
     *
336
     * @param array $handlers
337
     *
338
     * @return array
339
     */
340 2
    protected function prepareHandlers(array $handlers)
341
    {
342 2
        foreach ($handlers as $key => $handler) {
343 2
            $handlers[$key] = $this->prepareHandler($handler);
344
        }
345
346 2
        return $handlers;
347
    }
348
349
    /**
350
     * Prepare the handler for usage by Monolog.
351
     *
352
     * @param \Monolog\Handler\HandlerInterface $handler
353
     *
354
     * @return \Monolog\Handler\HandlerInterface
355
     */
356 3
    protected function prepareHandler(HandlerInterface $handler, array $config = [])
357
    {
358 3
        if (!isset($config['formatter'])) {
359 3
            if ($handler instanceof FormattableHandlerInterface) {
360 3
                $handler->setFormatter($this->formatter());
361
            }
362
        }
363
364 3
        return $handler;
365
    }
366
367
    /**
368
     * Get a Monolog formatter instance.
369
     *
370
     * @return \Monolog\Formatter\FormatterInterface
371
     */
372 3
    protected function formatter()
373
    {
374 3
        $formatter = new LineFormatter(null, null, true, true);
375 3
        $formatter->includeStacktraces();
376
377 3
        return $formatter;
378
    }
379
380
    /**
381
     * Extract the log channel from the given configuration.
382
     *
383
     * @param array $config
384
     *
385
     * @return string
386
     */
387 2
    protected function parseChannel(array $config)
388
    {
389 2
        return $config['name'] ?? 'EasyWeChat';
390
    }
391
392
    /**
393
     * Parse the string level into a Monolog constant.
394
     *
395
     * @param array $config
396
     *
397
     * @return int
398
     *
399
     * @throws InvalidArgumentException
400
     */
401 4
    protected function level(array $config)
402
    {
403 4
        $level = $config['level'] ?? 'debug';
404
405 4
        if (isset($this->levels[$level])) {
406 3
            return $this->levels[$level];
407
        }
408
409 1
        throw new InvalidArgumentException('Invalid log level.');
410
    }
411
412
    /**
413
     * Get the default log driver name.
414
     *
415
     * @return string
416
     */
417 3
    public function getDefaultDriver()
418
    {
419 3
        return $this->app['config']['log.default'];
420
    }
421
422
    /**
423
     * Set the default log driver name.
424
     *
425
     * @param string $name
426
     */
427 1
    public function setDefaultDriver($name)
428
    {
429 1
        $this->app['config']['log.default'] = $name;
430 1
    }
431
432
    /**
433
     * Register a custom driver creator Closure.
434
     *
435
     * @param string   $driver
436
     * @param \Closure $callback
437
     *
438
     * @return $this
439
     */
440 1
    public function extend($driver, \Closure $callback)
441
    {
442 1
        $this->customCreators[$driver] = $callback->bindTo($this, $this);
443
444 1
        return $this;
445
    }
446
447
    /**
448
     * System is unusable.
449
     *
450
     * @param string $message
451
     * @param array  $context
452
     *
453
     * @return mixed
454
     *
455
     * @throws \Exception
456
     */
457 1
    public function emergency($message, array $context = [])
458
    {
459 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...
460
    }
461
462
    /**
463
     * Action must be taken immediately.
464
     *
465
     * Example: Entire website down, database unavailable, etc. This should
466
     * trigger the SMS alerts and wake you up.
467
     *
468
     * @param string $message
469
     * @param array  $context
470
     *
471
     * @return mixed
472
     *
473
     * @throws \Exception
474
     */
475 1
    public function alert($message, array $context = [])
476
    {
477 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...
478
    }
479
480
    /**
481
     * Critical conditions.
482
     *
483
     * Example: Application component unavailable, unexpected exception.
484
     *
485
     * @param string $message
486
     * @param array  $context
487
     *
488
     * @return mixed
489
     *
490
     * @throws \Exception
491
     */
492 1
    public function critical($message, array $context = [])
493
    {
494 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...
495
    }
496
497
    /**
498
     * Runtime errors that do not require immediate action but should typically
499
     * be logged and monitored.
500
     *
501
     * @param string $message
502
     * @param array  $context
503
     *
504
     * @return mixed
505
     *
506
     * @throws \Exception
507
     */
508 1
    public function error($message, array $context = [])
509
    {
510 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...
511
    }
512
513
    /**
514
     * Exceptional occurrences that are not errors.
515
     *
516
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
517
     * that are not necessarily wrong.
518
     *
519
     * @param string $message
520
     * @param array  $context
521
     *
522
     * @return mixed
523
     *
524
     * @throws \Exception
525
     */
526 1
    public function warning($message, array $context = [])
527
    {
528 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...
529
    }
530
531
    /**
532
     * Normal but significant events.
533
     *
534
     * @param string $message
535
     * @param array  $context
536
     *
537
     * @return mixed
538
     *
539
     * @throws \Exception
540
     */
541 1
    public function notice($message, array $context = [])
542
    {
543 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...
544
    }
545
546
    /**
547
     * Interesting events.
548
     *
549
     * Example: User logs in, SQL logs.
550
     *
551
     * @param string $message
552
     * @param array  $context
553
     *
554
     * @return mixed
555
     *
556
     * @throws \Exception
557
     */
558 1
    public function info($message, array $context = [])
559
    {
560 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...
561
    }
562
563
    /**
564
     * Detailed debug information.
565
     *
566
     * @param string $message
567
     * @param array  $context
568
     *
569
     * @return mixed
570
     *
571
     * @throws \Exception
572
     */
573 2
    public function debug($message, array $context = [])
574
    {
575 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...
576
    }
577
578
    /**
579
     * Logs with an arbitrary level.
580
     *
581
     * @param mixed  $level
582
     * @param string $message
583
     * @param array  $context
584
     *
585
     * @return mixed
586
     *
587
     * @throws \Exception
588
     */
589 1
    public function log($level, $message, array $context = [])
590
    {
591 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...
592
    }
593
594
    /**
595
     * Dynamically call the default driver instance.
596
     *
597
     * @param string $method
598
     * @param array  $parameters
599
     *
600
     * @return mixed
601
     *
602
     * @throws \Exception
603
     */
604 1
    public function __call($method, $parameters)
605
    {
606 1
        return $this->driver()->$method(...$parameters);
607
    }
608
}
609