2
0

LineFormatter.php 6.3 KB

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