JsonFormatter.php 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. <?php
  2. /*
  3. * This file is part of the Monolog package.
  4. *
  5. * (c) Jordi Boggiano <j.boggiano@seld.be>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Monolog\Formatter;
  11. use Exception;
  12. use Throwable;
  13. /**
  14. * Encodes whatever record data is passed to it as json
  15. *
  16. * This can be useful to log to databases or remote APIs
  17. *
  18. * @author Jordi Boggiano <j.boggiano@seld.be>
  19. */
  20. class JsonFormatter extends NormalizerFormatter
  21. {
  22. const BATCH_MODE_JSON = 1;
  23. const BATCH_MODE_NEWLINES = 2;
  24. protected $batchMode;
  25. protected $appendNewline;
  26. /**
  27. * @var bool
  28. */
  29. protected $includeStacktraces = false;
  30. /**
  31. * @param int $batchMode
  32. * @param bool $appendNewline
  33. */
  34. public function __construct($batchMode = self::BATCH_MODE_JSON, $appendNewline = true)
  35. {
  36. $this->batchMode = $batchMode;
  37. $this->appendNewline = $appendNewline;
  38. }
  39. /**
  40. * The batch mode option configures the formatting style for
  41. * multiple records. By default, multiple records will be
  42. * formatted as a JSON-encoded array. However, for
  43. * compatibility with some API endpoints, alternative styles
  44. * are available.
  45. *
  46. * @return int
  47. */
  48. public function getBatchMode()
  49. {
  50. return $this->batchMode;
  51. }
  52. /**
  53. * True if newlines are appended to every formatted record
  54. *
  55. * @return bool
  56. */
  57. public function isAppendingNewlines()
  58. {
  59. return $this->appendNewline;
  60. }
  61. /**
  62. * {@inheritdoc}
  63. */
  64. public function format(array $record)
  65. {
  66. return $this->toJson($this->normalize($record), true) . ($this->appendNewline ? "\n" : '');
  67. }
  68. /**
  69. * {@inheritdoc}
  70. */
  71. public function formatBatch(array $records)
  72. {
  73. switch ($this->batchMode) {
  74. case static::BATCH_MODE_NEWLINES:
  75. return $this->formatBatchNewlines($records);
  76. case static::BATCH_MODE_JSON:
  77. default:
  78. return $this->formatBatchJson($records);
  79. }
  80. }
  81. /**
  82. * @param bool $include
  83. */
  84. public function includeStacktraces($include = true)
  85. {
  86. $this->includeStacktraces = $include;
  87. }
  88. /**
  89. * Return a JSON-encoded array of records.
  90. *
  91. * @param array $records
  92. * @return string
  93. */
  94. protected function formatBatchJson(array $records)
  95. {
  96. return $this->toJson($this->normalize($records), true);
  97. }
  98. /**
  99. * Use new lines to separate records instead of a
  100. * JSON-encoded array.
  101. *
  102. * @param array $records
  103. * @return string
  104. */
  105. protected function formatBatchNewlines(array $records)
  106. {
  107. $instance = $this;
  108. $oldNewline = $this->appendNewline;
  109. $this->appendNewline = false;
  110. array_walk($records, function (&$value, $key) use ($instance) {
  111. $value = $instance->format($value);
  112. });
  113. $this->appendNewline = $oldNewline;
  114. return implode("\n", $records);
  115. }
  116. /**
  117. * Normalizes given $data.
  118. *
  119. * @param mixed $data
  120. *
  121. * @return mixed
  122. */
  123. protected function normalize($data)
  124. {
  125. if (is_array($data) || $data instanceof \Traversable) {
  126. $normalized = array();
  127. $count = 1;
  128. foreach ($data as $key => $value) {
  129. if ($count++ >= 1000) {
  130. $normalized['...'] = 'Over 1000 items, aborting normalization';
  131. break;
  132. }
  133. $normalized[$key] = $this->normalize($value);
  134. }
  135. return $normalized;
  136. }
  137. if ($data instanceof Exception || $data instanceof Throwable) {
  138. return $this->normalizeException($data);
  139. }
  140. return $data;
  141. }
  142. /**
  143. * Normalizes given exception with or without its own stack trace based on
  144. * `includeStacktraces` property.
  145. *
  146. * @param Exception|Throwable $e
  147. *
  148. * @return array
  149. */
  150. protected function normalizeException($e)
  151. {
  152. // TODO 2.0 only check for Throwable
  153. if (!$e instanceof Exception && !$e instanceof Throwable) {
  154. throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e));
  155. }
  156. $data = array(
  157. 'class' => get_class($e),
  158. 'message' => $e->getMessage(),
  159. 'code' => $e->getCode(),
  160. 'file' => $e->getFile().':'.$e->getLine(),
  161. );
  162. if ($this->includeStacktraces) {
  163. $trace = $e->getTrace();
  164. foreach ($trace as $frame) {
  165. if (isset($frame['file'])) {
  166. $data['trace'][] = $frame['file'].':'.$frame['line'];
  167. } elseif (isset($frame['function']) && $frame['function'] === '{closure}') {
  168. // We should again normalize the frames, because it might contain invalid items
  169. $data['trace'][] = $frame['function'];
  170. } else {
  171. // We should again normalize the frames, because it might contain invalid items
  172. $data['trace'][] = $this->normalize($frame);
  173. }
  174. }
  175. }
  176. if ($previous = $e->getPrevious()) {
  177. $data['previous'] = $this->normalizeException($previous);
  178. }
  179. return $data;
  180. }
  181. }