MediaWiki  1.30.1
LegacyLogger.php
Go to the documentation of this file.
1 <?php
21 namespace MediaWiki\Logger;
22 
23 use DateTimeZone;
24 use Exception;
25 use MWDebug;
27 use Psr\Log\AbstractLogger;
28 use Psr\Log\LogLevel;
30 
48 class LegacyLogger extends AbstractLogger {
49 
53  protected $channel;
54 
61  protected static $levelMapping = [
62  LogLevel::DEBUG => 100,
63  LogLevel::INFO => 200,
64  LogLevel::NOTICE => 250,
65  LogLevel::WARNING => 300,
66  LogLevel::ERROR => 400,
67  LogLevel::CRITICAL => 500,
68  LogLevel::ALERT => 550,
69  LogLevel::EMERGENCY => 600,
70  ];
71 
75  protected static $dbChannels = [
76  'DBQuery' => true,
77  'DBConnection' => true
78  ];
79 
83  public function __construct( $channel ) {
84  $this->channel = $channel;
85  }
86 
95  public function log( $level, $message, array $context = [] ) {
96  if ( is_string( $level ) ) {
97  $level = self::$levelMapping[$level];
98  }
99  if ( $this->channel === 'DBQuery' && isset( $context['method'] )
100  && isset( $context['master'] ) && isset( $context['runtime'] )
101  ) {
102  MWDebug::query( $message, $context['method'], $context['master'], $context['runtime'] );
103  return; // only send profiling data to MWDebug profiling
104  }
105 
106  if ( isset( self::$dbChannels[$this->channel] )
107  && $level >= self::$levelMapping[LogLevel::ERROR]
108  ) {
109  // Format and write DB errors to the legacy locations
110  $effectiveChannel = 'wfLogDBError';
111  } else {
112  $effectiveChannel = $this->channel;
113  }
114 
115  if ( self::shouldEmit( $effectiveChannel, $message, $level, $context ) ) {
116  $text = self::format( $effectiveChannel, $message, $context );
117  $destination = self::destination( $effectiveChannel, $message, $context );
118  self::emit( $text, $destination );
119  }
120  if ( !isset( $context['private'] ) || !$context['private'] ) {
121  // Add to debug toolbar if not marked as "private"
122  MWDebug::debugMsg( $message, [ 'channel' => $this->channel ] + $context );
123  }
124  }
125 
136  public static function shouldEmit( $channel, $message, $level, $context ) {
138 
139  if ( is_string( $level ) ) {
140  $level = self::$levelMapping[$level];
141  }
142 
143  if ( $channel === 'wfLogDBError' ) {
144  // wfLogDBError messages are emitted if a database log location is
145  // specfied.
146  $shouldEmit = (bool)$wgDBerrorLog;
147 
148  } elseif ( $channel === 'wfErrorLog' ) {
149  // All messages on the wfErrorLog channel should be emitted.
150  $shouldEmit = true;
151 
152  } elseif ( $channel === 'wfDebug' ) {
153  // wfDebug messages are emitted if a catch all logging file has
154  // been specified. Checked explicitly so that 'private' flagged
155  // messages are not discarded by unset $wgDebugLogGroups channel
156  // handling below.
157  $shouldEmit = $wgDebugLogFile != '';
158 
159  } elseif ( isset( $wgDebugLogGroups[$channel] ) ) {
160  $logConfig = $wgDebugLogGroups[$channel];
161 
162  if ( is_array( $logConfig ) ) {
163  $shouldEmit = true;
164  if ( isset( $logConfig['sample'] ) ) {
165  // Emit randomly with a 1 in 'sample' chance for each message.
166  $shouldEmit = mt_rand( 1, $logConfig['sample'] ) === 1;
167  }
168 
169  if ( isset( $logConfig['level'] ) ) {
170  $shouldEmit = $level >= self::$levelMapping[$logConfig['level']];
171  }
172  } else {
173  // Emit unless the config value is explictly false.
174  $shouldEmit = $logConfig !== false;
175  }
176 
177  } elseif ( isset( $context['private'] ) && $context['private'] ) {
178  // Don't emit if the message didn't match previous checks based on
179  // the channel and the event is marked as private. This check
180  // discards messages sent via wfDebugLog() with dest == 'private'
181  // and no explicit wgDebugLogGroups configuration.
182  $shouldEmit = false;
183  } else {
184  // Default return value is the same as the historic wfDebug
185  // method: emit if $wgDebugLogFile has been set.
186  $shouldEmit = $wgDebugLogFile != '';
187  }
188 
189  return $shouldEmit;
190  }
191 
205  public static function format( $channel, $message, $context ) {
207 
208  if ( $channel === 'wfDebug' ) {
209  $text = self::formatAsWfDebug( $channel, $message, $context );
210 
211  } elseif ( $channel === 'wfLogDBError' ) {
212  $text = self::formatAsWfLogDBError( $channel, $message, $context );
213 
214  } elseif ( $channel === 'wfErrorLog' ) {
215  $text = "{$message}\n";
216 
217  } elseif ( $channel === 'profileoutput' ) {
218  // Legacy wfLogProfilingData formatitng
219  $forward = '';
220  if ( isset( $context['forwarded_for'] ) ) {
221  $forward = " forwarded for {$context['forwarded_for']}";
222  }
223  if ( isset( $context['client_ip'] ) ) {
224  $forward .= " client IP {$context['client_ip']}";
225  }
226  if ( isset( $context['from'] ) ) {
227  $forward .= " from {$context['from']}";
228  }
229  if ( $forward ) {
230  $forward = "\t(proxied via {$context['proxy']}{$forward})";
231  }
232  if ( $context['anon'] ) {
233  $forward .= ' anon';
234  }
235  if ( !isset( $context['url'] ) ) {
236  $context['url'] = 'n/a';
237  }
238 
239  $log = sprintf( "%s\t%04.3f\t%s%s\n",
240  gmdate( 'YmdHis' ), $context['elapsed'], $context['url'], $forward );
241 
242  $text = self::formatAsWfDebugLog(
243  $channel, $log . $context['output'], $context );
244 
245  } elseif ( !isset( $wgDebugLogGroups[$channel] ) ) {
246  $text = self::formatAsWfDebug(
247  $channel, "[{$channel}] {$message}", $context );
248 
249  } else {
250  // Default formatting is wfDebugLog's historic style
251  $text = self::formatAsWfDebugLog( $channel, $message, $context );
252  }
253 
254  // Append stacktrace of exception if available
255  if ( $wgLogExceptionBacktrace && isset( $context['exception'] ) ) {
256  $e = $context['exception'];
257  $backtrace = false;
258 
259  if ( $e instanceof Exception ) {
261 
262  } elseif ( is_array( $e ) && isset( $e['trace'] ) ) {
263  // Exception has already been unpacked as structured data
264  $backtrace = $e['trace'];
265  }
266 
267  if ( $backtrace ) {
268  $text .= MWExceptionHandler::prettyPrintTrace( $backtrace ) .
269  "\n";
270  }
271  }
272 
273  return self::interpolate( $text, $context );
274  }
275 
284  protected static function formatAsWfDebug( $channel, $message, $context ) {
285  $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $message );
286  if ( isset( $context['seconds_elapsed'] ) ) {
287  // Prepend elapsed request time and real memory usage with two
288  // trailing spaces.
289  $text = "{$context['seconds_elapsed']} {$context['memory_used']} {$text}";
290  }
291  if ( isset( $context['prefix'] ) ) {
292  $text = "{$context['prefix']}{$text}";
293  }
294  return "{$text}\n";
295  }
296 
305  protected static function formatAsWfLogDBError( $channel, $message, $context ) {
307  static $cachedTimezone = null;
308 
309  if ( !$cachedTimezone ) {
310  $cachedTimezone = new DateTimeZone( $wgDBerrorLogTZ );
311  }
312 
313  $d = date_create( 'now', $cachedTimezone );
314  $date = $d->format( 'D M j G:i:s T Y' );
315 
316  $host = wfHostname();
317  $wiki = wfWikiID();
318 
319  $text = "{$date}\t{$host}\t{$wiki}\t{$message}\n";
320  return $text;
321  }
322 
331  protected static function formatAsWfDebugLog( $channel, $message, $context ) {
332  $time = wfTimestamp( TS_DB );
333  $wiki = wfWikiID();
334  $host = wfHostname();
335  $text = "{$time} {$host} {$wiki}: {$message}\n";
336  return $text;
337  }
338 
346  public static function interpolate( $message, array $context ) {
347  if ( strpos( $message, '{' ) !== false ) {
348  $replace = [];
349  foreach ( $context as $key => $val ) {
350  $replace['{' . $key . '}'] = self::flatten( $val );
351  }
352  $message = strtr( $message, $replace );
353  }
354  return $message;
355  }
356 
364  protected static function flatten( $item ) {
365  if ( null === $item ) {
366  return '[Null]';
367  }
368 
369  if ( is_bool( $item ) ) {
370  return $item ? 'true' : 'false';
371  }
372 
373  if ( is_float( $item ) ) {
374  if ( is_infinite( $item ) ) {
375  return ( $item > 0 ? '' : '-' ) . 'INF';
376  }
377  if ( is_nan( $item ) ) {
378  return 'NaN';
379  }
380  return (string)$item;
381  }
382 
383  if ( is_scalar( $item ) ) {
384  return (string)$item;
385  }
386 
387  if ( is_array( $item ) ) {
388  return '[Array(' . count( $item ) . ')]';
389  }
390 
391  if ( $item instanceof \DateTime ) {
392  return $item->format( 'c' );
393  }
394 
395  if ( $item instanceof Exception ) {
396  return '[Exception ' . get_class( $item ) . '( ' .
397  $item->getFile() . ':' . $item->getLine() . ') ' .
398  $item->getMessage() . ']';
399  }
400 
401  if ( is_object( $item ) ) {
402  if ( method_exists( $item, '__toString' ) ) {
403  return (string)$item;
404  }
405 
406  return '[Object ' . get_class( $item ) . ']';
407  }
408 
409  if ( is_resource( $item ) ) {
410  return '[Resource ' . get_resource_type( $item ) . ']';
411  }
412 
413  return '[Unknown ' . gettype( $item ) . ']';
414  }
415 
426  protected static function destination( $channel, $message, $context ) {
428 
429  // Default destination is the debug log file as historically used by
430  // the wfDebug function.
431  $destination = $wgDebugLogFile;
432 
433  if ( isset( $context['destination'] ) ) {
434  // Use destination explicitly provided in context
435  $destination = $context['destination'];
436 
437  } elseif ( $channel === 'wfDebug' ) {
438  $destination = $wgDebugLogFile;
439 
440  } elseif ( $channel === 'wfLogDBError' ) {
441  $destination = $wgDBerrorLog;
442 
443  } elseif ( isset( $wgDebugLogGroups[$channel] ) ) {
444  $logConfig = $wgDebugLogGroups[$channel];
445 
446  if ( is_array( $logConfig ) ) {
447  $destination = $logConfig['destination'];
448  } else {
449  $destination = strval( $logConfig );
450  }
451  }
452 
453  return $destination;
454  }
455 
465  public static function emit( $text, $file ) {
466  if ( substr( $file, 0, 4 ) == 'udp:' ) {
467  $transport = UDPTransport::newFromString( $file );
468  $transport->emit( $text );
469  } else {
470  \MediaWiki\suppressWarnings();
471  $exists = file_exists( $file );
472  $size = $exists ? filesize( $file ) : false;
473  if ( !$exists ||
474  ( $size !== false && $size + strlen( $text ) < 0x7fffffff )
475  ) {
476  file_put_contents( $file, $text, FILE_APPEND );
477  }
478  \MediaWiki\restoreWarnings();
479  }
480  }
481 
482 }
MediaWiki\Logger\LegacyLogger\interpolate
static interpolate( $message, array $context)
Interpolate placeholders in logging message.
Definition: LegacyLogger.php:346
UDPTransport\newFromString
static newFromString( $info)
Definition: UDPTransport.php:52
MWExceptionHandler\getRedactedTrace
static getRedactedTrace( $e)
Return a copy of an exception's backtrace as an array.
Definition: MWExceptionHandler.php:370
MediaWiki\Logger\LegacyLogger\$levelMapping
static $levelMapping
Convert \Psr\Log\LogLevel constants into int for sane comparisons These are the same values that Monl...
Definition: LegacyLogger.php:61
MediaWiki\Logger\LegacyLogger\flatten
static flatten( $item)
Convert a logging context element to a string suitable for interpolation.
Definition: LegacyLogger.php:364
MWDebug
New debugger system that outputs a toolbar on page view.
Definition: MWDebug.php:33
MediaWiki\Logger\LegacyLogger\$dbChannels
static array $dbChannels
Definition: LegacyLogger.php:75
captcha-old.count
count
Definition: captcha-old.py:249
MediaWiki\Logger\LegacyLogger\destination
static destination( $channel, $message, $context)
Select the appropriate log output destination for the given log event.
Definition: LegacyLogger.php:426
MWExceptionHandler
Handler class for MWExceptions.
Definition: MWExceptionHandler.php:30
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:2040
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
MediaWiki\Logger\LegacyLogger\log
log( $level, $message, array $context=[])
Logs with an arbitrary level.
Definition: LegacyLogger.php:95
wfHostname
wfHostname()
Fetch server name for use in error reporting etc.
Definition: GlobalFunctions.php:1482
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
$wgDBerrorLog
$wgDBerrorLog
File to log database errors to.
Definition: DefaultSettings.php:1996
MediaWiki\Logger\LegacyLogger\formatAsWfDebugLog
static formatAsWfDebugLog( $channel, $message, $context)
Format a message as `wfDebugLog() would have formatted it.
Definition: LegacyLogger.php:331
MediaWiki\Logger\LegacyLogger\$channel
$channel
Definition: LegacyLogger.php:53
MWExceptionHandler\prettyPrintTrace
static prettyPrintTrace(array $trace, $pad='')
Generate a string representation of a stacktrace.
Definition: MWExceptionHandler.php:323
MediaWiki\Logger
Definition: ConsoleLogger.php:3
MWDebug\debugMsg
static debugMsg( $str, $context=[])
This is a method to pass messages from wfDebug to the pretty debugger.
Definition: MWDebug.php:323
$wgDebugLogGroups
$wgDebugLogGroups
Map of string log group names to log destinations.
Definition: DefaultSettings.php:6202
$wgLogExceptionBacktrace
$wgLogExceptionBacktrace
If true, send the exception backtrace to the error log.
Definition: DefaultSettings.php:6280
$time
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1778
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
$wgDebugLogFile
$wgDebugLogFile
Filename for debug logging.
Definition: DefaultSettings.php:6084
UDPTransport
A generic class to send a message over UDP.
Definition: UDPTransport.php:31
wfWikiID
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
Definition: GlobalFunctions.php:2807
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2141
MWDebug\query
static query( $sql, $function, $isMaster, $runTime)
Begins profiling on a database query.
Definition: MWDebug.php:355
MediaWiki\Logger\LegacyLogger\formatAsWfDebug
static formatAsWfDebug( $channel, $message, $context)
Format a message as wfDebug() would have formatted it.
Definition: LegacyLogger.php:284
MediaWiki\Logger\LegacyLogger\emit
static emit( $text, $file)
Log to a file without getting "file size exceeded" signals.
Definition: LegacyLogger.php:465
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
MediaWiki\Logger\LegacyLogger\format
static format( $channel, $message, $context)
Format a message.
Definition: LegacyLogger.php:205
true
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition: hooks.txt:1965
MediaWiki\Logger\LegacyLogger\formatAsWfLogDBError
static formatAsWfLogDBError( $channel, $message, $context)
Format a message as wfLogDBError() would have formatted it.
Definition: LegacyLogger.php:305
MediaWiki\Logger\LegacyLogger
PSR-3 logger that mimics the historic implementation of MediaWiki's wfErrorLog logging implementation...
Definition: LegacyLogger.php:48
$wgDBerrorLogTZ
$wgDBerrorLogTZ
Timezone to use in the error log.
Definition: DefaultSettings.php:2016
MediaWiki\Logger\LegacyLogger\__construct
__construct( $channel)
Definition: LegacyLogger.php:83
MediaWiki\$context
IContextSource $context
Definition: MediaWiki.php:37
MediaWiki\Logger\LegacyLogger\shouldEmit
static shouldEmit( $channel, $message, $level, $context)
Determine if the given message should be emitted or not.
Definition: LegacyLogger.php:136
array
the array() calling protocol came about after MediaWiki 1.4rc1.