Passed
Pull Request — master (#1678)
by Carlos
72:47 queued 14:33
created

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

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