Test Failed
Push — master ( e47890...96dce3 )
by Jim
03:33
created

LogManager   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 528
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 42
dl 0
loc 528
rs 8.295
c 0
b 0
f 0

33 Methods

Rating   Name   Duplication   Size   Complexity  
A createDailyDriver() 0 5 1
A error() 0 3 1
A resolve() 0 14 4
A channel() 0 3 1
A createSlackDriver() 0 12 1
A createSingleDriver() 0 5 1
A parseChannel() 0 3 1
A alert() 0 3 1
A driver() 0 3 2
A formatter() 0 6 1
A __call() 0 4 1
A callCustomCreator() 0 3 1
A get() 0 11 2
A createEmergencyLogger() 0 5 1
A debug() 0 3 1
A setDefaultDriver() 0 5 1
A extend() 0 5 1
A log() 0 3 1
A critical() 0 3 1
A info() 0 3 1
A getDefaultDriver() 0 3 1
A stack() 0 3 1
A addChannels() 0 9 2
A createErrorlogDriver() 0 5 1
A notice() 0 3 1
A emergency() 0 3 1
A level() 0 9 2
A createSyslogDriver() 0 5 1
A createStackDriver() 0 8 2
A prepareHandler() 0 3 1
A __construct() 0 3 1
A prepareHandlers() 0 7 2
A warning() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like LogManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use LogManager, and based on these observations, apply Extract Interface, too.

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

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