Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
23 / 23
91.67% covered (success)
91.67%
22 / 24
61.11% covered (warning)
61.11%
11 / 18
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Logger
100.00% covered (success)
100.00%
23 / 23
91.67% covered (success)
91.67%
22 / 24
61.11% covered (warning)
61.11%
11 / 18
100.00% covered (success)
100.00%
6 / 6
20.47
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 formatter
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withFormatter
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 log
100.00% covered (success)
100.00%
11 / 11
81.82% covered (warning)
81.82%
9 / 11
42.86% covered (danger)
42.86%
3 / 7
100.00% covered (success)
100.00%
1 / 1
4.68
 validateLevel
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
5 / 5
75.00% covered (warning)
75.00%
3 / 4
100.00% covered (success)
100.00%
1 / 1
3.14
 printLevel
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
5 / 5
50.00% covered (danger)
50.00%
2 / 4
100.00% covered (success)
100.00%
1 / 1
4.12
1<?php
2
3declare(strict_types=1);
4
5namespace Celemas\Log;
6
7use Celemas\Log\Formatter\TextFormatter;
8use Override;
9use Psr\Log\InvalidArgumentException;
10use Psr\Log\LoggerInterface as PsrLogger;
11use Psr\Log\LoggerTrait;
12use Psr\Log\LogLevel;
13use Stringable;
14
15/** @api */
16final 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}

Paths

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.

Logger->__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    }
Logger->formatter
66    public function formatter(Formatter $formatter): void
67    {
68        $this->formatter = $formatter;
69    }
Logger->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;
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    }
Logger->printLevel
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    }
Logger->validateLevel
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    }
Logger->withFormatter
71    public function withFormatter(Formatter $formatter): self
72    {
73        $new = clone $this;
74        $new->formatter($formatter);
75
76        return $new;
77    }