Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
EventBusMonologHandler
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
2 / 2
4
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 write
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3namespace MediaWiki\Extension\EventBus\Adapters\Monolog;
4
5use MediaWiki\Deferred\DeferredUpdates;
6use MediaWiki\Extension\EventBus\EventBus;
7use MediaWiki\Extension\EventBus\EventBusFactory;
8use MediaWiki\Extension\EventBus\EventBusSendUpdate;
9use MediaWiki\MediaWikiServices;
10use Monolog\Handler\AbstractProcessingHandler;
11use Monolog\LogRecord;
12use Psr\Log\LogLevel;
13
14/**
15 * Log handler that supports sending messages to Kafka over
16 * EventBus and EventGate service.
17 *
18 * @since 1.33
19 * @copyright © 2019 Wikimedia Foundation and contributors
20 * @author Petr Pchelko <ppchelko@wikimedia.org>
21 */
22class EventBusMonologHandler extends AbstractProcessingHandler {
23
24    private EventBusFactory $eventBusFactory;
25
26    /**
27     * The name of the event service to use.
28     * @var string
29     */
30    private string $eventServiceName;
31
32    /**
33     * EventBusHandler constructor.
34     *
35     * @param string $eventServiceName the name of the event service to use
36     * @param int|string $level The minimum logging level at which this handler will be triggered
37     * @param bool $bubble Whether the messages that are handled can bubble up the stack or not
38     */
39    public function __construct( $eventServiceName, $level = LogLevel::DEBUG, $bubble = true ) {
40        parent::__construct( $level, $bubble );
41
42        $this->eventBusFactory = MediaWikiServices::getInstance()
43            ->get( 'EventBus.EventBusFactory' );
44        $this->eventServiceName = $eventServiceName;
45    }
46
47    /**
48     * Assumes that $record['context'] contains the event to send via EventBus.
49     */
50    protected function write( array|LogRecord $record ): void {
51        // Use the log record context as formatted as the event data
52        // but skip fields from the global diagnostic context (T433457)
53        $event = [];
54        foreach ( $record['context'] as $key => $value ) {
55            if ( !str_starts_with( $key, 'context.' ) ) {
56                $event[ $key ] = $value;
57            }
58        }
59
60        // wfDebugLog() adds a field called 'private' to the context
61        // that does not belong in the event. Delete the 'private' field here and
62        // then let EventBus serialize the log context to JSON string and send it.
63        // NOTE: we could create a custom formatter for EventBus, but all
64        // it would do is exactly this.
65        unset( $event['private'] );
66
67        // Events via Monolog might have binary strings in them.
68        // We need to be sure that any binary data is first encoded.
69        EventBus::replaceBinaryValuesRecursive( $event );
70
71        DeferredUpdates::addUpdate(
72            new EventBusSendUpdate(
73                $this->eventBusFactory,
74                $this->eventServiceName,
75                [ $event ]
76            )
77        );
78    }
79}