Passed
Pull Request — master (#1729)
by
unknown
03:22
created

LogManager::createDailyDriver()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 9
c 2
b 0
f 0
nc 1
nop 1
dl 0
loc 11
ccs 9
cts 9
cp 1
crap 1
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 a custom log driver instance.
205
     *
206
     * @param  array  $config
207
     * @return \Psr\Log\LoggerInterface
208
     */
209
    protected function createCustomDriver(array $config)
210
    {
211
        $factory = is_callable($via = $config['via']) ? $via : $this->app->make($via);
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

211
        $factory = is_callable($via = $config['via']) ? $via : $this->app->/** @scrutinizer ignore-call */ make($via);
Loading history...
212
213
        return $factory($config);
214
    }
215
216
    /**
217
     * Create an aggregate log driver instance.
218
     *
219
     * @param array $config
220
     *
221
     * @return \Monolog\Logger
222
     *
223
     * @throws \Exception
224
     */
225 2
    protected function createStackDriver(array $config)
226
    {
227 2
        $handlers = [];
228
229 2
        foreach ($config['channels'] ?? [] as $channel) {
230 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

230
            $handlers = \array_merge($handlers, $this->channel($channel)->/** @scrutinizer ignore-call */ getHandlers());
Loading history...
231
        }
232
233 2
        if ($config['ignore_exceptions'] ?? false) {
234
            $handlers = [new WhatFailureGroupHandler($handlers)];
235
        }
236
237 2
        return new Monolog($this->parseChannel($config), $handlers);
238
    }
239
240
    /**
241
     * Create an instance of the single file log driver.
242
     *
243
     * @param array $config
244
     *
245
     * @return \Psr\Log\LoggerInterface
246
     *
247
     * @throws \Exception
248
     */
249 2
    protected function createSingleDriver(array $config)
250
    {
251 2
        return new Monolog($this->parseChannel($config), [
252 2
            $this->prepareHandler(new StreamHandler(
253 2
                $config['path'],
254 1
                $this->level($config),
255 1
                $config['bubble'] ?? true,
256 1
                $config['permission'] ?? null,
257 1
                $config['locking'] ?? false
258
            ), $config),
259
        ]);
260
    }
261
262
    /**
263
     * Create an instance of the daily file log driver.
264
     *
265
     * @param array $config
266
     *
267
     * @return \Psr\Log\LoggerInterface
268
     */
269 1
    protected function createDailyDriver(array $config)
270
    {
271 1
        return new Monolog($this->parseChannel($config), [
272 1
            $this->prepareHandler(new RotatingFileHandler(
273 1
                $config['path'],
274 1
                $config['days'] ?? 7,
275 1
                $this->level($config),
276 1
                $config['bubble'] ?? true,
277 1
                $config['permission'] ?? null,
278 1
                $config['locking'] ?? false
279
            ), $config),
280
        ]);
281
    }
282
283
    /**
284
     * Create an instance of the Slack log driver.
285
     *
286
     * @param array $config
287
     *
288
     * @return \Psr\Log\LoggerInterface
289
     */
290 1
    protected function createSlackDriver(array $config)
291
    {
292 1
        return new Monolog($this->parseChannel($config), [
293 1
            $this->prepareHandler(new SlackWebhookHandler(
294 1
                $config['url'],
295 1
                $config['channel'] ?? null,
296 1
                $config['username'] ?? 'EasyWeChat',
297 1
                $config['attachment'] ?? true,
298 1
                $config['emoji'] ?? ':boom:',
299 1
                $config['short'] ?? false,
300 1
                $config['context'] ?? true,
301 1
                $this->level($config),
302 1
                $config['bubble'] ?? true,
303 1
                $config['exclude_fields'] ?? []
304
            ), $config),
305
        ]);
306
    }
307
308
    /**
309
     * Create an instance of the syslog log driver.
310
     *
311
     * @param array $config
312
     *
313
     * @return \Psr\Log\LoggerInterface
314
     */
315 1
    protected function createSyslogDriver(array $config)
316
    {
317 1
        return new Monolog($this->parseChannel($config), [
318 1
            $this->prepareHandler(new SyslogHandler(
319 1
                'EasyWeChat',
320 1
                $config['facility'] ?? LOG_USER,
321 1
                $this->level($config)
322
            ), $config),
323
        ]);
324
    }
325
326
    /**
327
     * Create an instance of the "error log" log driver.
328
     *
329
     * @param array $config
330
     *
331
     * @return \Psr\Log\LoggerInterface
332
     */
333 2
    protected function createErrorlogDriver(array $config)
334
    {
335 2
        return new Monolog($this->parseChannel($config), [
336 2
            $this->prepareHandler(
337 2
                new ErrorLogHandler(
338 2
                    $config['type'] ?? ErrorLogHandler::OPERATING_SYSTEM,
339 2
                    $this->level($config)
340
                )
341
            ),
342
        ]);
343
    }
344
345
    /**
346
     * Create an instance of any handler available in Monolog.
347
     *
348
     * @param  array  $config
349
     * @return \Psr\Log\LoggerInterface
350
     *
351
     * @throws \InvalidArgumentException
352
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
353
     */
354
    protected function createMonologDriver(array $config)
355
    {
356
        if (!is_a($config['handler'], HandlerInterface::class, true)) {
357
            throw new InvalidArgumentException(
358
                $config['handler'] . ' must be an instance of ' . HandlerInterface::class
359
            );
360
        }
361
362
        $with = array_merge(
363
            ['level' => $this->level($config)],
364
            $config['with'] ?? [],
365
            $config['handler_with'] ?? []
366
        );
367
368
        return new Monolog($this->parseChannel($config), [$this->prepareHandler(
369
            $this->app->make($config['handler'], $with),
370
            $config
371
        )]);
372
    }
373
374
    /**
375
     * Prepare the handlers for usage by Monolog.
376
     *
377
     * @param array $handlers
378
     *
379
     * @return array
380
     */
381 2
    protected function prepareHandlers(array $handlers)
382
    {
383 2
        foreach ($handlers as $key => $handler) {
384 2
            $handlers[$key] = $this->prepareHandler($handler);
385
        }
386
387 2
        return $handlers;
388
    }
389
390
    /**
391
     * Prepare the handler for usage by Monolog.
392
     *
393
     * @param \Monolog\Handler\HandlerInterface $handler
394
     *
395
     * @return \Monolog\Handler\HandlerInterface
396
     */
397 3
    protected function prepareHandler(HandlerInterface $handler, array $config = [])
398
    {
399 3
        if (!isset($config['formatter'])) {
400 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

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