* Remove redundant file-level description and ensure the class desc and ingroup tag are on the class block instead. Ref https://gerrit.wikimedia.org/r/q/owner:Krinkle+message:ingroup * Widen `@covers` annotations in unit tests. Ref https://gerrit.wikimedia.org/r/q/owner:krinkle+is:merged+message:covers * Create "Debug" documentation group, covering the debug/ directory. This will show up on doc.wikimedia.org under "Modules", where each class is listed, and the class page will also link back to the group as part of its breadcrumb navigation. Test with `php maintenance/mwdocgen.php --file docs/,includes/debug/` and then view /w/docs/html/ * Improve docs of various classes and explain relationships better. In particular, reformat to ensure each class has a oneline description that captures its essential function. Change-Id: I5d1143a9244b7fd888e1dc31f0fd7965272aa900
180 lines
5.3 KiB
PHP
180 lines
5.3 KiB
PHP
<?php
|
|
/**
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
*
|
|
* @file
|
|
*/
|
|
|
|
namespace MediaWiki\Logger\Monolog;
|
|
|
|
use Error;
|
|
use Monolog\Formatter\LineFormatter as MonologLineFormatter;
|
|
use MWExceptionHandler;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Formats incoming records into a one-line string.
|
|
*
|
|
* An 'exeception' in the log record's context will be treated specially.
|
|
* It will be output for an '%exception%' placeholder in the format and
|
|
* excluded from '%context%' output if the '%exception%' placeholder is
|
|
* present.
|
|
*
|
|
* Throwables that are logged with this formatter will optional have their
|
|
* stack traces appended. If that is done, MWExceptionHandler::redactedTrace()
|
|
* will be used to redact the trace information.
|
|
*
|
|
* @since 1.26
|
|
* @ingroup Debug
|
|
* @copyright © 2015 Wikimedia Foundation and contributors
|
|
*/
|
|
class LineFormatter extends MonologLineFormatter {
|
|
|
|
/**
|
|
* @param string|null $format The format of the message
|
|
* @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format
|
|
* @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries
|
|
* @param bool $ignoreEmptyContextAndExtra
|
|
* @param bool $includeStacktraces
|
|
*/
|
|
public function __construct(
|
|
$format = null, $dateFormat = null, $allowInlineLineBreaks = false,
|
|
$ignoreEmptyContextAndExtra = false, $includeStacktraces = false
|
|
) {
|
|
parent::__construct(
|
|
$format, $dateFormat, $allowInlineLineBreaks,
|
|
$ignoreEmptyContextAndExtra
|
|
);
|
|
$this->includeStacktraces( $includeStacktraces );
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function format( array $record ): string {
|
|
// Drop the 'private' flag from the context
|
|
unset( $record['context']['private'] );
|
|
|
|
// Handle throwables specially: pretty format and remove from context
|
|
// Will be output for a '%exception%' placeholder in format
|
|
$prettyException = '';
|
|
if ( isset( $record['context']['exception'] ) &&
|
|
str_contains( $this->format, '%exception%' )
|
|
) {
|
|
$e = $record['context']['exception'];
|
|
unset( $record['context']['exception'] );
|
|
|
|
if ( $e instanceof Throwable ) {
|
|
$prettyException = $this->normalizeException( $e );
|
|
} elseif ( is_array( $e ) ) {
|
|
$prettyException = $this->normalizeExceptionArray( $e );
|
|
} else {
|
|
$prettyException = $this->stringify( $e );
|
|
}
|
|
}
|
|
|
|
$output = parent::format( $record );
|
|
|
|
if ( str_contains( $output, '%exception%' ) ) {
|
|
$output = str_replace( '%exception%', $prettyException, $output );
|
|
}
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* Convert a Throwable to a string.
|
|
*
|
|
* @param Throwable $e
|
|
* @param int $depth
|
|
* @return string
|
|
*/
|
|
protected function normalizeException( Throwable $e, int $depth = 0 ): string {
|
|
// Can't use typehint. Must match Monolog\Formatter\LineFormatter::normalizeException($e)
|
|
return $this->normalizeExceptionArray( $this->exceptionAsArray( $e ) );
|
|
}
|
|
|
|
/**
|
|
* Convert a throwable to an array of structured data.
|
|
*
|
|
* @param Throwable $e
|
|
* @return array
|
|
*/
|
|
protected function exceptionAsArray( Throwable $e ) {
|
|
$out = [
|
|
'class' => get_class( $e ),
|
|
'message' => $e->getMessage(),
|
|
'code' => $e->getCode(),
|
|
'file' => $e->getFile(),
|
|
'line' => $e->getLine(),
|
|
'trace' => MWExceptionHandler::redactTrace( $e->getTrace() ),
|
|
];
|
|
|
|
$prev = $e->getPrevious();
|
|
if ( $prev ) {
|
|
$out['previous'] = $this->exceptionAsArray( $prev );
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Convert an array of Throwable data to a string.
|
|
*
|
|
* @param array $e
|
|
* @return string
|
|
*/
|
|
protected function normalizeExceptionArray( array $e ) {
|
|
$defaults = [
|
|
'class' => 'Unknown',
|
|
'file' => 'unknown',
|
|
'line' => null,
|
|
'message' => 'unknown',
|
|
'trace' => [],
|
|
];
|
|
$e = array_merge( $defaults, $e );
|
|
|
|
// @phan-suppress-next-line PhanTypeMismatchArgumentNullableInternal class is always set
|
|
$which = is_a( $e['class'], Error::class, true ) ? 'Error' : 'Exception';
|
|
$str = "\n[$which {$e['class']}] (" .
|
|
"{$e['file']}:{$e['line']}) {$e['message']}";
|
|
|
|
if ( $this->includeStacktraces && $e['trace'] ) {
|
|
$str .= "\n" .
|
|
// @phan-suppress-next-line PhanTypeMismatchArgumentNullable trace is always set
|
|
MWExceptionHandler::prettyPrintTrace( $e['trace'], ' ' );
|
|
}
|
|
|
|
if ( isset( $e['previous'] ) ) {
|
|
$prev = $e['previous'];
|
|
while ( $prev ) {
|
|
$prev = array_merge( $defaults, $prev );
|
|
$which = is_a( $prev['class'], Error::class, true ) ? 'Error' : 'Exception';
|
|
$str .= "\nCaused by: [$which {$prev['class']}] (" .
|
|
"{$prev['file']}:{$prev['line']}) {$prev['message']}";
|
|
|
|
if ( $this->includeStacktraces && $prev['trace'] ) {
|
|
$str .= "\n" .
|
|
MWExceptionHandler::prettyPrintTrace(
|
|
$prev['trace'], ' '
|
|
);
|
|
}
|
|
|
|
$prev = $prev['previous'] ?? null;
|
|
}
|
|
}
|
|
return $str;
|
|
}
|
|
}
|