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 
12 namespace Monolog\Formatter;
13 
14 use Exception;
15 use Monolog\Utils;
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) {
234  $this->throwEncodeError($code, $data);
235  }
236 
237  if (is_string($data)) {
238  $this->detectAndCleanUtf8($data);
239  } elseif (is_array($data)) {
240  array_walk_recursive($data, array($this, 'detectAndCleanUtf8'));
241  } else {
242  $this->throwEncodeError($code, $data);
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 }
throwEncodeError($code, $data)
Throws an exception according to a given code with a customized message.
static getClass($object)
Definition: Utils.php:19
detectAndCleanUtf8(&$data)
Detect invalid UTF-8 string characters and convert to valid UTF-8.
$code
Definition: example_050.php:99
$records
Definition: simple_test.php:22
formatBatch(array $records)
{Formats a set of log records.A set of records to format mixed The formatted set of records} ...
handleJsonError($code, $data)
Handle a json_encode failure.
Normalizes incoming records to remove objects/resources so it&#39;s easier to dump to various targets...
toJson($data, $ignoreErrors=false)
Return the JSON representation of a value.
format(array $record)
{Formats a log record.A record to format mixed The formatted record}
$key
Definition: croninfo.php:18
$data
Definition: bench.php:6