LogManager::prepareHandlers()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 7
rs 10
1
<?php
2
3
namespace Freyo\ApiGateway\Kernel\Log;
4
5
use Freyo\ApiGateway\Kernel\ServiceContainer;
6
use Freyo\ApiGateway\Kernel\Support\Arr;
7
use Monolog\Formatter\LineFormatter;
8
use Monolog\Handler\ErrorLogHandler;
9
use Monolog\Handler\HandlerInterface;
10
use Monolog\Handler\RotatingFileHandler;
11
use Monolog\Handler\SlackWebhookHandler;
12
use Monolog\Handler\StreamHandler;
13
use Monolog\Handler\SyslogHandler;
14
use Monolog\Logger as Monolog;
15
use Psr\Log\LoggerInterface;
16
17
class LogManager implements LoggerInterface
18
{
19
    /**
20
     * @var \Freyo\ApiGateway\Kernel\ServiceContainer
21
     */
22
    protected $app;
23
24
    /**
25
     * The array of resolved channels.
26
     *
27
     * @var array
28
     */
29
    protected $channels = [];
30
31
    /**
32
     * The registered custom driver creators.
33
     *
34
     * @var array
35
     */
36
    protected $customCreators = [];
37
38
    /**
39
     * The Log levels.
40
     *
41
     * @var array
42
     */
43
    protected $levels = [
44
        'debug' => Monolog::DEBUG,
45
        'info' => Monolog::INFO,
46
        'notice' => Monolog::NOTICE,
47
        'warning' => Monolog::WARNING,
48
        'error' => Monolog::ERROR,
49
        'critical' => Monolog::CRITICAL,
50
        'alert' => Monolog::ALERT,
51
        'emergency' => Monolog::EMERGENCY,
52
    ];
53
54
    /**
55
     * LogManager constructor.
56
     *
57
     * @param \Freyo\ApiGateway\Kernel\ServiceContainer $app
58
     */
59
    public function __construct(ServiceContainer $app)
60
    {
61
        $this->app = $app;
62
    }
63
64
    /**
65
     * Create a new, on-demand aggregate logger instance.
66
     *
67
     * @param array       $channels
68
     * @param string|null $channel
69
     *
70
     * @return \Psr\Log\LoggerInterface
71
     */
72
    public function stack(array $channels, $channel = null)
73
    {
74
        return $this->createStackDriver(compact('channels', 'channel'));
75
    }
76
77
    /**
78
     * Get a log channel instance.
79
     *
80
     * @param string|null $channel
81
     *
82
     * @return mixed
83
     */
84
    public function channel($channel = null)
85
    {
86
        return $this->get($channel);
87
    }
88
89
    /**
90
     * Get a log driver instance.
91
     *
92
     * @param string|null $driver
93
     *
94
     * @return mixed
95
     */
96
    public function driver($driver = null)
97
    {
98
        return $this->get($driver ?: $this->getDefaultDriver());
99
    }
100
101
    /**
102
     * Attempt to get the log from the local cache.
103
     *
104
     * @param string $name
105
     *
106
     * @return \Psr\Log\LoggerInterface
107
     */
108
    protected function get($name)
109
    {
110
        try {
111
            return Arr::get($this->channels, $name) ?: ($this->channels[$name] = $this->resolve($name));
112
        } catch (\Throwable $e) {
113
            $logger = $this->createEmergencyLogger();
114
115
            $logger->emergency('Unable to create configured logger. Using emergency logger.', [
116
                    'exception' => $e,
117
                ]);
118
119
            return $logger;
120
        }
121
    }
122
123
    /**
124
     * Resolve the given log instance by name.
125
     *
126
     * @param string $name
127
     *
128
     * @return \Psr\Log\LoggerInterface
129
     *
130
     * @throws \InvalidArgumentException
131
     */
132
    protected function resolve($name)
133
    {
134
        $config = $this->app['config']->get(\sprintf('log.channels.%s', $name));
135
136
        if (is_null($config)) {
137
            throw new \InvalidArgumentException(\sprintf('Log [%s] is not defined.', $name));
138
        }
139
140
        if (isset($this->customCreators[$config['driver']])) {
141
            return $this->callCustomCreator($config);
142
        }
143
144
        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';
145
146
        if (method_exists($this, $driverMethod)) {
147
            return $this->{$driverMethod}($config);
148
        }
149
150
        throw new \InvalidArgumentException(\sprintf('Driver [%s] is not supported.', $config['driver']));
151
    }
152
153
    /**
154
     * Create an emergency log handler to avoid white screens of death.
155
     *
156
     * @return \Monolog\Logger
157
     */
158
    protected function createEmergencyLogger()
159
    {
160
        return new Monolog('Midas', $this->prepareHandlers([new StreamHandler(
161
            \sys_get_temp_dir().'/midas/midas.log', $this->level(['level' => 'debug'])
162
        )]));
163
    }
164
165
    /**
166
     * Call a custom driver creator.
167
     *
168
     * @param array $config
169
     *
170
     * @return mixed
171
     */
172
    protected function callCustomCreator(array $config)
173
    {
174
        return $this->customCreators[$config['driver']]($this->app, $config);
175
    }
176
177
    /**
178
     * Create an aggregate log driver instance.
179
     *
180
     * @param array $config
181
     *
182
     * @return \Monolog\Logger
183
     */
184
    protected function createStackDriver(array $config)
185
    {
186
        $handlers = [];
187
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 said class. However, the method does not exist in Psr\Log\AbstractLogger or Psr\Log\NullLogger. Are you sure you never get one of those? ( 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'),
240
                Arr::get($config, 'username', 'Midas'),
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
                    'Midas', 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');
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']->get('log.default');
363
    }
364
365
    /**
366
     * Set the default log driver name.
367
     *
368
     * @param string $name
369
     */
370
    public function setDefaultDriver($name)
371
    {
372
        $this->app['config']['log.default'] = $name;
373
    }
374
375
    /**
376
     * Register a custom driver creator Closure.
377
     *
378
     * @param string   $driver
379
     * @param \Closure $callback
380
     *
381
     * @return $this
382
     */
383
    public function extend($driver, \Closure $callback)
384
    {
385
        $this->customCreators[$driver] = $callback->bindTo($this, $this);
386
387
        return $this;
388
    }
389
390
    /**
391
     * System is unusable.
392
     *
393
     * @param string $message
394
     * @param array  $context
395
     *
396
     * @return mixed
397
     */
398
    public function emergency($message, array $context = [])
399
    {
400
        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...
401
    }
402
403
    /**
404
     * Action must be taken immediately.
405
     *
406
     * Example: Entire website down, database unavailable, etc. This should
407
     * trigger the SMS alerts and wake you up.
408
     *
409
     * @param string $message
410
     * @param array  $context
411
     *
412
     * @return mixed
413
     */
414
    public function alert($message, array $context = [])
415
    {
416
        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...
417
    }
418
419
    /**
420
     * Critical conditions.
421
     *
422
     * Example: Application component unavailable, unexpected exception.
423
     *
424
     * @param string $message
425
     * @param array  $context
426
     *
427
     * @return mixed
428
     */
429
    public function critical($message, array $context = [])
430
    {
431
        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...
432
    }
433
434
    /**
435
     * Runtime errors that do not require immediate action but should typically
436
     * be logged and monitored.
437
     *
438
     * @param string $message
439
     * @param array  $context
440
     *
441
     * @return mixed
442
     */
443
    public function error($message, array $context = [])
444
    {
445
        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...
446
    }
447
448
    /**
449
     * Exceptional occurrences that are not errors.
450
     *
451
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
452
     * that are not necessarily wrong.
453
     *
454
     * @param string $message
455
     * @param array  $context
456
     *
457
     * @return mixed
458
     */
459
    public function warning($message, array $context = [])
460
    {
461
        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...
462
    }
463
464
    /**
465
     * Normal but significant events.
466
     *
467
     * @param string $message
468
     * @param array  $context
469
     *
470
     * @return mixed
471
     */
472
    public function notice($message, array $context = [])
473
    {
474
        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...
475
    }
476
477
    /**
478
     * Interesting events.
479
     *
480
     * Example: User logs in, SQL logs.
481
     *
482
     * @param string $message
483
     * @param array  $context
484
     *
485
     * @return mixed
486
     */
487
    public function info($message, array $context = [])
488
    {
489
        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...
490
    }
491
492
    /**
493
     * Detailed debug information.
494
     *
495
     * @param string $message
496
     * @param array  $context
497
     *
498
     * @return mixed
499
     */
500
    public function debug($message, array $context = [])
501
    {
502
        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...
503
    }
504
505
    /**
506
     * Logs with an arbitrary level.
507
     *
508
     * @param mixed  $level
509
     * @param string $message
510
     * @param array  $context
511
     *
512
     * @return mixed
513
     */
514
    public function log($level, $message, array $context = [])
515
    {
516
        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...
517
    }
518
519
    /**
520
     * Dynamically call the default driver instance.
521
     *
522
     * @param string $method
523
     * @param array  $parameters
524
     *
525
     * @return mixed
526
     */
527
    public function __call($method, $parameters)
528
    {
529
        return $this->driver()->$method(...$parameters);
530
    }
531
}
532