Passed
Pull Request — master (#1678)
by Carlos
20:28
created

LogManager::channel()   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
     * @throws \Exception
86
     */
87 1
    public function stack(array $channels, $channel = null)
88
    {
89 1
        return $this->createStackDriver(compact('channels', 'channel'));
90
    }
91
92
    /**
93
     * Get a log channel instance.
94
     *
95
     * @param string|null $channel
96
     *
97
     * @return mixed
98
     * @throws \Exception
99
     */
100 2
    public function channel($channel = null)
101
    {
102 2
        return $this->get($channel);
103
    }
104
105
    /**
106
     * Get a log driver instance.
107
     *
108
     * @param string|null $driver
109
     *
110
     * @return mixed
111
     * @throws \Exception
112
     */
113 7
    public function driver($driver = null)
114
    {
115 7
        return $this->get($driver ?? $this->getDefaultDriver());
116
    }
117
118
    /**
119
     * Attempt to get the log from the local cache.
120
     *
121
     * @param string $name
122
     *
123
     * @return \Psr\Log\LoggerInterface
124
     * @throws \Exception
125
     */
126 8
    protected function get($name)
127
    {
128
        try {
129 8
            return $this->channels[$name] ?? ($this->channels[$name] = $this->resolve($name));
130 4
        } catch (\Throwable $e) {
131 4
            $logger = $this->createEmergencyLogger();
132
133 4
            $logger->emergency('Unable to create configured logger. Using emergency logger.', [
134 4
                    'exception' => $e,
135
                ]);
136
137 4
            return $logger;
138
        }
139
    }
140
141
    /**
142
     * Resolve the given log instance by name.
143
     *
144
     * @param string $name
145
     *
146
     * @return \Psr\Log\LoggerInterface
147
     *
148
     * @throws \InvalidArgumentException
149
     */
150 8
    protected function resolve($name)
151
    {
152 8
        $config = $this->app['config']->get(\sprintf('log.channels.%s', $name));
153
154 8
        if (is_null($config)) {
155 2
            throw new \InvalidArgumentException(\sprintf('Log [%s] is not defined.', $name));
156
        }
157
158 6
        if (isset($this->customCreators[$config['driver']])) {
159 1
            return $this->callCustomCreator($config);
160
        }
161
162 5
        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';
163
164 5
        if (method_exists($this, $driverMethod)) {
165 4
            return $this->{$driverMethod}($config);
166
        }
167
168 1
        throw new \InvalidArgumentException(\sprintf('Driver [%s] is not supported.', $config['driver']));
169
    }
170
171
    /**
172
     * Create an emergency log handler to avoid white screens of death.
173
     *
174
     * @return \Monolog\Logger
175
     *
176
     * @throws \Exception
177
     */
178 2
    protected function createEmergencyLogger()
179
    {
180 2
        return new Monolog('EasyWeChat', $this->prepareHandlers([new StreamHandler(
181 2
            \sys_get_temp_dir().'/easywechat/easywechat.log', $this->level(['level' => 'debug'])
182
        )]));
183
    }
184
185
    /**
186
     * Call a custom driver creator.
187
     *
188
     * @param array $config
189
     *
190
     * @return mixed
191
     */
192 1
    protected function callCustomCreator(array $config)
193
    {
194 1
        return $this->customCreators[$config['driver']]($this->app, $config);
195
    }
196
197
    /**
198
     * Create an aggregate log driver instance.
199
     *
200
     * @param array $config
201
     *
202
     * @return \Monolog\Logger
203
     * @throws \Exception
204
     */
205 2
    protected function createStackDriver(array $config)
206
    {
207 2
        $handlers = [];
208
209 2
        foreach ($config['channels'] ?? [] as $channel) {
210 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

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