Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
Logger
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
6 / 6
13
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 log
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 interpolate
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
5
 formatException
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 checkLevel
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createEntry
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Engelsystem\Logger;
6
7use Engelsystem\Models\LogEntry;
8use Psr\Log\AbstractLogger;
9use Psr\Log\InvalidArgumentException;
10use Psr\Log\LogLevel;
11use Stringable;
12use Throwable;
13
14class Logger extends AbstractLogger
15{
16    /** @var array<string> */
17    protected array $allowedLevels = [
18        LogLevel::ALERT,
19        LogLevel::CRITICAL,
20        LogLevel::DEBUG,
21        LogLevel::EMERGENCY,
22        LogLevel::ERROR,
23        LogLevel::INFO,
24        LogLevel::NOTICE,
25        LogLevel::WARNING,
26    ];
27
28    public function __construct(protected LogEntry $log)
29    {
30    }
31
32    /**
33     * Logs with an arbitrary level.
34     */
35    public function log(mixed $level, string|Stringable $message, array $context = []): void
36    {
37        if (!$this->checkLevel($level)) {
38            throw new InvalidArgumentException('Unknown log level: ' . $level);
39        }
40
41        $message = $this->interpolate($message, $context);
42
43        if (isset($context['exception']) && $context['exception'] instanceof Throwable) {
44            $message .= $this->formatException($context['exception']);
45        }
46
47        $this->createEntry(['level' => $level, 'message' => $message]);
48    }
49
50    /**
51     * Interpolates context values into the message placeholders.
52     */
53    protected function interpolate(string $message, array $context = []): string
54    {
55        foreach ($context as $key => $val) {
56            // check that the value can be casted to string
57            if (is_array($val) || (is_object($val) && !method_exists($val, '__toString'))) {
58                continue;
59            }
60
61            // replace the values of the message
62            $message = str_replace('{' . $key . '}', (string) $val, $message);
63        }
64
65        return $message;
66    }
67
68    protected function formatException(Throwable $e): string
69    {
70        return sprintf(
71            implode(PHP_EOL, ['', 'Exception: %s', 'File: %s:%u', 'Code: %s', 'Trace:', '%s']),
72            $e->getMessage(),
73            $e->getFile(),
74            $e->getLine(),
75            $e->getCode(),
76            $e->getTraceAsString()
77        );
78    }
79
80    protected function checkLevel(string $level): bool
81    {
82        return in_array($level, $this->allowedLevels);
83    }
84
85    protected function createEntry(array $data): void
86    {
87        $this->log->create($data);
88    }
89}