JsonFormatter.php 5.0 KB

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