Passed
Pull Request — master (#56)
by Evgeniy
01:49
created

Formatter   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 219
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

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

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