ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
NormalizerFormatter.php
Go to the documentation of this file.
1<?php
2
3/*
4 * This file is part of the Monolog package.
5 *
6 * (c) Jordi Boggiano <j.boggiano@seld.be>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Monolog\Formatter;
13
14use Exception;
16
23{
24 const SIMPLE_DATE = "Y-m-d H:i:s";
25
26 protected $dateFormat;
27
31 public function __construct($dateFormat = null)
32 {
33 $this->dateFormat = $dateFormat ?: static::SIMPLE_DATE;
34 if (!function_exists('json_encode')) {
35 throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s NormalizerFormatter');
36 }
37 }
38
42 public function format(array $record)
43 {
44 return $this->normalize($record);
45 }
46
50 public function formatBatch(array $records)
51 {
52 foreach ($records as $key => $record) {
53 $records[$key] = $this->format($record);
54 }
55
56 return $records;
57 }
58
59 protected function normalize($data, $depth = 0)
60 {
61 if ($depth > 9) {
62 return 'Over 9 levels deep, aborting normalization';
63 }
64
65 if (null === $data || is_scalar($data)) {
66 if (is_float($data)) {
67 if (is_infinite($data)) {
68 return ($data > 0 ? '' : '-') . 'INF';
69 }
70 if (is_nan($data)) {
71 return 'NaN';
72 }
73 }
74
75 return $data;
76 }
77
78 if (is_array($data)) {
79 $normalized = array();
80
81 $count = 1;
82 foreach ($data as $key => $value) {
83 if ($count++ > 1000) {
84 $normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization';
85 break;
86 }
87
88 $normalized[$key] = $this->normalize($value, $depth+1);
89 }
90
91 return $normalized;
92 }
93
94 if ($data instanceof \DateTime) {
95 return $data->format($this->dateFormat);
96 }
97
98 if (is_object($data)) {
99 // TODO 2.0 only check for Throwable
100 if ($data instanceof Exception || (PHP_VERSION_ID > 70000 && $data instanceof \Throwable)) {
101 return $this->normalizeException($data);
102 }
103
104 // non-serializable objects that implement __toString stringified
105 if (method_exists($data, '__toString') && !$data instanceof \JsonSerializable) {
106 $value = $data->__toString();
107 } else {
108 // the rest is json-serialized in some way
109 $value = $this->toJson($data, true);
110 }
111
112 return sprintf("[object] (%s: %s)", Utils::getClass($data), $value);
113 }
114
115 if (is_resource($data)) {
116 return sprintf('[resource] (%s)', get_resource_type($data));
117 }
118
119 return '[unknown('.gettype($data).')]';
120 }
121
122 protected function normalizeException($e)
123 {
124 // TODO 2.0 only check for Throwable
125 if (!$e instanceof Exception && !$e instanceof \Throwable) {
126 throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.Utils::getClass($e));
127 }
128
129 $data = array(
130 'class' => Utils::getClass($e),
131 'message' => $e->getMessage(),
132 'code' => $e->getCode(),
133 'file' => $e->getFile().':'.$e->getLine(),
134 );
135
136 if ($e instanceof \SoapFault) {
137 if (isset($e->faultcode)) {
138 $data['faultcode'] = $e->faultcode;
139 }
140
141 if (isset($e->faultactor)) {
142 $data['faultactor'] = $e->faultactor;
143 }
144
145 if (isset($e->detail)) {
146 $data['detail'] = $e->detail;
147 }
148 }
149
150 $trace = $e->getTrace();
151 foreach ($trace as $frame) {
152 if (isset($frame['file'])) {
153 $data['trace'][] = $frame['file'].':'.$frame['line'];
154 } elseif (isset($frame['function']) && $frame['function'] === '{closure}') {
155 // Simplify closures handling
156 $data['trace'][] = $frame['function'];
157 } else {
158 if (isset($frame['args'])) {
159 // Make sure that objects present as arguments are not serialized nicely but rather only
160 // as a class name to avoid any unexpected leak of sensitive information
161 $frame['args'] = array_map(function ($arg) {
162 if (is_object($arg) && !($arg instanceof \DateTime || $arg instanceof \DateTimeInterface)) {
163 return sprintf("[object] (%s)", Utils::getClass($arg));
164 }
165
166 return $arg;
167 }, $frame['args']);
168 }
169 // We should again normalize the frames, because it might contain invalid items
170 $data['trace'][] = $this->toJson($this->normalize($frame), true);
171 }
172 }
173
174 if ($previous = $e->getPrevious()) {
175 $data['previous'] = $this->normalizeException($previous);
176 }
177
178 return $data;
179 }
180
189 protected function toJson($data, $ignoreErrors = false)
190 {
191 // suppress json_encode errors since it's twitchy with some inputs
192 if ($ignoreErrors) {
193 return @$this->jsonEncode($data);
194 }
195
196 $json = $this->jsonEncode($data);
197
198 if ($json === false) {
199 $json = $this->handleJsonError(json_last_error(), $data);
200 }
201
202 return $json;
203 }
204
209 private function jsonEncode($data)
210 {
211 if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
212 return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
213 }
214
215 return json_encode($data);
216 }
217
231 private function handleJsonError($code, $data)
232 {
233 if ($code !== JSON_ERROR_UTF8) {
235 }
236
237 if (is_string($data)) {
239 } elseif (is_array($data)) {
240 array_walk_recursive($data, array($this, 'detectAndCleanUtf8'));
241 } else {
243 }
244
245 $json = $this->jsonEncode($data);
246
247 if ($json === false) {
248 $this->throwEncodeError(json_last_error(), $data);
249 }
250
251 return $json;
252 }
253
261 private function throwEncodeError($code, $data)
262 {
263 switch ($code) {
264 case JSON_ERROR_DEPTH:
265 $msg = 'Maximum stack depth exceeded';
266 break;
267 case JSON_ERROR_STATE_MISMATCH:
268 $msg = 'Underflow or the modes mismatch';
269 break;
270 case JSON_ERROR_CTRL_CHAR:
271 $msg = 'Unexpected control character found';
272 break;
273 case JSON_ERROR_UTF8:
274 $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
275 break;
276 default:
277 $msg = 'Unknown error';
278 }
279
280 throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true));
281 }
282
299 public function detectAndCleanUtf8(&$data)
300 {
301 if (is_string($data) && !preg_match('//u', $data)) {
302 $data = preg_replace_callback(
303 '/[\x80-\xFF]+/',
304 function ($m) { return utf8_encode($m[0]); },
305 $data
306 );
307 $data = str_replace(
308 array('¤', '¦', '¨', '´', '¸', '¼', '½', '¾'),
309 array('€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'),
310 $data
311 );
312 }
313 }
314}
An exception for terminatinating execution or to throw for unit testing.
Normalizes incoming records to remove objects/resources so it's easier to dump to various targets.
format(array $record)
{Formats a log record.mixed The formatted record}
detectAndCleanUtf8(&$data)
Detect invalid UTF-8 string characters and convert to valid UTF-8.
formatBatch(array $records)
{Formats a set of log records.mixed The formatted set of records}
toJson($data, $ignoreErrors=false)
Return the JSON representation of a value.
throwEncodeError($code, $data)
Throws an exception according to a given code with a customized message.
handleJsonError($code, $data)
Handle a json_encode failure.
static getClass($object)
Definition: Utils.php:19
$key
Definition: croninfo.php:18
$code
Definition: example_050.php:99
$records
Definition: simple_test.php:22
$data
Definition: bench.php:6