Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
100.00% |
23 / 23 |
|
91.67% |
22 / 24 |
|
61.11% |
11 / 18 |
|
83.33% |
5 / 6 |
CRAP | |
0.00% |
0 / 1 |
| Logger | |
100.00% |
23 / 23 |
|
91.67% |
22 / 24 |
|
61.11% |
11 / 18 |
|
100.00% |
6 / 6 |
20.47 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| formatter | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| withFormatter | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| log | |
100.00% |
11 / 11 |
|
81.82% |
9 / 11 |
|
42.86% |
3 / 7 |
|
100.00% |
1 / 1 |
4.68 | |||
| validateLevel | |
100.00% |
3 / 3 |
|
100.00% |
5 / 5 |
|
75.00% |
3 / 4 |
|
100.00% |
1 / 1 |
3.14 | |||
| printLevel | |
100.00% |
3 / 3 |
|
100.00% |
5 / 5 |
|
50.00% |
2 / 4 |
|
100.00% |
1 / 1 |
4.12 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Celemas\Log; |
| 6 | |
| 7 | use Celemas\Log\Formatter\TextFormatter; |
| 8 | use Override; |
| 9 | use Psr\Log\InvalidArgumentException; |
| 10 | use Psr\Log\LoggerInterface as PsrLogger; |
| 11 | use Psr\Log\LoggerTrait; |
| 12 | use Psr\Log\LogLevel; |
| 13 | use Stringable; |
| 14 | |
| 15 | /** @api */ |
| 16 | final class Logger implements PsrLogger |
| 17 | { |
| 18 | use LoggerTrait; |
| 19 | |
| 20 | public const string DEBUG = LogLevel::DEBUG; |
| 21 | public const string INFO = LogLevel::INFO; |
| 22 | public const string NOTICE = LogLevel::NOTICE; |
| 23 | public const string WARNING = LogLevel::WARNING; |
| 24 | public const string ERROR = LogLevel::ERROR; |
| 25 | public const string CRITICAL = LogLevel::CRITICAL; |
| 26 | public const string ALERT = LogLevel::ALERT; |
| 27 | public const string EMERGENCY = LogLevel::EMERGENCY; |
| 28 | |
| 29 | private const int ERROR_LOG_APPEND_TO_FILE = 3; |
| 30 | |
| 31 | /** @var array<string, positive-int> */ |
| 32 | private const array LEVEL_SEVERITY = [ |
| 33 | self::DEBUG => 100, |
| 34 | self::INFO => 200, |
| 35 | self::NOTICE => 300, |
| 36 | self::WARNING => 400, |
| 37 | self::ERROR => 500, |
| 38 | self::CRITICAL => 600, |
| 39 | self::ALERT => 700, |
| 40 | self::EMERGENCY => 800, |
| 41 | ]; |
| 42 | |
| 43 | /** @var array<string, non-empty-string> */ |
| 44 | private const array LEVEL_LABELS = [ |
| 45 | self::DEBUG => 'DEBUG', |
| 46 | self::INFO => 'INFO', |
| 47 | self::NOTICE => 'NOTICE', |
| 48 | self::WARNING => 'WARNING', |
| 49 | self::ERROR => 'ERROR', |
| 50 | self::CRITICAL => 'CRITICAL', |
| 51 | self::ALERT => 'ALERT', |
| 52 | self::EMERGENCY => 'EMERGENCY', |
| 53 | ]; |
| 54 | |
| 55 | protected Formatter $formatter; |
| 56 | |
| 57 | public function __construct( |
| 58 | protected ?string $file = null, |
| 59 | protected string $level = self::DEBUG, |
| 60 | ?Formatter $formatter = null, |
| 61 | ) { |
| 62 | $this->formatter = $formatter ?? new TextFormatter(); |
| 63 | $this->level = $this->validateLevel($level); |
| 64 | } |
| 65 | |
| 66 | public function formatter(Formatter $formatter): void |
| 67 | { |
| 68 | $this->formatter = $formatter; |
| 69 | } |
| 70 | |
| 71 | public function withFormatter(Formatter $formatter): self |
| 72 | { |
| 73 | $new = clone $this; |
| 74 | $new->formatter($formatter); |
| 75 | |
| 76 | return $new; |
| 77 | } |
| 78 | |
| 79 | #[Override] |
| 80 | public function log( |
| 81 | mixed $level, |
| 82 | string|Stringable $message, |
| 83 | array $context = [], |
| 84 | ): void { |
| 85 | $level = $this->validateLevel($level); |
| 86 | |
| 87 | if (self::LEVEL_SEVERITY[$level] < self::LEVEL_SEVERITY[$this->level]) { |
| 88 | return; |
| 89 | } |
| 90 | |
| 91 | $message = $this->formatter->format((string) $message, $context); |
| 92 | $message = str_replace("\0", '', $message); |
| 93 | $time = date(DATE_ATOM); |
| 94 | $line = "[{$time}] " . self::LEVEL_LABELS[$level] . ": {$message}"; |
| 95 | |
| 96 | if (is_string($this->file)) { |
| 97 | error_log($line . PHP_EOL, self::ERROR_LOG_APPEND_TO_FILE, $this->file); |
| 98 | |
| 99 | return; |
| 100 | } |
| 101 | |
| 102 | error_log(str_replace(["\r\n", "\r", "\n"], ' ', $line)); |
| 103 | } |
| 104 | |
| 105 | /** @return key-of<self::LEVEL_SEVERITY> */ |
| 106 | private function validateLevel(mixed $level): string |
| 107 | { |
| 108 | if (is_string($level) && array_key_exists($level, self::LEVEL_SEVERITY)) { |
| 109 | return $level; |
| 110 | } |
| 111 | |
| 112 | throw new InvalidArgumentException('Unknown log level: ' . $this->printLevel($level)); |
| 113 | } |
| 114 | |
| 115 | private function printLevel(mixed $level): string |
| 116 | { |
| 117 | if (is_scalar($level) || $level instanceof Stringable) { |
| 118 | return (string) $level; |
| 119 | } |
| 120 | |
| 121 | return get_debug_type($level); |
| 122 | } |
| 123 | } |
Below are the source code lines that represent each code path as identified by Xdebug. Please note a path is not
necessarily coterminous with a line, a line may contain multiple paths and therefore show up more than once.
Please also be aware that some paths may include implicit rather than explicit branches, e.g. an if statement
always has an else as part of its logical flow even if you didn't write one.
| 58 | protected ?string $file = null, |
| 59 | protected string $level = self::DEBUG, |
| 60 | ?Formatter $formatter = null, |
| 61 | ) { |
| 62 | $this->formatter = $formatter ?? new TextFormatter(); |
| 63 | $this->level = $this->validateLevel($level); |
| 64 | } |
| 66 | public function formatter(Formatter $formatter): void |
| 67 | { |
| 68 | $this->formatter = $formatter; |
| 69 | } |
| 81 | mixed $level, |
| 82 | string|Stringable $message, |
| 83 | array $context = [], |
| 84 | ): void { |
| 85 | $level = $this->validateLevel($level); |
| 86 | |
| 87 | if (self::LEVEL_SEVERITY[$level] < self::LEVEL_SEVERITY[$this->level]) { |
| 88 | return; |
| 81 | mixed $level, |
| 82 | string|Stringable $message, |
| 83 | array $context = [], |
| 84 | ): void { |
| 85 | $level = $this->validateLevel($level); |
| 86 | |
| 87 | if (self::LEVEL_SEVERITY[$level] < self::LEVEL_SEVERITY[$this->level]) { |
| 91 | $message = $this->formatter->format((string) $message, $context); |
| 92 | $message = str_replace("\0", '', $message); |
| 92 | $message = str_replace("\0", '', $message); |
| 92 | $message = str_replace("\0", '', $message); |
| 93 | $time = date(DATE_ATOM); |
| 94 | $line = "[{$time}] " . self::LEVEL_LABELS[$level] . ": {$message}"; |
| 95 | |
| 96 | if (is_string($this->file)) { |
| 97 | error_log($line . PHP_EOL, self::ERROR_LOG_APPEND_TO_FILE, $this->file); |
| 98 | |
| 99 | return; |
| 81 | mixed $level, |
| 82 | string|Stringable $message, |
| 83 | array $context = [], |
| 84 | ): void { |
| 85 | $level = $this->validateLevel($level); |
| 86 | |
| 87 | if (self::LEVEL_SEVERITY[$level] < self::LEVEL_SEVERITY[$this->level]) { |
| 91 | $message = $this->formatter->format((string) $message, $context); |
| 92 | $message = str_replace("\0", '', $message); |
| 92 | $message = str_replace("\0", '', $message); |
| 92 | $message = str_replace("\0", '', $message); |
| 93 | $time = date(DATE_ATOM); |
| 94 | $line = "[{$time}] " . self::LEVEL_LABELS[$level] . ": {$message}"; |
| 95 | |
| 96 | if (is_string($this->file)) { |
| 102 | error_log(str_replace(["\r\n", "\r", "\n"], ' ', $line)); |
| 102 | error_log(str_replace(["\r\n", "\r", "\n"], ' ', $line)); |
| 102 | error_log(str_replace(["\r\n", "\r", "\n"], ' ', $line)); |
| 103 | } |
| 81 | mixed $level, |
| 82 | string|Stringable $message, |
| 83 | array $context = [], |
| 84 | ): void { |
| 85 | $level = $this->validateLevel($level); |
| 86 | |
| 87 | if (self::LEVEL_SEVERITY[$level] < self::LEVEL_SEVERITY[$this->level]) { |
| 91 | $message = $this->formatter->format((string) $message, $context); |
| 92 | $message = str_replace("\0", '', $message); |
| 92 | $message = str_replace("\0", '', $message); |
| 92 | $message = str_replace("\0", '', $message); |
| 93 | $time = date(DATE_ATOM); |
| 94 | $line = "[{$time}] " . self::LEVEL_LABELS[$level] . ": {$message}"; |
| 95 | |
| 96 | if (is_string($this->file)) { |
| 102 | error_log(str_replace(["\r\n", "\r", "\n"], ' ', $line)); |
| 102 | error_log(str_replace(["\r\n", "\r", "\n"], ' ', $line)); |
| 102 | error_log(str_replace(["\r\n", "\r", "\n"], ' ', $line)); |
| 103 | } |
| 81 | mixed $level, |
| 82 | string|Stringable $message, |
| 83 | array $context = [], |
| 84 | ): void { |
| 85 | $level = $this->validateLevel($level); |
| 86 | |
| 87 | if (self::LEVEL_SEVERITY[$level] < self::LEVEL_SEVERITY[$this->level]) { |
| 91 | $message = $this->formatter->format((string) $message, $context); |
| 92 | $message = str_replace("\0", '', $message); |
| 92 | $message = str_replace("\0", '', $message); |
| 92 | $message = str_replace("\0", '', $message); |
| 93 | $time = date(DATE_ATOM); |
| 94 | $line = "[{$time}] " . self::LEVEL_LABELS[$level] . ": {$message}"; |
| 95 | |
| 96 | if (is_string($this->file)) { |
| 97 | error_log($line . PHP_EOL, self::ERROR_LOG_APPEND_TO_FILE, $this->file); |
| 98 | |
| 99 | return; |
| 81 | mixed $level, |
| 82 | string|Stringable $message, |
| 83 | array $context = [], |
| 84 | ): void { |
| 85 | $level = $this->validateLevel($level); |
| 86 | |
| 87 | if (self::LEVEL_SEVERITY[$level] < self::LEVEL_SEVERITY[$this->level]) { |
| 91 | $message = $this->formatter->format((string) $message, $context); |
| 92 | $message = str_replace("\0", '', $message); |
| 92 | $message = str_replace("\0", '', $message); |
| 92 | $message = str_replace("\0", '', $message); |
| 93 | $time = date(DATE_ATOM); |
| 94 | $line = "[{$time}] " . self::LEVEL_LABELS[$level] . ": {$message}"; |
| 95 | |
| 96 | if (is_string($this->file)) { |
| 102 | error_log(str_replace(["\r\n", "\r", "\n"], ' ', $line)); |
| 102 | error_log(str_replace(["\r\n", "\r", "\n"], ' ', $line)); |
| 102 | error_log(str_replace(["\r\n", "\r", "\n"], ' ', $line)); |
| 103 | } |
| 81 | mixed $level, |
| 82 | string|Stringable $message, |
| 83 | array $context = [], |
| 84 | ): void { |
| 85 | $level = $this->validateLevel($level); |
| 86 | |
| 87 | if (self::LEVEL_SEVERITY[$level] < self::LEVEL_SEVERITY[$this->level]) { |
| 91 | $message = $this->formatter->format((string) $message, $context); |
| 92 | $message = str_replace("\0", '', $message); |
| 92 | $message = str_replace("\0", '', $message); |
| 92 | $message = str_replace("\0", '', $message); |
| 93 | $time = date(DATE_ATOM); |
| 94 | $line = "[{$time}] " . self::LEVEL_LABELS[$level] . ": {$message}"; |
| 95 | |
| 96 | if (is_string($this->file)) { |
| 102 | error_log(str_replace(["\r\n", "\r", "\n"], ' ', $line)); |
| 102 | error_log(str_replace(["\r\n", "\r", "\n"], ' ', $line)); |
| 102 | error_log(str_replace(["\r\n", "\r", "\n"], ' ', $line)); |
| 103 | } |
| 115 | private function printLevel(mixed $level): string |
| 116 | { |
| 117 | if (is_scalar($level) || $level instanceof Stringable) { |
| 117 | if (is_scalar($level) || $level instanceof Stringable) { |
| 117 | if (is_scalar($level) || $level instanceof Stringable) { |
| 118 | return (string) $level; |
| 115 | private function printLevel(mixed $level): string |
| 116 | { |
| 117 | if (is_scalar($level) || $level instanceof Stringable) { |
| 117 | if (is_scalar($level) || $level instanceof Stringable) { |
| 117 | if (is_scalar($level) || $level instanceof Stringable) { |
| 121 | return get_debug_type($level); |
| 122 | } |
| 115 | private function printLevel(mixed $level): string |
| 116 | { |
| 117 | if (is_scalar($level) || $level instanceof Stringable) { |
| 117 | if (is_scalar($level) || $level instanceof Stringable) { |
| 118 | return (string) $level; |
| 115 | private function printLevel(mixed $level): string |
| 116 | { |
| 117 | if (is_scalar($level) || $level instanceof Stringable) { |
| 117 | if (is_scalar($level) || $level instanceof Stringable) { |
| 121 | return get_debug_type($level); |
| 122 | } |
| 106 | private function validateLevel(mixed $level): string |
| 107 | { |
| 108 | if (is_string($level) && array_key_exists($level, self::LEVEL_SEVERITY)) { |
| 108 | if (is_string($level) && array_key_exists($level, self::LEVEL_SEVERITY)) { |
| 108 | if (is_string($level) && array_key_exists($level, self::LEVEL_SEVERITY)) { |
| 109 | return $level; |
| 106 | private function validateLevel(mixed $level): string |
| 107 | { |
| 108 | if (is_string($level) && array_key_exists($level, self::LEVEL_SEVERITY)) { |
| 108 | if (is_string($level) && array_key_exists($level, self::LEVEL_SEVERITY)) { |
| 108 | if (is_string($level) && array_key_exists($level, self::LEVEL_SEVERITY)) { |
| 112 | throw new InvalidArgumentException('Unknown log level: ' . $this->printLevel($level)); |
| 113 | } |
| 106 | private function validateLevel(mixed $level): string |
| 107 | { |
| 108 | if (is_string($level) && array_key_exists($level, self::LEVEL_SEVERITY)) { |
| 108 | if (is_string($level) && array_key_exists($level, self::LEVEL_SEVERITY)) { |
| 109 | return $level; |
| 106 | private function validateLevel(mixed $level): string |
| 107 | { |
| 108 | if (is_string($level) && array_key_exists($level, self::LEVEL_SEVERITY)) { |
| 108 | if (is_string($level) && array_key_exists($level, self::LEVEL_SEVERITY)) { |
| 112 | throw new InvalidArgumentException('Unknown log level: ' . $this->printLevel($level)); |
| 113 | } |
| 71 | public function withFormatter(Formatter $formatter): self |
| 72 | { |
| 73 | $new = clone $this; |
| 74 | $new->formatter($formatter); |
| 75 | |
| 76 | return $new; |
| 77 | } |