Passed
Push — master ( dc15a9...bf933d )
by Carlos
02:25
created

LogManager   B

Complexity

Total Complexity 39

Size/Duplication

Total Lines 495
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 495
rs 8.2857
c 0
b 0
f 0
wmc 39

32 Methods

Rating   Name   Duplication   Size   Complexity  
A parseChannel() 0 3 1
A createErrorlogDriver() 0 5 1
A createSyslogDriver() 0 5 1
A get() 0 12 2
A error() 0 3 1
A formatter() 0 6 1
A critical() 0 3 1
A getDefaultDriver() 0 3 1
A log() 0 3 1
A extend() 0 5 1
A setDefaultDriver() 0 3 1
A level() 0 9 2
A warning() 0 3 1
A resolve() 0 19 4
A notice() 0 3 1
A info() 0 3 1
A createEmergencyLogger() 0 4 1
A callCustomCreator() 0 3 1
A createSlackDriver() 0 12 1
A stack() 0 3 1
A createSingleDriver() 0 5 1
A prepareHandler() 0 3 1
A driver() 0 3 1
A createStackDriver() 0 9 2
A debug() 0 3 1
A emergency() 0 3 1
A alert() 0 3 1
A createDailyDriver() 0 5 1
A __construct() 0 3 1
A channel() 0 3 1
A prepareHandlers() 0 7 2
A __call() 0 3 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\HandlerInterface;
18
use Monolog\Handler\RotatingFileHandler;
19
use Monolog\Handler\SlackWebhookHandler;
20
use Monolog\Handler\StreamHandler;
21
use Monolog\Handler\SyslogHandler;
22
use Monolog\Logger as Monolog;
23
use Psr\Log\LoggerInterface;
24
25
/**
26
 * Class LogManager.
27
 *
28
 * @author overtrue <[email protected]>
29
 */
