Passed
Pull Request — master (#77)
by Wilmer
02:14
created

Formatter::setTimestampFormat()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 1
b 0
f 0
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
    }
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
    }
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 6
    public function setTimestampFormat(string $timestampFormat): void
84
    {
85 6
        $this->timestampFormat = $timestampFormat;
86
    }
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 84
    public function format(Message $message, array $commonContext): string
99
    {
100 84
        if ($this->format === null) {
101 53
            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
                '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 53
    private function defaultFormat(Message $message, array $commonContext): string
125
    {
126 53
        $time = $this->getTime($message);
127 53
        $prefix = $this->getPrefix($message, $commonContext);
128 40
        $context = $this->getContext($message, $commonContext);
129 40
        $category = $message->context('category', CategoryFilter::DEFAULT);
130
131 40
        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 53
    private function getTime(Message $message): string
142
    {
143 53
        $timestamp = (string) $message->context('time', microtime(true));
144
145 53
        switch (true) {
146 53
            case strpos($timestamp, '.') !== false:
147 50
                $format = 'U.u';
148 50
                break;
149 4
            case strpos($timestamp, ',') !== false:
150 1
                $format = 'U,u';
151 1
                break;
152
            default:
153 3
                $format = 'U';
154 3
                break;
155
        }
156
157 53
        return DateTime::createFromFormat($format, $timestamp)->format($this->timestampFormat);
158
    }
159
160
    /**
161
     * Gets a string to be prefixed to the given message.
162
     *
163
     * If {@see Formatter::$prefix} is configured it will return the result of the callback.
164
     * The default implementation will return user IP, user ID and session ID as a prefix.
165
     *
166
     * @param Message $message The log message being exported.
167
     * @param array $commonContext The user parameters in the `key => value` format.
168
     *
169
     * @throws RuntimeException for a callable "prefix" that does not return a string.
170
     *
171
     * @return string The log prefix string.
172
     */
173 71
    private function getPrefix(Message $message, array $commonContext): string
174
    {
175 71
        if ($this->prefix === null) {
176 50
            return '';
177
        }
178
179 21
        $prefix = ($this->prefix)($message, $commonContext);
180
181 21
        if (!is_string($prefix)) {
182 13
            throw new RuntimeException(sprintf(
183
                'The PHP callable "prefix" must return a string, %s received.',
184 13
                gettype($prefix)
185
            ));
186
        }
187
188 8
        return $prefix;
189
    }
190
191
    /**
192
     * Gets the context information to be logged.
193
     *
194
     * @param Message $message The log message.
195
     * @param array $commonContext The user parameters in the `key => value` format.
196
     *
197
     * @return string The context information. If an empty string, it means no context information.
198
     */
199 40
    private function getContext(Message $message, array $commonContext): string
200
    {
201 40
        $trace = $this->getTrace($message);
202 40
        $context = [];
203 40
        $common = [];
204
205 40
        if ($trace !== '') {
206 2
            $context[] = $trace;
207
        }
208
209 40
        foreach ($message->context() as $name => $value) {
210 40
            if ($name !== 'trace') {
211 40
                $context[] = "{$name}: " . $this->convertToString($value);
212
            }
213
        }
214
215 40
        foreach ($commonContext as $name => $value) {
216 16
            $common[] = "{$name}: " . $this->convertToString($value);
217
        }
218
219 40
        return (empty($context) ? '' : "\n\nMessage context:\n\n" . implode("\n", $context))
220 40
            . (empty($common) ? '' : "\n\nCommon context:\n\n" . implode("\n", $common)) . "\n";
221
    }
222
223
    /**
224
     * Gets debug backtrace in string representation.
225
     *
226
     * @param Message $message The log message.
227
     *
228
     * @return string Debug backtrace in string representation.
229
     */
230 40
    private function getTrace(Message $message): string
231
    {
232 40
        $traces = (array) $message->context('trace', []);
233
234 40
        foreach ($traces as $key => $trace) {
235 2
            if (isset($trace['file'], $trace['line'])) {
236 2
                $traces[$key] = "in {$trace['file']}:{$trace['line']}";
237
            }
238
        }
239
240 40
        return empty($traces) ? '' : "trace:\n    " . implode("\n    ", $traces);
241
    }
242
243
    /**
244
     * Converts a value to a string.
245
     *
246
     * @param mixed $value The value to convert
247
     *
248
     * @return string Converted string.
249
     */
250 40
    private function convertToString($value): string
251
    {
252 40
        if (is_object($value) && method_exists($value, '__toString')) {
253 8
            return $value->__toString();
254
        }
255
256 40
        return VarDumper::create($value)->asString();
257
    }
258
}
259