Passed
Push — master ( d8d686...ba68a8 )
by Carlos
04:06
created

LogManager::createSyslogDriver()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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