JsonFormatter.php 5.5 KB

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