Lines
92.98%
53 / 57
Methods
80.00%
12 / 15
Classes
0.00%
0 / 1
| Name | Lines | Methods | CRAP | ||||
|---|---|---|---|---|---|---|---|
| __construct | 100.00% | 6 / 6 | 100.00% | 1 / 1 | 2 | ||
| getName | 0.00% | 0 / 1 | 0.00% | 0 / 1 | 2 | ||
| highlightLine | 83.33% | 5 / 6 | 0.00% | 0 / 1 | 4.07 | ||
| formatMessage | 100.00% | 20 / 20 | 100.00% | 1 / 1 | 2 | ||
| write | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| interpolate | 100.00% | 9 / 9 | 100.00% | 1 / 1 | 7 | ||
| log | 66.66% | 4 / 6 | 0.00% | 0 / 1 | 10.37 | ||
| emergency | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| alert | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| critical | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| error | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| warning | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| notice | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| info | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| debug | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| 18 | class Channel implements LoggerInterface | |
| 19 | { | |
| 20 | private string $channel; | |
| 21 | private Driver $driver; | |
| 22 | private bool $useColors; | |
| 23 | ||
| 24 | private const VALID_LEVELS = [ | |
| 25 | LogLevel::EMERGENCY, | |
| 26 | LogLevel::ALERT, | |
| 27 | LogLevel::CRITICAL, | |
| 28 | LogLevel::ERROR, | |
| 29 | LogLevel::WARNING, | |
| 30 | LogLevel::NOTICE, | |
| 31 | LogLevel::INFO, | |
| 32 | LogLevel::DEBUG, | |
| 33 | ]; | |
| 34 | ||
| 35 | // Simplified color scheme to start with | |
| 36 | private array $levelColors = [ | |
| 37 | 'emergency' => "\033[1;37;41m", // Bold white on red | |
| 38 | 'alert' => "\033[1;31m", // Bold red | |
| 39 | 'critical' => "\033[0;31m", // Red | |
| 40 | 'error' => "\033[0;31m", // Red | |
| 41 | 'warning' => "\033[0;33m", // Yellow | |
| 42 | 'notice' => "\033[0;36m", // Cyan | |
| 43 | 'info' => "\033[0;32m", // Green | |
| 44 | 'debug' => "\033[0;37m" // White | |
| 45 | ]; | |
| 46 | ||
| 47 | /** | |
| 48 | * A list of highlighter's. | |
| 49 | * @var Highlighter[] | |
| 50 | */ | |
| 51 | private array $highlighters; | |
| 52 | ||
| 53 | public function __construct(string $channel, Driver $driver, bool $useColors = true) | |
| 54 | { | |
| 55 | $this->channel = $channel; | |
| 56 | $this->driver = $driver; | |
| 57 | $this->useColors = $useColors && PHP_SAPI === 'cli'; | |
| 58 | $this->highlighters = [ | |
| 59 | new SqlHighlighter(), | |
| 60 | ]; | |
| 61 | } | |
| 62 | ||
| 63 | /** | |
| 64 | * The channel's name, used both in the formatted log output and as the | |
| 65 | * lookup key when registered via Application::addLoggingChannel(). | |
| 66 | */ | |
| 67 | public function getName(): string | |
| 68 | { | |
| 69 | return $this->channel; | |
| 70 | } | |
| 71 | ||
| 72 | private function highlightLine(string $line): string | |
| 73 | { | |
| 74 | foreach ($this->highlighters as $highlighter) { | |
| 75 | if (!($highlighter instanceof Highlighter)) { | |
| 76 | continue; | |
| 77 | } | |
| 78 | ||
| 79 | if ($highlighter->shouldHighlight($line, $line)) { | |
| 80 | $line = $highlighter->highlight($line, $line); | |
| 81 | } | |
| 82 | } | |
| 83 | ||
| 84 | return $line; | |
| 85 | } | |
| 86 | ||
| 87 | private function formatMessage(string $level, string $message): string | |
| 88 | { | |
| 89 | $timestamp = date('Y-m-d H:i:s'); | |
| 90 | $levelUpper = strtoupper($level); | |
| 91 | ||
| 92 | if ($this->useColors) { | |
| 93 | $levelColor = $this->levelColors[$level] ?? "\033[0m"; | |
| 94 | $formattedMessage = $this->highlightLine($message); | |
| 95 | return sprintf( | |
| 96 | "[%s] %s%s\033[0m | %s | %s\n", | |
| 97 | $timestamp, | |
| 98 | $levelColor, | |
| 99 | $levelUpper, | |
| 100 | $this->channel, | |
| 101 | $formattedMessage | |
| 102 | ); | |
| 103 | } | |
| 104 | ||
| 105 | return sprintf( | |
| 106 | "[%s] %s | %s | %s\n", | |
| 107 | $timestamp, | |
| 108 | $levelUpper, | |
| 109 | $this->channel, | |
| 110 | $message | |
| 111 | ); | |
| 112 | } | |
| 113 | ||
| 114 | private function write(string $level, string $message): void | |
| 115 | { | |
| 116 | $this->driver->write($this->formatMessage($level, $message)); | |
| 117 | } | |
| 118 | ||
| 119 | /** | |
| 120 | * Interpolate context values into {placeholder} tokens. | |
| 121 | * | |
| 122 | * Follows the PSR-3 spec recommendation: a value whose key matches a | |
| 123 | * placeholder is stringified (if stringable); otherwise the placeholder | |
| 124 | * is left untouched. The "exception" key is never interpolated, so | |
| 125 | * exception stack traces are not inlined into the message line. | |
| 126 | */ | |
| 127 | private function interpolate(string|\Stringable $message, array $context): string | |
| 128 | { | |
| 129 | if ($context === []) { | |
| 130 | return (string) $message; | |
| 131 | } | |
| 132 | ||
| 133 | $replace = []; | |
| 134 | foreach ($context as $key => $value) { | |
| 135 | if ($key === 'exception') { | |
| 136 | continue; | |
| 137 | } | |
| 138 | ||
| 139 | if (is_string($value) || is_scalar($value) || $value instanceof \Stringable) { | |
| 140 | $replace['{' . $key . '}'] = (string) $value; | |
| 141 | } | |
| 142 | } | |
| 143 | ||
| 144 | return strtr((string) $message, $replace); | |
| 145 | } | |
| 146 | ||
| 147 | /** | |
| 148 | * Route a log call to the matching level-specific method. | |
| 149 | * | |
| 150 | * PSR-3 requires `log()` to behave identically to the level-specific | |
| 151 | * method for known levels, and to throw an InvalidArgumentException for | |
| 152 | * unknown levels. | |
| 153 | */ | |
| 154 | public function log($level, string|\Stringable $message, array $context = []): void | |
| 155 | { | |
| 156 | if (!in_array($level, self::VALID_LEVELS, true)) { | |
| 157 | $levelLabel = match (true) { | |
| 158 | is_string($level) || is_int($level) || is_float($level) || is_bool($level) => (string) $level, | |
| 159 | $level instanceof \Stringable => (string) $level, | |
| 160 | default => gettype($level), | |
| 161 | }; | |
| 162 | throw new InvalidArgumentException("Unknown log level: {$levelLabel}"); | |
| 163 | } | |
| 164 | ||
| 165 | $this->write($level, $this->interpolate($message, $context)); | |
| 166 | } | |
| 167 | ||
| 168 | // PSR-3 log levels | |
| 169 | public function emergency(string|\Stringable $message, array $context = []): void | |
| 170 | { | |
| 171 | $this->log(LogLevel::EMERGENCY, $message, $context); | |
| 172 | } | |
| 173 | ||
| 174 | public function alert(string|\Stringable $message, array $context = []): void | |
| 175 | { | |
| 176 | $this->log(LogLevel::ALERT, $message, $context); | |
| 177 | } | |
| 178 | ||
| 179 | public function critical(string|\Stringable $message, array $context = []): void | |
| 180 | { | |
| 181 | $this->log(LogLevel::CRITICAL, $message, $context); | |
| 182 | } | |
| 183 | ||
| 184 | public function error(string|\Stringable $message, array $context = []): void | |
| 185 | { | |
| 186 | $this->log(LogLevel::ERROR, $message, $context); | |
| 187 | } | |
| 188 | ||
| 189 | public function warning(string|\Stringable $message, array $context = []): void | |
| 190 | { | |
| 191 | $this->log(LogLevel::WARNING, $message, $context); | |
| 192 | } | |
| 193 | ||
| 194 | public function notice(string|\Stringable $message, array $context = []): void | |
| 195 | { | |
| 196 | $this->log(LogLevel::NOTICE, $message, $context); | |
| 197 | } | |
| 198 | ||
| 199 | public function info(string|\Stringable $message, array $context = []): void | |
| 200 | { | |
| 201 | $this->log(LogLevel::INFO, $message, $context); | |
| 202 | } | |
| 203 | ||
| 204 | public function debug(string|\Stringable $message, array $context = []): void | |
| 205 | { | |
| 206 | $this->log(LogLevel::DEBUG, $message, $context); | |
| 207 | } | |
| 208 | } |