LineFormatter.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  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. /** @var string */
  24. protected $format;
  25. /** @var bool */
  26. protected $allowInlineLineBreaks;
  27. /** @var bool */
  28. protected $ignoreEmptyContextAndExtra;
  29. /** @var bool */
  30. protected $includeStacktraces;
  31. /**
  32. * @param string|null $format The format of the message
  33. * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format
  34. * @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries
  35. * @param bool $ignoreEmptyContextAndExtra
  36. */
  37. public function __construct(?string $format = null, ?string $dateFormat = null, bool $allowInlineLineBreaks = false, bool $ignoreEmptyContextAndExtra = false)
  38. {
  39. $this->format = $format === null ? static::SIMPLE_FORMAT : $format;
  40. $this->allowInlineLineBreaks = $allowInlineLineBreaks;
  41. $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra;
  42. parent::__construct($dateFormat);
  43. }
  44. public function includeStacktraces(bool $include = true): void
  45. {
  46. $this->includeStacktraces = $include;
  47. if ($this->includeStacktraces) {
  48. $this->allowInlineLineBreaks = true;
  49. }
  50. }
  51. public function allowInlineLineBreaks(bool $allow = true): void
  52. {
  53. $this->allowInlineLineBreaks = $allow;
  54. }
  55. public function ignoreEmptyContextAndExtra(bool $ignore = true): void
  56. {
  57. $this->ignoreEmptyContextAndExtra = $ignore;
  58. }
  59. /**
  60. * {@inheritdoc}
  61. */
  62. public function format(array $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. }
  97. return $output;
  98. }
  99. public function formatBatch(array $records): string
  100. {
  101. $message = '';
  102. foreach ($records as $record) {
  103. $message .= $this->format($record);
  104. }
  105. return $message;
  106. }
  107. /**
  108. * @param mixed $value
  109. */
  110. public function stringify($value): string
  111. {
  112. return $this->replaceNewlines($this->convertToString($value));
  113. }
  114. protected function normalizeException(\Throwable $e, int $depth = 0): string
  115. {
  116. $str = $this->formatException($e);
  117. if ($previous = $e->getPrevious()) {
  118. do {
  119. $str .= "\n[previous exception] " . $this->formatException($previous);
  120. } while ($previous = $previous->getPrevious());
  121. }
  122. return $str;
  123. }
  124. /**
  125. * @param mixed $data
  126. */
  127. protected function convertToString($data): string
  128. {
  129. if (null === $data || is_bool($data)) {
  130. return var_export($data, true);
  131. }
  132. if (is_scalar($data)) {
  133. return (string) $data;
  134. }
  135. return $this->toJson($data, true);
  136. }
  137. protected function replaceNewlines(string $str): string
  138. {
  139. if ($this->allowInlineLineBreaks) {
  140. if (0 === strpos($str, '{')) {
  141. return str_replace(array('\r', '\n'), array("\r", "\n"), $str);
  142. }
  143. return $str;
  144. }
  145. return str_replace(["\r\n", "\r", "\n"], ' ', $str);
  146. }
  147. private function formatException(\Throwable $e): string
  148. {
  149. $str = '[object] (' . Utils::getClass($e) . '(code: ' . $e->getCode();
  150. if ($e instanceof \SoapFault) {
  151. if (isset($e->faultcode)) {
  152. $str .= ' faultcode: ' . $e->faultcode;
  153. }
  154. if (isset($e->faultactor)) {
  155. $str .= ' faultactor: ' . $e->faultactor;
  156. }
  157. if (isset($e->detail)) {
  158. if (is_string($e->detail)) {
  159. $str .= ' detail: ' . $e->detail;
  160. } elseif (is_object($e->detail) || is_array($e->detail)) {
  161. $str .= ' detail: ' . $this->toJson($e->detail, true);
  162. }
  163. }
  164. }
  165. $str .= '): ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine() . ')';
  166. if ($this->includeStacktraces) {
  167. $str .= "\n[stacktrace]\n" . $e->getTraceAsString() . "\n";
  168. }
  169. return $str;
  170. }
  171. }