MediaWiki  1.28.0
LegacyLogger.php
Go to the documentation of this file.
1 <?php
21 namespace MediaWiki\Logger;
22 
25 use MWDebug;
30 
49 class LegacyLogger extends AbstractLogger {
50 
54  protected $channel;
55 
62  protected static $levelMapping = [
63  LogLevel::DEBUG => 100,
64  LogLevel::INFO => 200,
65  LogLevel::NOTICE => 250,
66  LogLevel::WARNING => 300,
67  LogLevel::ERROR => 400,
68  LogLevel::CRITICAL => 500,
69  LogLevel::ALERT => 550,
70  LogLevel::EMERGENCY => 600,
71  ];
72 
76  protected static $dbChannels = [
77  'DBQuery' => true,
78  'DBConnection' => true
79  ];
80 
84  public function __construct( $channel ) {
85  $this->channel = $channel;
86  }
87 
96  public function log( $level, $message, array $context = [] ) {
97  if ( is_string( $level ) ) {
98  $level = self::$levelMapping[$level];
99  }
100  if ( $this->channel === 'DBQuery' && isset( $context['method'] )
101  && isset( $context['master'] ) && isset( $context['runtime'] )
102  ) {
103  MWDebug::query( $message, $context['method'], $context['master'], $context['runtime'] );
104  return; // only send profiling data to MWDebug profiling
105  }
106 
107  if ( isset( self::$dbChannels[$this->channel] )
108  && $level >= self::$levelMapping[LogLevel::ERROR]
109  ) {
110  // Format and write DB errors to the legacy locations
111  $effectiveChannel = 'wfLogDBError';
112  } else {
113  $effectiveChannel = $this->channel;
114  }
115 
116  if ( self::shouldEmit( $effectiveChannel, $message, $level, $context ) ) {
117  $text = self::format( $effectiveChannel, $message, $context );
118  $destination = self::destination( $effectiveChannel, $message, $context );
119  self::emit( $text, $destination );
120  }
121  if ( !isset( $context['private'] ) || !$context['private'] ) {
122  // Add to debug toolbar if not marked as "private"
123  MWDebug::debugMsg( $message, [ 'channel' => $this->channel ] + $context );
124  }
125  }
126 
137  public static function shouldEmit( $channel, $message, $level, $context ) {
138  global $wgDebugLogFile, $wgDBerrorLog, $wgDebugLogGroups;
139 
140  if ( is_string( $level ) ) {
141  $level = self::$levelMapping[$level];
142  }
143 
144  if ( $channel === 'wfLogDBError' ) {
145  // wfLogDBError messages are emitted if a database log location is
146  // specfied.
147  $shouldEmit = (bool)$wgDBerrorLog;
148 
149  } elseif ( $channel === 'wfErrorLog' ) {
150  // All messages on the wfErrorLog channel should be emitted.
151  $shouldEmit = true;
152 
153  } elseif ( $channel === 'wfDebug' ) {
154  // wfDebug messages are emitted if a catch all logging file has
155  // been specified. Checked explicitly so that 'private' flagged
156  // messages are not discarded by unset $wgDebugLogGroups channel
157  // handling below.
158  $shouldEmit = $wgDebugLogFile != '';
159 
160  } elseif ( isset( $wgDebugLogGroups[$channel] ) ) {
161  $logConfig = $wgDebugLogGroups[$channel];
162 
163  if ( is_array( $logConfig ) ) {
164  $shouldEmit = true;
165  if ( isset( $logConfig['sample'] ) ) {
166  // Emit randomly with a 1 in 'sample' chance for each message.
167  $shouldEmit = mt_rand( 1, $logConfig['sample'] ) === 1;
168  }
169 
170  if ( isset( $logConfig['level'] ) ) {
171  $shouldEmit = $level >= self::$levelMapping[$logConfig['level']];
172  }
173  } else {
174  // Emit unless the config value is explictly false.
175  $shouldEmit = $logConfig !== false;
176  }
177 
178  } elseif ( isset( $context['private'] ) && $context['private'] ) {
179  // Don't emit if the message didn't match previous checks based on
180  // the channel and the event is marked as private. This check
181  // discards messages sent via wfDebugLog() with dest == 'private'
182  // and no explicit wgDebugLogGroups configuration.
183  $shouldEmit = false;
184  } else {
185  // Default return value is the same as the historic wfDebug
186  // method: emit if $wgDebugLogFile has been set.
187  $shouldEmit = $wgDebugLogFile != '';
188  }
189 
190  return $shouldEmit;
191  }
192 
206  public static function format( $channel, $message, $context ) {
207  global $wgDebugLogGroups, $wgLogExceptionBacktrace;
208 
209  if ( $channel === 'wfDebug' ) {
210  $text = self::formatAsWfDebug( $channel, $message, $context );
211 
212  } elseif ( $channel === 'wfLogDBError' ) {
213  $text = self::formatAsWfLogDBError( $channel, $message, $context );
214 
215  } elseif ( $channel === 'wfErrorLog' ) {
216  $text = "{$message}\n";
217 
218  } elseif ( $channel === 'profileoutput' ) {
219  // Legacy wfLogProfilingData formatitng
220  $forward = '';
221  if ( isset( $context['forwarded_for'] ) ) {
222  $forward = " forwarded for {$context['forwarded_for']}";
223  }
224  if ( isset( $context['client_ip'] ) ) {
225  $forward .= " client IP {$context['client_ip']}";
226  }
227  if ( isset( $context['from'] ) ) {
228  $forward .= " from {$context['from']}";
229  }
230  if ( $forward ) {
231  $forward = "\t(proxied via {$context['proxy']}{$forward})";
232  }
233  if ( $context['anon'] ) {
234  $forward .= ' anon';
235  }
236  if ( !isset( $context['url'] ) ) {
237  $context['url'] = 'n/a';
238  }
239 
240  $log = sprintf( "%s\t%04.3f\t%s%s\n",
241  gmdate( 'YmdHis' ), $context['elapsed'], $context['url'], $forward );
242 
243  $text = self::formatAsWfDebugLog(
244  $channel, $log . $context['output'], $context );
245 
246  } elseif ( !isset( $wgDebugLogGroups[$channel] ) ) {
247  $text = self::formatAsWfDebug(
248  $channel, "[{$channel}] {$message}", $context );
249 
250  } else {
251  // Default formatting is wfDebugLog's historic style
252  $text = self::formatAsWfDebugLog( $channel, $message, $context );
253  }
254 
255  // Append stacktrace of exception if available
256  if ( $wgLogExceptionBacktrace && isset( $context['exception'] ) ) {
257  $e = $context['exception'];
258  $backtrace = false;
259 
260  if ( $e instanceof Exception ) {
262 
263  } elseif ( is_array( $e ) && isset( $e['trace'] ) ) {
264  // Exception has already been unpacked as structured data
265  $backtrace = $e['trace'];
266  }
267 
268  if ( $backtrace ) {
269  $text .= MWExceptionHandler::prettyPrintTrace( $backtrace ) .
270  "\n";
271  }
272  }
273 
274  return self::interpolate( $text, $context );
275  }
276 
285  protected static function formatAsWfDebug( $channel, $message, $context ) {
286  $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $message );
287  if ( isset( $context['seconds_elapsed'] ) ) {
288  // Prepend elapsed request time and real memory usage with two
289  // trailing spaces.
290  $text = "{$context['seconds_elapsed']} {$context['memory_used']} {$text}";
291  }
292  if ( isset( $context['prefix'] ) ) {
293  $text = "{$context['prefix']}{$text}";
294  }
295  return "{$text}\n";
296  }
297 
306  protected static function formatAsWfLogDBError( $channel, $message, $context ) {
308  static $cachedTimezone = null;
309 
310  if ( !$cachedTimezone ) {
311  $cachedTimezone = new DateTimeZone( $wgDBerrorLogTZ );
312  }
313 
314  $d = date_create( 'now', $cachedTimezone );
315  $date = $d->format( 'D M j G:i:s T Y' );
316 
317  $host = wfHostname();
318  $wiki = wfWikiID();
319 
320  $text = "{$date}\t{$host}\t{$wiki}\t{$message}\n";
321  return $text;
322  }
323 
332  protected static function formatAsWfDebugLog( $channel, $message, $context ) {
333  $time = wfTimestamp( TS_DB );
334  $wiki = wfWikiID();
335  $host = wfHostname();
336  $text = "{$time} {$host} {$wiki}: {$message}\n";
337  return $text;
338  }
339 
347  public static function interpolate( $message, array $context ) {
348  if ( strpos( $message, '{' ) !== false ) {
349  $replace = [];
350  foreach ( $context as $key => $val ) {
351  $replace['{' . $key . '}'] = self::flatten( $val );
352  }
353  $message = strtr( $message, $replace );
354  }
355  return $message;
356  }
357 
365  protected static function flatten( $item ) {
366  if ( null === $item ) {
367  return '[Null]';
368  }
369 
370  if ( is_bool( $item ) ) {
371  return $item ? 'true' : 'false';
372  }
373 
374  if ( is_float( $item ) ) {
375  if ( is_infinite( $item ) ) {
376  return ( $item > 0 ? '' : '-' ) . 'INF';
377  }
378  if ( is_nan( $item ) ) {
379  return 'NaN';
380  }
381  return $item;
382  }
383 
384  if ( is_scalar( $item ) ) {
385  return (string)$item;
386  }
387 
388  if ( is_array( $item ) ) {
389  return '[Array(' . count( $item ) . ')]';
390  }
391 
392  if ( $item instanceof \DateTime ) {
393  return $item->format( 'c' );
394  }
395 
396  if ( $item instanceof Exception ) {
397  return '[Exception ' . get_class( $item ) . '( ' .
398  $item->getFile() . ':' . $item->getLine() . ') ' .
399  $item->getMessage() . ']';
400  }
401 
402  if ( is_object( $item ) ) {
403  if ( method_exists( $item, '__toString' ) ) {
404  return (string)$item;
405  }
406 
407  return '[Object ' . get_class( $item ) . ']';
408  }
409 
410  if ( is_resource( $item ) ) {
411  return '[Resource ' . get_resource_type( $item ) . ']';
412  }
413 
414  return '[Unknown ' . gettype( $item ) . ']';
415  }
416 
427  protected static function destination( $channel, $message, $context ) {
428  global $wgDebugLogFile, $wgDBerrorLog, $wgDebugLogGroups;
429 
430  // Default destination is the debug log file as historically used by
431  // the wfDebug function.
432  $destination = $wgDebugLogFile;
433 
434  if ( isset( $context['destination'] ) ) {
435  // Use destination explicitly provided in context
436  $destination = $context['destination'];
437 
438  } elseif ( $channel === 'wfDebug' ) {
439  $destination = $wgDebugLogFile;
440 
441  } elseif ( $channel === 'wfLogDBError' ) {
442  $destination = $wgDBerrorLog;
443 
444  } elseif ( isset( $wgDebugLogGroups[$channel] ) ) {
445  $logConfig = $wgDebugLogGroups[$channel];
446 
447  if ( is_array( $logConfig ) ) {
448  $destination = $logConfig['destination'];
449  } else {
450  $destination = strval( $logConfig );
451  }
452  }
453 
454  return $destination;
455  }
456 
466  public static function emit( $text, $file ) {
467  if ( substr( $file, 0, 4 ) == 'udp:' ) {
468  $transport = UDPTransport::newFromString( $file );
469  $transport->emit( $text );
470  } else {
471  \MediaWiki\suppressWarnings();
472  $exists = file_exists( $file );
473  $size = $exists ? filesize( $file ) : false;
474  if ( !$exists ||
475  ( $size !== false && $size + strlen( $text ) < 0x7fffffff )
476  ) {
477  file_put_contents( $file, $text, FILE_APPEND );
478  }
479  \MediaWiki\restoreWarnings();
480  }
481  }
482 
483 }
static query($sql, $function, $isMaster, $runTime)
Begins profiling on a database query.
Definition: MWDebug.php:357
the array() calling protocol came about after MediaWiki 1.4rc1.
$wgDBerrorLogTZ
Timezone to use in the error log.
PSR-3 logger that mimics the historic implementation of MediaWiki's wfErrorLog logging implementation...
static debugMsg($str, $context=[])
This is a method to pass messages from wfDebug to the pretty debugger.
Definition: MWDebug.php:325
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException'returning false will NOT prevent logging $e
Definition: hooks.txt:2102
$wgDBerrorLog
File to log database errors to.
wfHostname()
Fetch server name for use in error reporting etc.
static shouldEmit($channel, $message, $level, $context)
Determine if the given message should be emitted or not.
static interpolate($message, array $context)
Interpolate placeholders in logging message.
static formatAsWfDebugLog($channel, $message, $context)
Format a message as `wfDebugLog() would have formatted it.
static destination($channel, $message, $context)
Select the appropriate log output destination for the given log event.
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
static emit($text, $file)
Log to a file without getting "file size exceeded" signals.
static formatAsWfLogDBError($channel, $message, $context)
Format a message as wfLogDBError() would have formatted it.
wfTimestamp($outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
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:1936
IContextSource $context
Definition: MediaWiki.php:33
static array __construct($channel)
static format($channel, $message, $context)
Format a message.
static flatten($item)
Convert a logging context element to a string suitable for interpolation.
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
static newFromString($info)
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
static formatAsWfDebug($channel, $message, $context)
Format a message as wfDebug() would have formatted it.
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
static getRedactedTrace($e)
Return a copy of an exception's backtrace as an array.
static prettyPrintTrace(array $trace, $pad= '')
Generate a string representation of a stacktrace.
log($level, $message, array $context=[])
Logs with an arbitrary level.
static $levelMapping
Convert \Psr\Log\LogLevel constants into int for sane comparisons These are the same values that Monl...
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1749
if the prop value should be in the metadata multi language array format
Definition: hooks.txt:1610
const TS_DB
MySQL DATETIME (YYYY-MM-DD HH:MM:SS)
Definition: defines.php:16