MediaWiki  1.32.0
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 === 'wfDebug' ) {
149  // wfDebug messages are emitted if a catch all logging file has
150  // been specified. Checked explicitly so that 'private' flagged
151  // messages are not discarded by unset $wgDebugLogGroups channel
152  // handling below.
153  $shouldEmit = $wgDebugLogFile != '';
154 
155  } elseif ( isset( $wgDebugLogGroups[$channel] ) ) {
156  $logConfig = $wgDebugLogGroups[$channel];
157 
158  if ( is_array( $logConfig ) ) {
159  $shouldEmit = true;
160  if ( isset( $logConfig['sample'] ) ) {
161  // Emit randomly with a 1 in 'sample' chance for each message.
162  $shouldEmit = mt_rand( 1, $logConfig['sample'] ) === 1;
163  }
164 
165  if ( isset( $logConfig['level'] ) ) {
166  $shouldEmit = $level >= self::$levelMapping[$logConfig['level']];
167  }
168  } else {
169  // Emit unless the config value is explictly false.
170  $shouldEmit = $logConfig !== false;
171  }
172 
173  } elseif ( isset( $context['private'] ) && $context['private'] ) {
174  // Don't emit if the message didn't match previous checks based on
175  // the channel and the event is marked as private. This check
176  // discards messages sent via wfDebugLog() with dest == 'private'
177  // and no explicit wgDebugLogGroups configuration.
178  $shouldEmit = false;
179  } else {
180  // Default return value is the same as the historic wfDebug
181  // method: emit if $wgDebugLogFile has been set.
182  $shouldEmit = $wgDebugLogFile != '';
183  }
184 
185  return $shouldEmit;
186  }
187 
200  public static function format( $channel, $message, $context ) {
202 
203  if ( $channel === 'wfDebug' ) {
204  $text = self::formatAsWfDebug( $channel, $message, $context );
205 
206  } elseif ( $channel === 'wfLogDBError' ) {
207  $text = self::formatAsWfLogDBError( $channel, $message, $context );
208 
209  } elseif ( $channel === 'profileoutput' ) {
210  // Legacy wfLogProfilingData formatitng
211  $forward = '';
212  if ( isset( $context['forwarded_for'] ) ) {
213  $forward = " forwarded for {$context['forwarded_for']}";
214  }
215  if ( isset( $context['client_ip'] ) ) {
216  $forward .= " client IP {$context['client_ip']}";
217  }
218  if ( isset( $context['from'] ) ) {
219  $forward .= " from {$context['from']}";
220  }
221  if ( $forward ) {
222  $forward = "\t(proxied via {$context['proxy']}{$forward})";
223  }
224  if ( $context['anon'] ) {
225  $forward .= ' anon';
226  }
227  if ( !isset( $context['url'] ) ) {
228  $context['url'] = 'n/a';
229  }
230 
231  $log = sprintf( "%s\t%04.3f\t%s%s\n",
232  gmdate( 'YmdHis' ), $context['elapsed'], $context['url'], $forward );
233 
234  $text = self::formatAsWfDebugLog(
235  $channel, $log . $context['output'], $context );
236 
237  } elseif ( !isset( $wgDebugLogGroups[$channel] ) ) {
238  $text = self::formatAsWfDebug(
239  $channel, "[{$channel}] {$message}", $context );
240 
241  } else {
242  // Default formatting is wfDebugLog's historic style
243  $text = self::formatAsWfDebugLog( $channel, $message, $context );
244  }
245 
246  // Append stacktrace of exception if available
247  if ( $wgLogExceptionBacktrace && isset( $context['exception'] ) ) {
248  $e = $context['exception'];
249  $backtrace = false;
250 
251  if ( $e instanceof Exception ) {
253 
254  } elseif ( is_array( $e ) && isset( $e['trace'] ) ) {
255  // Exception has already been unpacked as structured data
256  $backtrace = $e['trace'];
257  }
258 
259  if ( $backtrace ) {
260  $text .= MWExceptionHandler::prettyPrintTrace( $backtrace ) .
261  "\n";
262  }
263  }
264 
265  return self::interpolate( $text, $context );
266  }
267 
276  protected static function formatAsWfDebug( $channel, $message, $context ) {
277  $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $message );
278  if ( isset( $context['seconds_elapsed'] ) ) {
279  // Prepend elapsed request time and real memory usage with two
280  // trailing spaces.
281  $text = "{$context['seconds_elapsed']} {$context['memory_used']} {$text}";
282  }
283  if ( isset( $context['prefix'] ) ) {
284  $text = "{$context['prefix']}{$text}";
285  }
286  return "{$text}\n";
287  }
288 
297  protected static function formatAsWfLogDBError( $channel, $message, $context ) {
298  global $wgDBerrorLogTZ;
299  static $cachedTimezone = null;
300 
301  if ( !$cachedTimezone ) {
302  $cachedTimezone = new DateTimeZone( $wgDBerrorLogTZ );
303  }
304 
305  $d = date_create( 'now', $cachedTimezone );
306  $date = $d->format( 'D M j G:i:s T Y' );
307 
308  $host = wfHostname();
309  $wiki = wfWikiID();
310 
311  $text = "{$date}\t{$host}\t{$wiki}\t{$message}\n";
312  return $text;
313  }
314 
323  protected static function formatAsWfDebugLog( $channel, $message, $context ) {
324  $time = wfTimestamp( TS_DB );
325  $wiki = wfWikiID();
326  $host = wfHostname();
327  $text = "{$time} {$host} {$wiki}: {$message}\n";
328  return $text;
329  }
330 
338  public static function interpolate( $message, array $context ) {
339  if ( strpos( $message, '{' ) !== false ) {
340  $replace = [];
341  foreach ( $context as $key => $val ) {
342  $replace['{' . $key . '}'] = self::flatten( $val );
343  }
344  $message = strtr( $message, $replace );
345  }
346  return $message;
347  }
348 
356  protected static function flatten( $item ) {
357  if ( null === $item ) {
358  return '[Null]';
359  }
360 
361  if ( is_bool( $item ) ) {
362  return $item ? 'true' : 'false';
363  }
364 
365  if ( is_float( $item ) ) {
366  if ( is_infinite( $item ) ) {
367  return ( $item > 0 ? '' : '-' ) . 'INF';
368  }
369  if ( is_nan( $item ) ) {
370  return 'NaN';
371  }
372  return (string)$item;
373  }
374 
375  if ( is_scalar( $item ) ) {
376  return (string)$item;
377  }
378 
379  if ( is_array( $item ) ) {
380  return '[Array(' . count( $item ) . ')]';
381  }
382 
383  if ( $item instanceof \DateTime ) {
384  return $item->format( 'c' );
385  }
386 
387  if ( $item instanceof Exception ) {
388  return '[Exception ' . get_class( $item ) . '( ' .
389  $item->getFile() . ':' . $item->getLine() . ') ' .
390  $item->getMessage() . ']';
391  }
392 
393  if ( is_object( $item ) ) {
394  if ( method_exists( $item, '__toString' ) ) {
395  return (string)$item;
396  }
397 
398  return '[Object ' . get_class( $item ) . ']';
399  }
400 
401  if ( is_resource( $item ) ) {
402  return '[Resource ' . get_resource_type( $item ) . ']';
403  }
404 
405  return '[Unknown ' . gettype( $item ) . ']';
406  }
407 
418  protected static function destination( $channel, $message, $context ) {
420 
421  // Default destination is the debug log file as historically used by
422  // the wfDebug function.
423  $destination = $wgDebugLogFile;
424 
425  if ( isset( $context['destination'] ) ) {
426  // Use destination explicitly provided in context
427  $destination = $context['destination'];
428 
429  } elseif ( $channel === 'wfDebug' ) {
430  $destination = $wgDebugLogFile;
431 
432  } elseif ( $channel === 'wfLogDBError' ) {
433  $destination = $wgDBerrorLog;
434 
435  } elseif ( isset( $wgDebugLogGroups[$channel] ) ) {
436  $logConfig = $wgDebugLogGroups[$channel];
437 
438  if ( is_array( $logConfig ) ) {
439  $destination = $logConfig['destination'];
440  } else {
441  $destination = strval( $logConfig );
442  }
443  }
444 
445  return $destination;
446  }
447 
457  public static function emit( $text, $file ) {
458  if ( substr( $file, 0, 4 ) == 'udp:' ) {
459  $transport = UDPTransport::newFromString( $file );
460  $transport->emit( $text );
461  } else {
462  \Wikimedia\suppressWarnings();
463  $exists = file_exists( $file );
464  $size = $exists ? filesize( $file ) : false;
465  if ( !$exists ||
466  ( $size !== false && $size + strlen( $text ) < 0x7fffffff )
467  ) {
468  file_put_contents( $file, $text, FILE_APPEND );
469  }
470  \Wikimedia\restoreWarnings();
471  }
472  }
473 
474 }
MediaWiki\Logger\LegacyLogger\interpolate
static interpolate( $message, array $context)
Interpolate placeholders in logging message.
Definition: LegacyLogger.php:338
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:440
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:356
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:418
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:1954
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:1392
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:2084
MediaWiki\Logger\LegacyLogger\formatAsWfDebugLog
static formatAsWfDebugLog( $channel, $message, $context)
Format a message as `wfDebugLog() would have formatted it.
Definition: LegacyLogger.php:323
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:393
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:6244
$wgLogExceptionBacktrace
$wgLogExceptionBacktrace
If true, send the exception backtrace to the error log.
Definition: DefaultSettings.php:6327
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
$time
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1841
array
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
$wgDebugLogFile
$wgDebugLogFile
Filename for debug logging.
Definition: DefaultSettings.php:6114
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:2644
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2213
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:276
MediaWiki\Logger\LegacyLogger\emit
static emit( $text, $file)
Log to a file without getting "file size exceeded" signals.
Definition: LegacyLogger.php:457
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:200
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:2036
MediaWiki\Logger\LegacyLogger\formatAsWfLogDBError
static formatAsWfLogDBError( $channel, $message, $context)
Format a message as wfLogDBError() would have formatted it.
Definition: LegacyLogger.php:297
MediaWiki\Logger\LegacyLogger
PSR-3 logger that mimics the historic implementation of MediaWiki's former wfErrorLog logging impleme...
Definition: LegacyLogger.php:48
$wgDBerrorLogTZ
$wgDBerrorLogTZ
Timezone to use in the error log.
Definition: DefaultSettings.php:2104
MediaWiki\Logger\LegacyLogger\__construct
__construct( $channel)
Definition: LegacyLogger.php:83
MediaWiki\$context
IContextSource $context
Definition: MediaWiki.php:38
MediaWiki\Logger\LegacyLogger\shouldEmit
static shouldEmit( $channel, $message, $level, $context)
Determine if the given message should be emitted or not.
Definition: LegacyLogger.php:136