Passed
Pull Request — master (#1252)
by Keal
02:21
created

LogManager::info()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
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(\sprintf('log.channels.%s', $name));
148
149
        if (is_null($config)) {
150
            throw new \InvalidArgumentException(\sprintf('Log [%s] is not defined.', $name));
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(\sprintf('Driver [%s] is not supported.', $config['driver']));
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().'/easywechat/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
     * @return mixed
410
     */
411
    public function emergency($message, array $context = [])
412
    {
413
        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...
414
    }
415
416
    /**
417
     * Action must be taken immediately.
418
     *
419
     * Example: Entire website down, database unavailable, etc. This should
420
     * trigger the SMS alerts and wake you up.
421
     *
422
     * @param string $message
423
     * @param array  $context
424
     *
425
     * @return mixed
426
     */
427
    public function alert($message, array $context = [])
428
    {
429
        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...
430
    }
431
432
    /**
433
     * Critical conditions.
434
     *
435
     * Example: Application component unavailable, unexpected exception.
436
     *
437
     * @param string $message
438
     * @param array  $context
439
     *
440
     * @return mixed
441
     */
442
    public function critical($message, array $context = [])
443
    {
444
        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...
445
    }
446
447
    /**
448
     * Runtime errors that do not require immediate action but should typically
449
     * be logged and monitored.
450
     *
451
     * @param string $message
452
     * @param array  $context
453
     *
454
     * @return mixed
455
     */
456
    public function error($message, array $context = [])
457
    {
458
        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...
459
    }
460
461
    /**
462
     * Exceptional occurrences that are not errors.
463
     *
464
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
465
     * that are not necessarily wrong.
466
     *
467
     * @param string $message
468
     * @param array  $context
469
     *
470
     * @return mixed
471
     */
472
    public function warning($message, array $context = [])
473
    {
474
        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...
475
    }
476
477
    /**
478
     * Normal but significant events.
479
     *
480
     * @param string $message
481
     * @param array  $context
482
     *
483
     * @return mixed
484
     */
485
    public function notice($message, array $context = [])
486
    {
487
        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...
488
    }
489
490
    /**
491
     * Interesting events.
492
     *
493
     * Example: User logs in, SQL logs.
494
     *
495
     * @param string $message
496
     * @param array  $context
497
     *
498
     * @return mixed
499
     */
500
    public function info($message, array $context = [])
501
    {
502
        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...
503
    }
504
505
    /**
506
     * Detailed debug information.
507
     *
508
     * @param string $message
509
     * @param array  $context
510
     *
511
     * @return mixed
512
     */
513
    public function debug($message, array $context = [])
514
    {
515
        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...
516
    }
517
518
    /**
519
     * Logs with an arbitrary level.
520
     *
521
     * @param mixed  $level
522
     * @param string $message
523
     * @param array  $context
524
     *
525
     * @return mixed
526
     */
527
    public function log($level, $message, array $context = [])
528
    {
529
        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...
530
    }
531
532
    /**
533
     * Dynamically call the default driver instance.
534
     *
535
     * @param string $method
536
     * @param array  $parameters
537
     *
538
     * @return mixed
539
     */
540
    public function __call($method, $parameters)
541
    {
542
        return $this->driver()->$method(...$parameters);
543
    }
544
}
545