30
class LogManager implements LoggerInterface
31
{
32
    /**
33
     * @var \EasyWeChat\Kernel\ServiceContainer
34
     */
35
    protected $app;
36
37
    /**
38
     * The array of resolved channels.
39
     *
40
     * @var array
41
     */
42
    protected $channels = [];
43
44
    /**
45
     * The registered custom driver creators.
46
     *
47
     * @var array
48
     */
49
    protected $customCreators = [];
50
51
    /**
52
     * The Log levels.
53
     *
54
     * @var array
55
     */
56
    protected $levels = [
57
        'debug' => Monolog::DEBUG,
58
        'info' => Monolog::INFO,
59
        'notice' => Monolog::NOTICE,
60
        'warning' => Monolog::WARNING,
61
        'error' => Monolog::ERROR,
62
        'critical' => Monolog::CRITICAL,
63
        'alert' => Monolog::ALERT,
64
        'emergency' => Monolog::EMERGENCY,
65
    ];
66
67
    /**
68
     * LogManager constructor.
69
     *
70
     * @param \EasyWeChat\Kernel\ServiceContainer $app
71
     */
72
    public function __construct(ServiceContainer $app)
73
    {
74
        $this->app = $app;
75
    }
76
77
    /**
78
     * Create a new, on-demand aggregate logger instance.
79
     *
80
     * @param array       $channels
81
     * @param string|null $channel
82
     *
83
     * @return \Psr\Log\LoggerInterface
84
     */
85
    public function stack(array $channels, $channel = null)
86
    {
87
        return $this->createStackDriver(compact('channels', 'channel'));
88
    }
89
90
    /**
91
     * Get a log channel instance.
92
     *
93
     * @param string|null $channel
94
     *
95
     * @return mixed
96
     */
97
    public function channel($channel = null)
98
    {
99
        return $this->get($channel);
100
    }
101
102
    /**
103
     * Get a log driver instance.
104
     *
105
     * @param string|null $driver
106
     *
107
     * @return mixed
108
     */
109
    public function driver($driver = null)
110
    {
111
        return $this->get($driver ?? $this->getDefaultDriver());
112
    }
113
114
    /**
115
     * Attempt to get the log from the local cache.
116
     *
117
     * @param string $name
118
     *
119
     * @return \Psr\Log\LoggerInterface
120
     */
121
    protected function get($name)
122
    {
123
        try {
124
            return $this->channels[$name] ?? ($this->channels[$name] = $this->resolve($name));
125
        } catch (\Throwable $e) {
126
            $logger = $this->createEmergencyLogger();
127
128
            $logger->emergency('Unable to create configured logger. Using emergency logger.', [
129
                    'exception' => $e,
130
                ]);
131
132
            return $logger;
133
        }
134
    }
135
136
    /**
137
     * Resolve the given log instance by name.
138
     *
139
     * @param string $name
140
     *
141
     * @return \Psr\Log\LoggerInterface
142
     *
143
     * @throws \InvalidArgumentException
144
     */
145
    protected function resolve($name)
146
    {
147
        $config = $this->app['config']->get("log.channels.{$name}");
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $name instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
148
149
        if (is_null($config)) {
150
            throw new \InvalidArgumentException("Log [{$name}] is not defined.");
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $name instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
151
        }
152
153
        if (isset($this->customCreators[$config['driver']])) {
154
            return $this->callCustomCreator($config);
155
        }
156
157
        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';
158
159
        if (method_exists($this, $driverMethod)) {
160
            return $this->{$driverMethod}($config);
161
        }
162
163
        throw new \InvalidArgumentException("Driver [{$config['driver']}] is not supported.");
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $config instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
164
    }
165
166
    /**
167
     * Create an emergency log handler to avoid white screens of death.
168
     *
169
     * @return \Monolog\Logger
170
     */
171
    protected function createEmergencyLogger()
172
    {
173
        return new Monolog('EasyWeChat', $this->prepareHandlers([new StreamHandler(
174
            \sys_get_temp_dir().'/logs/easywechat.log', $this->level(['level' => 'debug'])
175
        )]));
176
    }
177
178
    /**
179
     * Call a custom driver creator.
180
     *
181
     * @param array $config
182
     *
183
     * @return mixed
184
     */
185
    protected function callCustomCreator(array $config)
186
    {
187
        return $this->customCreators[$config['driver']]($this->app, $config);
188
    }
189
190
    /**
191
     * Create an aggregate log driver instance.
192
     *
193
     * @param array $config
194
     *
195
     * @return \Monolog\Logger
196
     */
197
    protected function createStackDriver(array $config)
198
    {
199
        $handlers = [];
200
201
        foreach ($config['channels'] ?? [] as $channel) {
202
            $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. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

202
            $handlers = \array_merge($handlers, $this->channel($channel)->/** @scrutinizer ignore-call */ getHandlers());
Loading history...
203
        }
204
205
        return new Monolog($this->parseChannel($config), $handlers);
206
    }
207
208
    /**
209
     * Create an instance of the single file log driver.
210
     *
211
     * @param array $config
212
     *
213
     * @return \Psr\Log\LoggerInterface
214
     */
215
    protected function createSingleDriver(array $config)
216
    {
217
        return new Monolog($this->parseChannel($config), [
218
            $this->prepareHandler(
219
                new StreamHandler($config['path'], $this->level($config))
220
            ),
221
        ]);
222
    }
223
224
    /**
225
     * Create an instance of the daily file log driver.
226
     *
227
     * @param array $config
228
     *
229
     * @return \Psr\Log\LoggerInterface
230
     */
231
    protected function createDailyDriver(array $config)
232
    {
233
        return new Monolog($this->parseChannel($config), [
234
            $this->prepareHandler(new RotatingFileHandler(
235
                $config['path'], $config['days'] ?? 7, $this->level($config)
236
            )),
237
        ]);
238
    }
239
240
    /**
241
     * Create an instance of the Slack log driver.
242
     *
243
     * @param array $config
244
     *
245
     * @return \Psr\Log\LoggerInterface
246
     */
247
    protected function createSlackDriver(array $config)
248
    {
249
        return new Monolog($this->parseChannel($config), [
250
            $this->prepareHandler(new SlackWebhookHandler(
251
                $config['url'],
252
                $config['channel'] ?? null,
253
                $config['username'] ?? 'EasyWeChat',
254
                $config['attachment'] ?? true,
255
                $config['emoji'] ?? ':boom:',
256
                $config['short'] ?? false,
257
                $config['context'] ?? true,
258
                $this->level($config)
259
            )),
260
        ]);
261
    }
262
263
    /**
264
     * Create an instance of the syslog log driver.
265
     *
266
     * @param array $config
267
     *
268
     * @return \Psr\Log\LoggerInterface
269
     */
270
    protected function createSyslogDriver(array $config)
271
    {
272
        return new Monolog($this->parseChannel($config), [
273
            $this->prepareHandler(new SyslogHandler(
274
                    'EasyWeChat', $config['facility'] ?? LOG_USER, $this->level($config))
275
            ),
276
        ]);
277
    }
278
279
    /**
280
     * Create an instance of the "error log" log driver.
281
     *
282
     * @param array $config
283
     *
284
     * @return \Psr\Log\LoggerInterface
285
     */
286
    protected function createErrorlogDriver(array $config)
287
    {
288
        return new Monolog($this->parseChannel($config), [
289
            $this->prepareHandler(new ErrorLogHandler(
290
                    $config['type'] ?? ErrorLogHandler::OPERATING_SYSTEM, $this->level($config))
291
            ),
292
        ]);
293
    }
294
295
    /**
296
     * Prepare the handlers for usage by Monolog.
297
     *
298
     * @param array $handlers
299
     *
300
     * @return array
301
     */
302
    protected function prepareHandlers(array $handlers)
303
    {
304
        foreach ($handlers as $key => $handler) {
305
            $handlers[$key] = $this->prepareHandler($handler);
306
        }
307
308
        return $handlers;
309
    }
310
311
    /**
312
     * Prepare the handler for usage by Monolog.
313
     *
314
     * @param \Monolog\Handler\HandlerInterface $handler
315
     *
316
     * @return \Monolog\Handler\HandlerInterface
317
     */
318
    protected function prepareHandler(HandlerInterface $handler)
319
    {
320
        return $handler->setFormatter($this->formatter());
321
    }
322
323
    /**
324
     * Get a Monolog formatter instance.
325
     *
326
     * @return \Monolog\Formatter\FormatterInterface
327
     */
328
    protected function formatter()
329
    {
330
        $formatter = new LineFormatter(null, null, true, true);
331
        $formatter->includeStacktraces();
332
333
        return $formatter;
334
    }
335
336
    /**
337
     * Extract the log channel from the given configuration.
338
     *
339
     * @param array $config
340
     *
341
     * @return string
342
     */
343
    protected function parseChannel(array $config)
344
    {
345
        return $config['name'] ?? null;
346
    }
347
348
    /**
349
     * Parse the string level into a Monolog constant.
350
     *
351
     * @param array $config
352
     *
353
     * @return int
354
     *
355
     * @throws \InvalidArgumentException
356
     */
357
    protected function level(array $config)
358
    {
359
        $level = $config['level'] ?? 'debug';
360
361
        if (isset($this->levels[$level])) {
362
            return $this->levels[$level];
363
        }
364
365
        throw new \InvalidArgumentException('Invalid log level.');
366
    }
367
368
    /**
369
     * Get the default log driver name.
370
     *
371
     * @return string
372
     */
373
    public function getDefaultDriver()
374
    {
375
        return $this->app['config']['log.default'];
376
    }
377
378
    /**
379
     * Set the default log driver name.
380
     *
381
     * @param string $name
382
     */
383
    public function setDefaultDriver($name)
384
    {
385
        $this->app['config']['log.default'] = $name;
386
    }
387
388
    /**
389
     * Register a custom driver creator Closure.
390
     *
391
     * @param string   $driver
392
     * @param \Closure $callback
393
     *
394
     * @return $this
395
     */
396
    public function extend($driver, \Closure $callback)
397
    {
398
        $this->customCreators[$driver] = $callback->bindTo($this, $this);
399
400
        return $this;
401
    }
402
403
    /**
404
     * System is unusable.
405
     *
406
     * @param string $message
407
     * @param array  $context
408
     */
409
    public function emergency($message, array $context = [])
410
    {
411
        return $this->driver()->emergency($message, $context);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->driver()->...ncy($message, $context) returns the type boolean which is incompatible with the return type mandated by Psr\Log\LoggerInterface::emergency() of void.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
412
    }
413
414
    /**
415
     * Action must be taken immediately.
416
     *
417
     * Example: Entire website down, database unavailable, etc. This should
418
     * trigger the SMS alerts and wake you up.
419
     *
420
     * @param string $message
421
     * @param array  $context
422
     */
423
    public function alert($message, array $context = [])
424
    {
425
        return $this->driver()->alert($message, $context);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->driver()->alert($message, $context) returns the type boolean which is incompatible with the return type mandated by Psr\Log\LoggerInterface::alert() of void.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
426
    }
427
428
    /**
429
     * Critical conditions.
430
     *
431
     * Example: Application component unavailable, unexpected exception.
432
     *
433
     * @param string $message
434
     * @param array  $context
435
     */
436
    public function critical($message, array $context = [])
437
    {
438
        return $this->driver()->critical($message, $context);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->driver()->...cal($message, $context) returns the type boolean which is incompatible with the return type mandated by Psr\Log\LoggerInterface::critical() of void.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
439
    }
440
441
    /**
442
     * Runtime errors that do not require immediate action but should typically
443
     * be logged and monitored.
444
     *
445
     * @param string $message
446
     * @param array  $context
447
     */
448
    public function error($message, array $context = [])
449
    {
450
        return $this->driver()->error($message, $context);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->driver()->error($message, $context) returns the type boolean which is incompatible with the return type mandated by Psr\Log\LoggerInterface::error() of void.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
451
    }
452
453
    /**
454
     * Exceptional occurrences that are not errors.
455
     *
456
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
457
     * that are not necessarily wrong.
458
     *
459
     * @param string $message
460
     * @param array  $context
461
     */
462
    public function warning($message, array $context = [])
463
    {
464
        return $this->driver()->warning($message, $context);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->driver()->...ing($message, $context) returns the type boolean which is incompatible with the return type mandated by Psr\Log\LoggerInterface::warning() of void.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
465
    }
466
467
    /**
468
     * Normal but significant events.
469
     *
470
     * @param string $message
471
     * @param array  $context
472
     */
473
    public function notice($message, array $context = [])
474
    {
475
        return $this->driver()->notice($message, $context);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->driver()->...ice($message, $context) returns the type boolean which is incompatible with the return type mandated by Psr\Log\LoggerInterface::notice() of void.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
476
    }
477
478
    /**
479
     * Interesting events.
480
     *
481
     * Example: User logs in, SQL logs.
482
     *
483
     * @param string $message
484
     * @param array  $context
485
     */
486
    public function info($message, array $context = [])
487
    {
488
        return $this->driver()->info($message, $context);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->driver()->info($message, $context) returns the type boolean which is incompatible with the return type mandated by Psr\Log\LoggerInterface::info() of void.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
489
    }
490
491
    /**
492
     * Detailed debug information.
493
     *
494
     * @param string $message
495
     * @param array  $context
496
     */
497
    public function debug($message, array $context = [])
498
    {
499
        return $this->driver()->debug($message, $context);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->driver()->debug($message, $context) returns the type boolean which is incompatible with the return type mandated by Psr\Log\LoggerInterface::debug() of void.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
500
    }
501
502
    /**
503
     * Logs with an arbitrary level.
504
     *
505
     * @param mixed  $level
506
     * @param string $message
507
     * @param array  $context
508
     */
509
    public function log($level, $message, array $context = [])
510
    {
511
        return $this->driver()->log($level, $message, $context);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->driver()->...el, $message, $context) returns the type boolean which is incompatible with the return type mandated by Psr\Log\LoggerInterface::log() of void.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
512
    }
513
514
    /**
515
     * Dynamically call the default driver instance.
516
     *
517
     * @param string $method
518
     * @param array  $parameters
519
     *
520
     * @return mixed
521
     */
522
    public function __call($method, $parameters)
523
    {
524
        return $this->driver()->$method(...$parameters);
525
    }
526
}
527