JsonFormatter.php 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  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 Monolog\Utils;
  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. public const BATCH_MODE_JSON = 1;
  23. public const BATCH_MODE_NEWLINES = 2;
  24. protected $batchMode;
  25. protected $appendNewline;
  26. /**
  27. * @var bool
  28. */
  29. protected $includeStacktraces = false;
  30. public function __construct(int $batchMode = self::BATCH_MODE_JSON, bool $appendNewline = true)
  31. {
  32. $this->batchMode = $batchMode;
  33. $this->appendNewline = $appendNewline;
  34. }
  35. /**
  36. * The batch mode option configures the formatting style for
  37. * multiple records. By default, multiple records will be
  38. * formatted as a JSON-encoded array. However, for
  39. * compatibility with some API endpoints, alternative styles
  40. * are available.
  41. */
  42. public function getBatchMode(): int
  43. {
  44. return $this->batchMode;
  45. }
  46. /**
  47. * True if newlines are appended to every formatted record
  48. */
  49. public function isAppendingNewlines(): bool
  50. {
  51. return $this->appendNewline;
  52. }
  53. /**
  54. * {@inheritdoc}
  55. *
  56. * @suppress PhanTypeComparisonToArray
  57. */
  58. public function format(array $record): string
  59. {
  60. $normalized = $this->normalize($record);
  61. if (isset($normalized['context']) && $normalized['context'] === []) {
  62. $normalized['context'] = new \stdClass;
  63. }
  64. if (isset($normalized['extra']) && $normalized['extra'] === []) {
  65. $normalized['extra'] = new \stdClass;
  66. }
  67. return $this->toJson($normalized, true) . ($this->appendNewline ? "\n" : '');
  68. }
  69. /**
  70. * {@inheritdoc}
  71. */
  72. public function formatBatch(array $records): string
  73. {
  74. switch ($this->batchMode) {
  75. case static::BATCH_MODE_NEWLINES:
  76. return $this->formatBatchNewlines($records);
  77. case static::BATCH_MODE_JSON:
  78. default:
  79. return $this->formatBatchJson($records);
  80. }
  81. }
  82. public function includeStacktraces(bool $include = true)
  83. {
  84. $this->includeStacktraces = $include;
  85. }
  86. /**
  87. * Return a JSON-encoded array of records.
  88. */
  89. protected function formatBatchJson(array $records): string
  90. {
  91. return $this->toJson($this->normalize($records), true);
  92. }
  93. /**
  94. * Use new lines to separate records instead of a
  95. * JSON-encoded array.
  96. */
  97. protected function formatBatchNewlines(array $records): string
  98. {
  99. $instance = $this;
  100. $oldNewline = $this->appendNewline;
  101. $this->appendNewline = false;
  102. array_walk($records, function (&$value, $key) use ($instance) {
  103. $value = $instance->format($value);
  104. });
  105. $this->appendNewline = $oldNewline;
  106. return implode("\n", $records);
  107. }
  108. /**
  109. * Normalizes given $data.
  110. *
  111. * @param mixed $data
  112. *
  113. * @return mixed
  114. */
  115. protected function normalize($data, int $depth = 0)
  116. {
  117. if ($depth > $this->maxNormalizeDepth) {
  118. return 'Over '.$this->maxNormalizeDepth.' levels deep, aborting normalization';
  119. }
  120. if (is_array($data) || $data instanceof \Traversable) {
  121. $normalized = [];
  122. $count = 1;
  123. foreach ($data as $key => $value) {
  124. if ($count++ > $this->maxNormalizeItemCount) {
  125. $normalized['...'] = 'Over '.$this->maxNormalizeItemCount.' items ('.count($data).' total), aborting normalization';
  126. break;
  127. }
  128. $normalized[$key] = $this->normalize($value, $depth + 1);
  129. }
  130. return $normalized;
  131. }
  132. if ($data instanceof Throwable) {
  133. return $this->normalizeException($data, $depth);
  134. }
  135. return $data;
  136. }
  137. /**
  138. * Normalizes given exception with or without its own stack trace based on
  139. * `includeStacktraces` property.
  140. */
  141. protected function normalizeException(Throwable $e, int $depth = 0): array
  142. {
  143. $data = parent::normalizeException($e, $depth);
  144. if (!$this->includeStacktraces) {
  145. unset($data['trace']);
  146. }
  147. return $data;
  148. }
  149. }