ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
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 
22 {
23  const SIMPLE_DATE = "Y-m-d H:i:s";
24 
25  protected $dateFormat;
26 
30  public function __construct($dateFormat = null)
31  {
32  $this->dateFormat = $dateFormat ?: static::SIMPLE_DATE;
33  if (!function_exists('json_encode')) {
34  throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s NormalizerFormatter');
35  }
36  }
37 
41  public function format(array $record)
42  {
43  return $this->normalize($record);
44  }
45 
49  public function formatBatch(array $records)
50  {
51  foreach ($records as $key => $record) {
52  $records[$key] = $this->format($record);
53  }
54 
55  return $records;
56  }
57 
58  protected function normalize($data)
59  {
60  if (null === $data || is_scalar($data)) {
61  if (is_float($data)) {
62  if (is_infinite($data)) {
63  return ($data > 0 ? '' : '-') . 'INF';
64  }
65  if (is_nan($data)) {
66  return 'NaN';
67  }
68  }
69 
70  return $data;
71  }
72 
73  if (is_array($data) || $data instanceof \Traversable) {
74  $normalized = array();
75 
76  $count = 1;
77  foreach ($data as $key => $value) {
78  if ($count++ >= 1000) {
79  $normalized['...'] = 'Over 1000 items, aborting normalization';
80  break;
81  }
82  $normalized[$key] = $this->normalize($value);
83  }
84 
85  return $normalized;
86  }
87 
88  if ($data instanceof \DateTime) {
89  return $data->format($this->dateFormat);
90  }
91 
92  if (is_object($data)) {
93  // TODO 2.0 only check for Throwable
94  if ($data instanceof Exception || (PHP_VERSION_ID > 70000 && $data instanceof \Throwable)) {
95  return $this->normalizeException($data);
96  }
97 
98  // non-serializable objects that implement __toString stringified
99  if (method_exists($data, '__toString') && !$data instanceof \JsonSerializable) {
100  $value = $data->__toString();
101  } else {
102  // the rest is json-serialized in some way
103  $value = $this->toJson($data, true);
104  }
105 
106  return sprintf("[object] (%s: %s)", get_class($data), $value);
107  }
108 
109  if (is_resource($data)) {
110  return sprintf('[resource] (%s)', get_resource_type($data));
111  }
112 
113  return '[unknown('.gettype($data).')]';
114  }
115 
116  protected function normalizeException($e)
117  {
118  // TODO 2.0 only check for Throwable
119  if (!$e instanceof Exception && !$e instanceof \Throwable) {
120  throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e));
121  }
122 
123  $data = array(
124  'class' => get_class($e),
125  'message' => $e->getMessage(),
126  'code' => $e->getCode(),
127  'file' => $e->getFile().':'.$e->getLine(),
128  );
129 
130  if ($e instanceof \SoapFault) {
131  if (isset($e->faultcode)) {
132  $data['faultcode'] = $e->faultcode;
133  }
134 
135  if (isset($e->faultactor)) {
136  $data['faultactor'] = $e->faultactor;
137  }
138 
139  if (isset($e->detail)) {
140  $data['detail'] = $e->detail;
141  }
142  }
143 
144  $trace = $e->getTrace();
145  foreach ($trace as $frame) {
146  if (isset($frame['file'])) {
147  $data['trace'][] = $frame['file'].':'.$frame['line'];
148  } elseif (isset($frame['function']) && $frame['function'] === '{closure}') {
149  // We should again normalize the frames, because it might contain invalid items
150  $data['trace'][] = $frame['function'];
151  } else {
152  // We should again normalize the frames, because it might contain invalid items
153  $data['trace'][] = $this->toJson($this->normalize($frame), true);
154  }
155  }
156 
157  if ($previous = $e->getPrevious()) {
158  $data['previous'] = $this->normalizeException($previous);
159  }
160 
161  return $data;
162  }
163 
172  protected function toJson($data, $ignoreErrors = false)
173  {
174  // suppress json_encode errors since it's twitchy with some inputs
175  if ($ignoreErrors) {
176  return @$this->jsonEncode($data);
177  }
178 
179  $json = $this->jsonEncode($data);
180 
181  if ($json === false) {
182  $json = $this->handleJsonError(json_last_error(), $data);
183  }
184 
185  return $json;
186  }
187 
192  private function jsonEncode($data)
193  {
194  if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
195  return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
196  }
197 
198  return json_encode($data);
199  }
200 
214  private function handleJsonError($code, $data)
215  {
216  if ($code !== JSON_ERROR_UTF8) {
217  $this->throwEncodeError($code, $data);
218  }
219 
220  if (is_string($data)) {
221  $this->detectAndCleanUtf8($data);
222  } elseif (is_array($data)) {
223  array_walk_recursive($data, array($this, 'detectAndCleanUtf8'));
224  } else {
225  $this->throwEncodeError($code, $data);
226  }
227 
228  $json = $this->jsonEncode($data);
229 
230  if ($json === false) {
231  $this->throwEncodeError(json_last_error(), $data);
232  }
233 
234  return $json;
235  }
236 
244  private function throwEncodeError($code, $data)
245  {
246  switch ($code) {
247  case JSON_ERROR_DEPTH:
248  $msg = 'Maximum stack depth exceeded';
249  break;
250  case JSON_ERROR_STATE_MISMATCH:
251  $msg = 'Underflow or the modes mismatch';
252  break;
253  case JSON_ERROR_CTRL_CHAR:
254  $msg = 'Unexpected control character found';
255  break;
256  case JSON_ERROR_UTF8:
257  $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
258  break;
259  default:
260  $msg = 'Unknown error';
261  }
262 
263  throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true));
264  }
265 
282  public function detectAndCleanUtf8(&$data)
283  {
284  if (is_string($data) && !preg_match('//u', $data)) {
285  $data = preg_replace_callback(
286  '/[\x80-\xFF]+/',
287  function ($m) { return utf8_encode($m[0]); },
288  $data
289  );
290  $data = str_replace(
291  array('¤', '¦', '¨', '´', '¸', '¼', '½', '¾'),
292  array('€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'),
293  $data
294  );
295  }
296  }
297 }
throwEncodeError($code, $data)
Throws an exception according to a given code with a customized message.
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...
Create styles array
The data for the language used.
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}