LineFormatter.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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. /**
  13. * Formats incoming records into a one-line string
  14. *
  15. * This is especially useful for logging to files
  16. *
  17. * @author Jordi Boggiano <j.boggiano@seld.be>
  18. * @author Christophe Coevoet <stof@notk.org>
  19. */
  20. class LineFormatter extends NormalizerFormatter
  21. {
  22. public const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n";
  23. protected $format;
  24. protected $allowInlineLineBreaks;
  25. protected $ignoreEmptyContextAndExtra;
  26. protected $includeStacktraces;
  27. /**
  28. * @param ?string $format The format of the message
  29. * @param ?string $dateFormat The format of the timestamp: one supported by DateTime::format
  30. * @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries
  31. * @param bool $ignoreEmptyContextAndExtra
  32. */
  33. public function __construct(?string $format = null, ?string $dateFormat = null, bool $allowInlineLineBreaks = false, bool $ignoreEmptyContextAndExtra = false)
  34. {
  35. $this->format = $format === null ? static::SIMPLE_FORMAT : $format;
  36. $this->allowInlineLineBreaks = $allowInlineLineBreaks;
  37. $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra;
  38. parent::__construct($dateFormat);
  39. }
  40. public function includeStacktraces(bool $include = true)
  41. {
  42. $this->includeStacktraces = $include;
  43. if ($this->includeStacktraces) {
  44. $this->allowInlineLineBreaks = true;
  45. }
  46. }
  47. public function allowInlineLineBreaks(bool $allow = true)
  48. {
  49. $this->allowInlineLineBreaks = $allow;
  50. }
  51. public function ignoreEmptyContextAndExtra(bool $ignore = true)
  52. {
  53. $this->ignoreEmptyContextAndExtra = $ignore;
  54. }
  55. /**
  56. * {@inheritdoc}
  57. */
  58. public function format(array $record): string
  59. {
  60. $vars = parent::format($record);
  61. $output = $this->format;
  62. foreach ($vars['extra'] as $var => $val) {
  63. if (false !== strpos($output, '%extra.'.$var.'%')) {
  64. $output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output);
  65. unset($vars['extra'][$var]);
  66. }
  67. }
  68. foreach ($vars['context'] as $var => $val) {
  69. if (false !== strpos($output, '%context.'.$var.'%')) {
  70. $output = str_replace('%context.'.$var.'%', $this->stringify($val), $output);
  71. unset($vars['context'][$var]);
  72. }
  73. }
  74. if ($this->ignoreEmptyContextAndExtra) {
  75. if (empty($vars['context'])) {
  76. unset($vars['context']);
  77. $output = str_replace('%context%', '', $output);
  78. }
  79. if (empty($vars['extra'])) {
  80. unset($vars['extra']);
  81. $output = str_replace('%extra%', '', $output);
  82. }
  83. }
  84. foreach ($vars as $var => $val) {
  85. if (false !== strpos($output, '%'.$var.'%')) {
  86. $output = str_replace('%'.$var.'%', $this->stringify($val), $output);
  87. }
  88. }
  89. // remove leftover %extra.xxx% and %context.xxx% if any
  90. if (false !== strpos($output, '%')) {
  91. $output = preg_replace('/%(?:extra|context)\..+?%/', '', $output);
  92. }
  93. return $output;
  94. }
  95. public function formatBatch(array $records): string
  96. {
  97. $message = '';
  98. foreach ($records as $record) {
  99. $message .= $this->format($record);
  100. }
  101. return $message;
  102. }
  103. public function stringify($value): string
  104. {
  105. return $this->replaceNewlines($this->convertToString($value));
  106. }
  107. /**
  108. * @suppress PhanParamSignatureMismatch
  109. */
  110. protected function normalizeException(\Throwable $e, int $depth = 0): string
  111. {
  112. $str = $this->formatException($e);
  113. if ($previous = $e->getPrevious()) {
  114. do {
  115. $str .= "\n[previous exception] " . $this->formatException($previous);
  116. } while ($previous = $previous->getPrevious());
  117. }
  118. return $str;
  119. }
  120. protected function convertToString($data): string
  121. {
  122. if (null === $data || is_bool($data)) {
  123. return var_export($data, true);
  124. }
  125. if (is_scalar($data)) {
  126. return (string) $data;
  127. }
  128. return (string) $this->toJson($data, true);
  129. }
  130. protected function replaceNewlines(string $str): string
  131. {
  132. if ($this->allowInlineLineBreaks) {
  133. if (0 === strpos($str, '{')) {
  134. return str_replace(array('\r', '\n'), array("\r", "\n"), $str);
  135. }
  136. return $str;
  137. }
  138. return str_replace(["\r\n", "\r", "\n"], ' ', $str);
  139. }
  140. private function formatException(\Throwable $e): string
  141. {
  142. $str = '[object] (' . Utils::getClass($e) . '(code: ' . $e->getCode() . '): ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine() . ')';
  143. if ($this->includeStacktraces) {
  144. $str .= "\n[stacktrace]\n" . $e->getTraceAsString() . "\n";
  145. }
  146. return $str;
  147. }
  148. }