LogstashFormatter.php 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. <?php
  2. namespace MediaWiki\Logger\Monolog;
  3. /**
  4. * LogstashFormatter squashes the base message array and the context and extras subarrays into one.
  5. * This can result in unfortunately named context fields overwriting other data (T145133).
  6. * This class modifies the standard LogstashFormatter to rename such fields and flag the message.
  7. * Also changes exception JSON-ification which is done poorly by the standard class.
  8. *
  9. * Compatible with Monolog 1.x only.
  10. *
  11. * @since 1.29
  12. */
  13. class LogstashFormatter extends \Monolog\Formatter\LogstashFormatter {
  14. /** @var array Keys which should not be used in log context */
  15. protected $reservedKeys = [
  16. // from LogstashFormatter
  17. 'message', 'channel', 'level', 'type',
  18. // from WebProcessor
  19. 'url', 'ip', 'http_method', 'server', 'referrer',
  20. // from WikiProcessor
  21. 'host', 'wiki', 'reqId', 'mwversion',
  22. // from config magic
  23. 'normalized_message',
  24. ];
  25. /**
  26. * Prevent key conflicts
  27. * @param array $record
  28. * @return array
  29. */
  30. protected function formatV0( array $record ) {
  31. if ( $this->contextPrefix ) {
  32. return parent::formatV0( $record );
  33. }
  34. $context = !empty( $record['context'] ) ? $record['context'] : [];
  35. $record['context'] = [];
  36. $formatted = parent::formatV0( $record );
  37. $formatted['@fields'] = $this->fixKeyConflicts( $formatted['@fields'], $context );
  38. return $formatted;
  39. }
  40. /**
  41. * Prevent key conflicts
  42. * @param array $record
  43. * @return array
  44. */
  45. protected function formatV1( array $record ) {
  46. if ( $this->contextPrefix ) {
  47. return parent::formatV1( $record );
  48. }
  49. $context = !empty( $record['context'] ) ? $record['context'] : [];
  50. $record['context'] = [];
  51. $formatted = parent::formatV1( $record );
  52. $formatted = $this->fixKeyConflicts( $formatted, $context );
  53. return $formatted;
  54. }
  55. /**
  56. * Check whether some context field would overwrite another message key. If so, rename
  57. * and flag.
  58. * @param array $fields Fields to be sent to logstash
  59. * @param array $context Copy of the original $record['context']
  60. * @return array Updated version of $fields
  61. */
  62. protected function fixKeyConflicts( array $fields, array $context ) {
  63. foreach ( $context as $key => $val ) {
  64. if (
  65. in_array( $key, $this->reservedKeys, true ) &&
  66. isset( $fields[$key] ) && $fields[$key] !== $val
  67. ) {
  68. $fields['logstash_formatter_key_conflict'][] = $key;
  69. $key = 'c_' . $key;
  70. }
  71. $fields[$key] = $val;
  72. }
  73. return $fields;
  74. }
  75. /**
  76. * Use a more user-friendly trace format than NormalizerFormatter
  77. * @param \Exception|\Throwable $e
  78. * @return array
  79. */
  80. protected function normalizeException( $e ) {
  81. if ( !$e instanceof \Exception && !$e instanceof \Throwable ) {
  82. throw new \InvalidArgumentException( 'Exception/Throwable expected, got '
  83. . gettype( $e ) . ' / ' . get_class( $e ) );
  84. }
  85. $data = [
  86. 'class' => get_class( $e ),
  87. 'message' => $e->getMessage(),
  88. 'code' => $e->getCode(),
  89. 'file' => $e->getFile() . ':' . $e->getLine(),
  90. 'trace' => \MWExceptionHandler::getRedactedTraceAsString( $e ),
  91. ];
  92. $previous = $e->getPrevious();
  93. if ( $previous ) {
  94. $data['previous'] = $this->normalizeException( $previous );
  95. }
  96. return $data;
  97. }
  98. }