LineFormatter.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. <?php
  2. /**
  3. * This program is free software; you can redistribute it and/or modify
  4. * it under the terms of the GNU General Public License as published by
  5. * the Free Software Foundation; either version 2 of the License, or
  6. * (at your option) any later version.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU General Public License along
  14. * with this program; if not, write to the Free Software Foundation, Inc.,
  15. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. * http://www.gnu.org/copyleft/gpl.html
  17. *
  18. * @file
  19. */
  20. namespace MediaWiki\Logger\Monolog;
  21. use Error;
  22. use Exception;
  23. use Monolog\Formatter\LineFormatter as MonologLineFormatter;
  24. use MWExceptionHandler;
  25. use Throwable;
  26. /**
  27. * Formats incoming records into a one-line string.
  28. *
  29. * An 'exeception' in the log record's context will be treated specially.
  30. * It will be output for an '%exception%' placeholder in the format and
  31. * excluded from '%context%' output if the '%exception%' placeholder is
  32. * present.
  33. *
  34. * Throwables that are logged with this formatter will optional have their
  35. * stack traces appended. If that is done, MWExceptionHandler::redactedTrace()
  36. * will be used to redact the trace information.
  37. *
  38. * @since 1.26
  39. * @copyright © 2015 Wikimedia Foundation and contributors
  40. */
  41. class LineFormatter extends MonologLineFormatter {
  42. /**
  43. * @param string|null $format The format of the message
  44. * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format
  45. * @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries
  46. * @param bool $ignoreEmptyContextAndExtra
  47. * @param bool $includeStacktraces
  48. */
  49. public function __construct(
  50. $format = null, $dateFormat = null, $allowInlineLineBreaks = false,
  51. $ignoreEmptyContextAndExtra = false, $includeStacktraces = false
  52. ) {
  53. parent::__construct(
  54. $format, $dateFormat, $allowInlineLineBreaks,
  55. $ignoreEmptyContextAndExtra
  56. );
  57. $this->includeStacktraces( $includeStacktraces );
  58. }
  59. /**
  60. * @inheritDoc
  61. */
  62. public function format( array $record ) {
  63. // Drop the 'private' flag from the context
  64. unset( $record['context']['private'] );
  65. // Handle exceptions specially: pretty format and remove from context
  66. // Will be output for a '%exception%' placeholder in format
  67. $prettyException = '';
  68. if ( isset( $record['context']['exception'] ) &&
  69. strpos( $this->format, '%exception%' ) !== false
  70. ) {
  71. $e = $record['context']['exception'];
  72. unset( $record['context']['exception'] );
  73. if ( $e instanceof Throwable || $e instanceof Exception ) {
  74. $prettyException = $this->normalizeException( $e );
  75. } elseif ( is_array( $e ) ) {
  76. $prettyException = $this->normalizeExceptionArray( $e );
  77. } else {
  78. $prettyException = $this->stringify( $e );
  79. }
  80. }
  81. $output = parent::format( $record );
  82. if ( strpos( $output, '%exception%' ) !== false ) {
  83. $output = str_replace( '%exception%', $prettyException, $output );
  84. }
  85. return $output;
  86. }
  87. /**
  88. * Convert a Throwable to a string.
  89. *
  90. * @param Exception|Throwable $e
  91. * @return string
  92. */
  93. protected function normalizeException( $e ) {
  94. return $this->normalizeExceptionArray( $this->exceptionAsArray( $e ) );
  95. }
  96. /**
  97. * Convert a throwable to an array of structured data.
  98. *
  99. * @param Exception|Throwable $e
  100. * @return array
  101. */
  102. protected function exceptionAsArray( $e ) {
  103. $out = [
  104. 'class' => get_class( $e ),
  105. 'message' => $e->getMessage(),
  106. 'code' => $e->getCode(),
  107. 'file' => $e->getFile(),
  108. 'line' => $e->getLine(),
  109. 'trace' => MWExceptionHandler::redactTrace( $e->getTrace() ),
  110. ];
  111. $prev = $e->getPrevious();
  112. if ( $prev ) {
  113. $out['previous'] = $this->exceptionAsArray( $prev );
  114. }
  115. return $out;
  116. }
  117. /**
  118. * Convert an array of Throwable data to a string.
  119. *
  120. * @param array $e
  121. * @return string
  122. */
  123. protected function normalizeExceptionArray( array $e ) {
  124. $defaults = [
  125. 'class' => 'Unknown',
  126. 'file' => 'unknown',
  127. 'line' => null,
  128. 'message' => 'unknown',
  129. 'trace' => [],
  130. ];
  131. $e = array_merge( $defaults, $e );
  132. $which = is_a( $e['class'], Error::class, true ) ? 'Error' : 'Exception';
  133. $str = "\n[$which {$e['class']}] (" .
  134. "{$e['file']}:{$e['line']}) {$e['message']}";
  135. if ( $this->includeStacktraces && $e['trace'] ) {
  136. $str .= "\n" .
  137. MWExceptionHandler::prettyPrintTrace( $e['trace'], ' ' );
  138. }
  139. if ( isset( $e['previous'] ) ) {
  140. $prev = $e['previous'];
  141. while ( $prev ) {
  142. $prev = array_merge( $defaults, $prev );
  143. $which = is_a( $prev['class'], Error::class, true ) ? 'Error' : 'Exception';
  144. $str .= "\nCaused by: [$which {$prev['class']}] (" .
  145. "{$prev['file']}:{$prev['line']}) {$prev['message']}";
  146. if ( $this->includeStacktraces && $prev['trace'] ) {
  147. $str .= "\n" .
  148. MWExceptionHandler::prettyPrintTrace(
  149. $prev['trace'], ' '
  150. );
  151. }
  152. $prev = $prev['previous'] ?? null;
  153. }
  154. }
  155. return $str;
  156. }
  157. }