LogManager::emergency()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 2
cts 2
cp 1
crap 1
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 19
    public function __construct(ServiceContainer $app)
66
    {
67 19
        $this->app = $app;
68 19
    }
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 1
    public function stack(array $channels, $channel = null)
79
    {
80 1
        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 2
    public function channel($channel = null)
91
    {
92 2
        return $this->get($channel);
93
    }
94
95
    /**
96
     * Get a log driver instance.
97
     *
98
     * @param null $driver
99
     * @return \Psr\Log\LoggerInterface
100
     */
101 15
    public function driver($driver = null)
102
    {
103 15
        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 16
    protected function get($name)
114
    {
115
        try {
116 16
            return Arr::get($this->channels, $name, $this->channels[$name] = $this->resolve($name));
117
        }
118
        // 兼容低于PHP7.0,不使用\Throwable
119 3
        catch (\InvalidArgumentException $e) {
120 3
            $logger = $this->createEmergencyLogger();
121 3
            $logger->emergency('Unable to create configured logger. Using emergency logger.', [
122 3
                'exception' => $e,
123
            ]);
124
125 3
            return $logger;
126
        }
127
    }
128
129
    /**
130
     * Resolve the given log instance by name.
131
     *
132
     * @param string $name
133
     *
134
     * @return \Psr\Log\LoggerInterface
135
     *
136
     * @throws \InvalidArgumentException
137
     */
138 16
    protected function resolve($name)
139
    {
140 16
        $config = $this->app['config']->get(\sprintf('log.channels.%s', $name));
141 16
        if (is_null($config)) {
142 2
            throw new \InvalidArgumentException(\sprintf('Log [%s] is not defined.', $name));
143
        }
144 14
        if (isset($this->customCreators[$config['driver']])) {
145 1
            return $this->callCustomCreator($config);
146
        }
147 13
        $driverMethod = 'create' . ucfirst($config['driver']) . 'Driver';
148 13
        if (method_exists($this, $driverMethod)) {
149 12
            return $this->{$driverMethod}($config);
150
        }
151 1
        throw new \InvalidArgumentException(\sprintf('Driver [%s] is not supported.', $config['driver']));
152
    }
153
154
    /**
155
     * Create an emergency log handler to avoid white screens of death.
156
     *
157
     * @return \Monolog\Logger
158
     */
159 1
    protected function createEmergencyLogger()
160
    {
161 1
        return new Monolog('TimSDK', $this->prepareHandlers([
162 1
            new StreamHandler(
163 1
                \sys_get_temp_dir() . '/tim-sdk/tim-sdk.log', $this->level(['level' => 'debug'])
164
            ),
165
        ]));
166
    }
167
168
    /**
169
     * Call a custom driver creator.
170
     *
171
     * @param array $config
172
     *
173
     * @return mixed
174
     */
175 1
    protected function callCustomCreator(array $config)
176
    {
177 1
        return $this->customCreators[$config['driver']]($this->app, $config);
178
    }
179
180
    /**
181
     * Create an aggregate log driver instance.
182
     *
183
     * @param array $config
184
     *
185
     * @return \Monolog\Logger
186
     */
187 2
    protected function createStackDriver(array $config)
188
    {
189 2
        $handlers = [];
190 2
        foreach (Arr::get($config, 'channels', []) as $channel) {
191 2
            $handlers = \array_merge($handlers, $this->channel($channel)->getHandlers());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Psr\Log\LoggerInterface as the method getHandlers() does only exist in the following implementations of said interface: Monolog\Logger.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
192
        }
193
194 2
        return new Monolog($this->parseChannel($config), $handlers);
195
    }
196
197
    /**
198
     * Create an instance of the single file log driver.
199
     *
200
     * @param array $config
201
     *
202
     * @return \Psr\Log\LoggerInterface
203
     *
204
     * @throws \InvalidArgumentException
205
     */
206 5 View Code Duplication
    protected function createSingleDriver(array $config)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
207
    {
208 5
        if (!isset($config['path'])) {
209 1
            throw new \InvalidArgumentException('Invalid log file path.');
210
        }
211
212 4
        return new Monolog($this->parseChannel($config), [
213 4
            $this->prepareHandler(
214 4
                new StreamHandler($config['path'], $this->level($config))
215
            ),
216
        ]);
217
    }
218
219
    /**
220
     * Create an instance of the daily file log driver.
221
     *
222
     * @param array $config
223
     *
224
     * @return \Psr\Log\LoggerInterface
225
     *
226
     * @throws \InvalidArgumentException
227
     */
228 2 View Code Duplication
    protected function createDailyDriver(array $config)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
229
    {
230 2
        if (!isset($config['path'])) {
231 1
            throw new \InvalidArgumentException('Invalid log file path.');
232
        }
233
234 1
        return new Monolog($this->parseChannel($config), [
235 1
            $this->prepareHandler(new RotatingFileHandler(
236 1
                $config['path'], Arr::get($config, 'days', 7), $this->level($config)
237
            )),
238
        ]);
239
    }
240
241
    /**
242
     * Create an instance of the Slack log driver.
243
     *
244
     * @param array $config
245
     *
246
     * @return \Psr\Log\LoggerInterface
247
     */
248 1
    protected function createSlackDriver(array $config)
249
    {
250 1
        return new Monolog($this->parseChannel($config), [
251 1
            $this->prepareHandler(new SlackWebhookHandler(
252 1
                $config['url'],
253 1
                Arr::get($config, 'channel', null),
254 1
                Arr::get($config ,'username', 'TimSDK'),
255 1
                Arr::get($config ,'attachment', true),
256 1
                Arr::get($config ,'emoji', ':boom:'),
257 1
                Arr::get($config ,'short', false),
258 1
                Arr::get($config ,'context', true),
259 1
                $this->level($config)
260
            )),
261
        ]);
262
    }
263
264
    /**
265
     * Create an instance of the syslog log driver.
266
     *
267
     * @param array $config
268
     *
269
     * @return \Psr\Log\LoggerInterface
270
     */
271 1 View Code Duplication
    protected function createSyslogDriver(array $config)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
272
    {
273 1
        return new Monolog($this->parseChannel($config), [
274 1
            $this->prepareHandler(new SyslogHandler(
275 1
                    'TimSDK', Arr::get($config, 'facility', LOG_USER), $this->level($config))
276
            ),
277
        ]);
278
    }
279
280
    /**
281
     * Create an instance of the "error log" log driver.
282
     *
283
     * @param array $config
284
     *
285
     * @return \Psr\Log\LoggerInterface
286
     */
287 7 View Code Duplication
    protected function createErrorlogDriver(array $config)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
288
    {
289 7
        return new Monolog($this->parseChannel($config), [
290 7
            $this->prepareHandler(new ErrorLogHandler(
291 7
                Arr::get($config, 'type', ErrorLogHandler::OPERATING_SYSTEM), $this->level($config))
292
            ),
293
        ]);
294
    }
295
296
    /**
297
     * Prepare the handlers for usage by Monolog.
298
     *
299
     * @param array $handlers
300
     *
301
     * @return array
302
     */
303 1
    protected function prepareHandlers(array $handlers)
304
    {
305 1
        foreach ($handlers as $key => $handler) {
306 1
            $handlers[$key] = $this->prepareHandler($handler);
307
        }
308
309 1
        return $handlers;
310
    }
311
312
    /**
313
     * Prepare the handler for usage by Monolog.
314
     *
315
     * @param \Monolog\Handler\HandlerInterface $handler
316
     *
317
     * @return \Monolog\Handler\HandlerInterface
318
     */
319 10
    protected function prepareHandler(HandlerInterface $handler)
320
    {
321 10
        return $handler->setFormatter($this->formatter());
322
    }
323
324
    /**
325
     * Get a Monolog formatter instance.
326
     *
327
     * @return \Monolog\Formatter\FormatterInterface
328
     */
329 10
    protected function formatter()
330
    {
331 10
        $formatter = new LineFormatter(null, null, true, true);
332 10
        $formatter->includeStacktraces();
333
334 10
        return $formatter;
335
    }
336
337
    /**
338
     * Extract the log channel from the given configuration.
339
     *
340
     * @param array $config
341
     *
342
     * @return string
343
     */
344 9
    protected function parseChannel(array $config)
345
    {
346 9
        return Arr::get($config, 'name', $this->getDefaultDriver());
347
    }
348
349
    /**
350
     * Parse the string level into a Monolog constant.
351
     *
352
     * @param array $config
353
     *
354
     * @return int
355
     *
356
     * @throws \InvalidArgumentException
357
     */
358 11
    protected function level(array $config)
359
    {
360 11
        $level = Arr::get($config, 'level', 'debug');
361
362 11
        if (isset($this->levels[$level])) {
363 10
            return $this->levels[$level];
364
        }
365
366 1
        throw new \InvalidArgumentException('Invalid log level.');
367
    }
368
369
    /**
370
     * Get the default log driver name.
371
     *
372
     * @return string
373
     */
374 13
    public function getDefaultDriver()
375
    {
376 13
        return $this->app['config']['log.default'];
377
    }
378
379
    /**
380
     * Set the default log driver name.
381
     *
382
     * @param string $name
383
     * @return LogManager
384
     */
385 1
    public function setDefaultDriver($name)
386
    {
387 1
        $this->app['config']['log.default'] = $name;
388
389 1
        return $this;
390
    }
391
392
    /**
393
     * Add more channels
394
     *
395
     * @param array $channels
396
     * @return LogManager
397
     */
398
    public function addChannels(array $channels)
399
    {
400
        $original = $this->app['config']->get('log.channels', []);
401
402
        if (is_array($channels)) {
403
            $this->app['config']->set('log.channels', array_merge($original, $channels));
404
        }
405
406
        return $this;
407
    }
408
409
    /**
410
     * Register a custom driver creator Closure.
411
     *
412
     * @param string   $driver
413
     * @param \Closure $callback
414
     *
415
     * @return $this
416
     */
417 1
    public function extend($driver, \Closure $callback)
418
    {
419 1
        $this->customCreators[$driver] = $callback->bindTo($this, $this);
420
421 1
        return $this;
422
    }
423
424
    /**
425
     * System is unusable.
426
     *
427
     * @param string $message
428
     * @param array  $context
429
     *
430
     * @return mixed
431
     */
432 1
    public function emergency($message, array $context = [])
433
    {
434 1
        return $this->log(__FUNCTION__, $message, $context);
435
    }
436
437
    /**
438
     * Action must be taken immediately.
439
     *
440
     * Example: Entire website down, database unavailable, etc. This should
441
     * trigger the SMS alerts and wake you up.
442
     *
443
     * @param string $message
444
     * @param array  $context
445
     *
446
     * @return void
447
     */
448 1
    public function alert($message, array $context = [])
449
    {
450 1
        $this->log(__FUNCTION__, $message, $context);
451 1
    }
452
453
    /**
454
     * Critical conditions.
455
     *
456
     * Example: Application component unavailable, unexpected exception.
457
     *
458
     * @param string $message
459
     * @param array  $context
460
     *
461
     * @return void
462
     */
463 1
    public function critical($message, array $context = [])
464
    {
465 1
        $this->log(__FUNCTION__, $message, $context);
466 1
    }
467
468
    /**
469
     * Runtime errors that do not require immediate action but should typically
470
     * be logged and monitored.
471
     *
472
     * @param string $message
473
     * @param array  $context
474
     *
475
     * @return void
476
     */
477 1
    public function error($message, array $context = [])
478
    {
479 1
        $this->log(__FUNCTION__, $message, $context);
480 1
    }
481
482
    /**
483
     * Exceptional occurrences that are not errors.
484
     *
485
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
486
     * that are not necessarily wrong.
487
     *
488
     * @param string $message
489
     * @param array  $context
490
     *
491
     * @return void
492
     */
493 1
    public function warning($message, array $context = [])
494
    {
495 1
        $this->log(__FUNCTION__, $message, $context);
496 1
    }
497
498
    /**
499
     * Normal but significant events.
500
     *
501
     * @param string $message
502
     * @param array  $context
503
     *
504
     * @return void
505
     */
506 1
    public function notice($message, array $context = [])
507
    {
508 1
        $this->log(__FUNCTION__, $message, $context);
509 1
    }
510
511
    /**
512
     * Interesting events.
513
     *
514
     * Example: User logs in, SQL logs.
515
     *
516
     * @param string $message
517
     * @param array  $context
518
     *
519
     * @return void
520
     */
521 1
    public function info($message, array $context = [])
522
    {
523 1
        $this->log(__FUNCTION__, $message, $context);
524 1
    }
525
526
    /**
527
     * Detailed debug information.
528
     *
529
     * @param string $message
530
     * @param array  $context
531
     *
532
     * @return void
533
     */
534 8
    public function debug($message, array $context = [])
535
    {
536 8
        $this->log(__FUNCTION__, $message, $context);
537 8
    }
538
539
    /**
540
     * Logs with an arbitrary level.
541
     *
542
     * @param mixed  $level
543
     * @param string $message
544
     * @param array  $context
545
     *
546
     * @return void
547
     */
548 9
    public function log($level, $message, array $context = [])
549
    {
550 9
        $this->driver()->log($level, $message, $context);
551 9
    }
552
553
    /**
554
     * Dynamically call the default driver instance.
555
     *
556
     * @param string $method
557
     * @param array  $parameters
558
     *
559
     * @return mixed
560
     */
561 1
    public function __call($method, $parameters)
562
    {
563
//        return $this->driver()->$method(...$parameters);
0 ignored issues
show
Unused Code Comprehensibility introduced by
80% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
564 1
        return call_user_func_array([$this->driver(), $method], $parameters);
565
    }
566
}
567