Test Failed
Pull Request — master (#56)
by Evgeniy
02:21
created

Formatter::setFormat()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
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
    public function setFormat(callable $format): void
60
    {
61
        $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
    public function setPrefix(callable $prefix): void
72
    {
73
        $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
    public function setTimestampFormat(string $timestampFormat): void
84
    {
85
        $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
    public function format(Message $message, array $commonContext): string
99
    {
100
        if ($this->format === null) {
101
            return $this->defaultFormat($message, $commonContext);
102
        }
103
104
        $formatted = ($this->format)($message, $commonContext);
105
106
        if (!is_string($formatted)) {
107
            throw new RuntimeException(sprintf(
108
                'The PHP callable "format" must return a string, %s received.',
109
                gettype($formatted)
110
            ));
111
        }
112
113
        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
    private function defaultFormat(Message $message, array $commonContext): string
125
    {
126
        $time = $this->getTime($message);
127
        $prefix = $this->getPrefix($message, $commonContext);
128
        $context = $this->getContext($message, $commonContext);
129
        $category = $message->context('category', CategoryFilter::DEFAULT);
130
131
        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
    private function getTime(Message $message): string
142
    {
143
        $timestamp = (string) $message->context('time', microtime(true));
144
        $format = strpos($timestamp, '.') === false ? 'U' : 'U.u';
145
        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
    private function getPrefix(Message $message, array $commonContext): string
162
    {
163
        if ($this->prefix === null) {
164
            return '';
165
        }
166
167
        $prefix = ($this->prefix)($message, $commonContext);
168
169
        if (!is_string($prefix)) {
170
            throw new RuntimeException(sprintf(
171
                'The PHP callable "prefix" must return a string, %s received.',
172
                gettype($prefix)
173
            ));
174
        }
175
176
        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
    private function getContext(Message $message, array $commonContext): string
188
    {
189
        $trace = $this->getTrace($message);
190
        $context = [];
191
        $common = [];
192
193
        if ($trace !== '') {
194
            $context[] = $trace;
195
        }
196
197
        foreach ($message->context() as $name => $value) {
198
            if ($name !== 'trace') {
199
                $context[] = "{$name}: " . $this->convertToString($value);
200
            }
201
        }
202
203
        foreach ($commonContext as $name => $value) {
204
            $common[] = "{$name}: " . $this->convertToString($value);
205
        }
206
207
        return (empty($context) ? '' : "\n\nMessage context:\n\n" . implode("\n", $context))
208
            . (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
    private function getTrace(Message $message): string
219
    {
220
        $traces = (array) $message->context('trace', []);
221
222
        foreach ($traces as $key => $trace) {
223
            if (isset($trace['file'], $trace['line'])) {
224
                $traces[$key] = "in {$trace['file']}:{$trace['line']}";
225
            }
226
        }
227
228
        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
    private function convertToString($value): string
239
    {
240
        if (is_object($value) && method_exists($value, '__toString')) {
241
            return $value->__toString();
242
        }
243
244
        return VarDumper::create($value)->asString();
245
    }
246
}
247