Passed
Push — master ( dc96af...1f8a30 )
by Alexander
02:08
created

Formatter   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 219
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 50
c 1
b 0
f 0
dl 0
loc 219
ccs 60
cts 60
cp 1
rs 10
wmc 26

10 Methods

Rating   Name   Duplication   Size   Complexity  
A getTrace() 0 11 4
A setFormat() 0 3 1
A format() 0 16 3
A getPrefix() 0 16 3
A getTime() 0 5 2
A setTimestampFormat() 0 3 1
A setPrefix() 0 3 1
B getContext() 0 22 7
A defaultFormat() 0 8 1
A convertToString() 0 7 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Log\Message;
6
7
use DateTime;
8
use RuntimeException;
9
use Yiisoft\Log\Message;
10
use Yiisoft\VarDumper\VarDumper;
11
12
use function gettype;
13
use function implode;
14
use function is_string;
15
use function is_object;
16
use function method_exists;
17
use function microtime;
18
use function sprintf;
19
use function strpos;
20
21
/**
22
 * Formatter formats log messages.
23
 *
24
 * @internal
25
 */
26
final class Formatter
27
{
28
    /**
29
     * @var callable|null PHP callable that returns a string representation of the log message.
30
     *
31
     * If not set, {@see Formatter::defaultFormat()} will be used.
32
     *
33
     * The signature of the callable should be `function (Message $message, array $commonContext): string;`.
34
     */
35
    private $format;
36
37
    /**
38
     * @var callable|null PHP callable that returns a string to be prefixed to every exported message.
39
     *
40
     * If not set, {@see Formatter::getPrefix()} will be used, which prefixes
41
     * the message with context information such as user IP, user ID and session ID.
42
     *
43
     * The signature of the callable should be `function (Message $message, array $commonContext): string;`.
44
     */
45
    private $prefix;
46
47
    /**
48
     * @var string The date format for the log timestamp. Defaults to `Y-m-d H:i:s.u`.
49
     */
50
    private string $timestampFormat = 'Y-m-d H:i:s.u';
51
52
    /**
53
     * Sets the format for the string representation of the log message.
54
     *
55
     * @param callable $format The PHP callable to get a string representation of the log message.
56
     *
57
     * @see Formatter::$format
58
     */
59 35
    public function setFormat(callable $format): void
60
    {
61 35
        $this->format = $format;
62 35
    }
63
64
    /**
65
     * Sets a PHP callable that returns a string to be prefixed to every exported message.
66
     *
67
     * @param callable $prefix The PHP callable to get a string prefix of the log message.
68
     *
69
     * @see Formatter::$prefix
70
     */
71 21
    public function setPrefix(callable $prefix): void
72
    {
73 21
        $this->prefix = $prefix;
74 21
    }
75
76
    /**
77
     * Sets a date format for the log timestamp.
78
     *
79
     * @param string $timestampFormat The date format for the log timestamp.
80
     *
81
     * @see Formatter::$timestampFormat
82
     */
83 5
    public function setTimestampFormat(string $timestampFormat): void
84
    {
85 5
        $this->timestampFormat = $timestampFormat;
86 5
    }
87
88
    /**
89
     * Formats a log message for display as a string.
90
     *
91
     * @param Message $message The log message to be formatted.
92
     * @param array $commonContext The user parameters in the `key => value` format.
93
     *
94
     * @throws RuntimeException for a callable "format" that does not return a string.
95
     *
96
     * @return string The formatted log message.
97
     */
98 83
    public function format(Message $message, array $commonContext): string
99
    {
100 83
        if ($this->format === null) {
101 52
            return $this->defaultFormat($message, $commonContext);
102
        }
103
104 31
        $formatted = ($this->format)($message, $commonContext);
105
106 31
        if (!is_string($formatted)) {
107 13
            throw new RuntimeException(sprintf(
108 13
                'The PHP callable "format" must return a string, %s received.',
109 13
                gettype($formatted)
110
            ));
111
        }
112
113 18
        return $this->getPrefix($message, $commonContext) . $formatted;
114
    }
115
116
    /**
117
     * Default formats a log message for display as a string.
118
     *
119
     * @param Message $message The log message to be default formatted.
120
     * @param array $commonContext The user parameters in the `key => value` format.
121
     *
122
     * @return string The default formatted log message.
123
     */
124 52
    private function defaultFormat(Message $message, array $commonContext): string
125
    {
126 52
        $time = $this->getTime($message);
127 52
        $prefix = $this->getPrefix($message, $commonContext);
128 39
        $context = $this->getContext($message, $commonContext);
129 39
        $category = $message->context('category', CategoryFilter::DEFAULT);
130
131 39
        return "{$time} {$prefix}[{$message->level()}][{$category}] {$message->message()}{$context}";
132
    }
133
134
    /**
135
     * Gets formatted timestamp for message, according to {@see Formatter::$timestampFormat}.
136
     *
137
     * @param Message $message The log message.
138
     *
139
     * @return string Formatted timestamp for message.
140
     */
141 52
    private function getTime(Message $message): string
142
    {
143 52
        $timestamp = (string) $message->context('time', microtime(true));
144 52
        $format = strpos($timestamp, '.') === false ? 'U' : 'U.u';
145 52
        return DateTime::createFromFormat($format, $timestamp)->format($this->timestampFormat);
146
    }
147
148
    /**
149
     * Gets a string to be prefixed to the given message.
150
     *
151
     * If {@see Formatter::$prefix} is configured it will return the result of the callback.
152
     * The default implementation will return user IP, user ID and session ID as a prefix.
153
     *
154
     * @param Message $message The log message being exported.
155
     * @param array $commonContext The user parameters in the `key => value` format.
156
     *
157
     * @throws RuntimeException for a callable "prefix" that does not return a string.
158
     *
159
     * @return string The log prefix string.
160
     */
161 70
    private function getPrefix(Message $message, array $commonContext): string
162
    {
163 70
        if ($this->prefix === null) {
164 49
            return '';
165
        }
166
167 21
        $prefix = ($this->prefix)($message, $commonContext);
168
169 21
        if (!is_string($prefix)) {
170 13
            throw new RuntimeException(sprintf(
171 13
                'The PHP callable "prefix" must return a string, %s received.',
172 13
                gettype($prefix)
173
            ));
174
        }
175
176 8
        return $prefix;
177
    }
178
179
    /**
180
     * Gets the context information to be logged.
181
     *
182
     * @param Message $message The log message.
183
     * @param array $commonContext The user parameters in the `key => value` format.
184
     *
185
     * @return string The context information. If an empty string, it means no context information.
186
     */
187 39
    private function getContext(Message $message, array $commonContext): string
188
    {
189 39
        $trace = $this->getTrace($message);
190 39
        $context = [];
191 39
        $common = [];
192
193 39
        if ($trace !== '') {
194 2
            $context[] = $trace;
195
        }
196
197 39
        foreach ($message->context() as $name => $value) {
198 39
            if ($name !== 'trace') {
199 39
                $context[] = "{$name}: " . $this->convertToString($value);
200
            }
201
        }
202
203 39
        foreach ($commonContext as $name => $value) {
204 16
            $common[] = "{$name}: " . $this->convertToString($value);
205
        }
206
207 39
        return (empty($context) ? '' : "\n\nMessage context:\n\n" . implode("\n", $context))
208 39
            . (empty($common) ? '' : "\n\nCommon context:\n\n" . implode("\n", $common)) . "\n";
209
    }
210
211
    /**
212
     * Gets debug backtrace in string representation.
213
     *
214
     * @param Message $message The log message.
215
     *
216
     * @return string Debug backtrace in string representation.
217
     */
218 39
    private function getTrace(Message $message): string
219
    {
220 39
        $traces = (array) $message->context('trace', []);
221
222 39
        foreach ($traces as $key => $trace) {
223 2
            if (isset($trace['file'], $trace['line'])) {
224 2
                $traces[$key] = "in {$trace['file']}:{$trace['line']}";
225
            }
226
        }
227
228 39
        return empty($traces) ? '' : "trace:\n    " . implode("\n    ", $traces);
229
    }
230
231
    /**
232
     * Converts a value to a string.
233
     *
234
     * @param mixed $value The value to convert
235
     *
236
     * @return string Converted string.
237
     */
238 39
    private function convertToString($value): string
239
    {
240 39
        if (is_object($value) && method_exists($value, '__toString')) {
241 8
            return $value->__toString();
242
        }
243
244 39
        return VarDumper::create($value)->asString();
245
    }
246
}
247