Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 48
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
MultiSearchRequestLog
0.00% covered (danger)
0.00%
0 / 48
0.00% covered (danger)
0.00%
0 / 3
182
0.00% covered (danger)
0.00%
0 / 1
 getLogVariables
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
30
 getRequests
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
56
 extractRequestVariables
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace CirrusSearch;
4
5use MediaWiki\Logger\LoggerFactory;
6
7/**
8 * Extending from SearchRequestLog doesn't quite feel right, but there
9 * is a good amount of shared code. think about best way.
10 */
11class MultiSearchRequestLog extends SearchRequestLog {
12
13    /**
14     * Not sure what's best to return here, primarily we need whatever
15     * would be interesting when looking at error logging. For now this
16     * just generates a context for the first request and ignores the
17     * existence of the rest.
18     *
19     * Basically this is known to be wrong, but not sure what to do instead.
20     *
21     * @return array
22     */
23    public function getLogVariables() {
24        $vars = [
25            'queryType' => $this->queryType,
26            'tookMs' => $this->getTookMs(),
27        ] + $this->extra;
28
29        if ( !$this->request || !$this->response ) {
30            return $vars;
31        }
32
33        // In a multi-search instance the plain string, as sent to elasticsearch,
34        // is returned by Request::getData(). Each single request is represented
35        // by two lines, first a metadata line about the request and second the
36        // actual query.
37        $lines = explode( "\n", trim( $this->request->getData(), "\n" ) );
38        $vars += $this->extractRequestVariables(
39            array_slice( $lines, 0, 2 )
40        );
41
42        $responseData = $this->response->getData();
43        if ( !empty( $responseData['responses'] ) ) {
44            // Have to use + $vars, rather than +=, to
45            // allow 'suggestion' returned from here to
46            // override 'suggestion' provided by $this->extra
47            $vars = $this->extractResponseVariables(
48                reset( $responseData['responses'] )
49            ) + $vars;
50        }
51
52        // in case of failures from Elastica
53        if ( isset( $responseData['message'] ) ) {
54            $vars['error_message'] = $responseData['message'];
55        }
56
57        return $vars;
58    }
59
60    /**
61     * @return array[]
62     */
63    public function getRequests() {
64        if ( !$this->request || !$this->response ) {
65            // we don't actually know at this point how many searches there were,
66            // or how many results to return...so just bail and return nothing
67            return [];
68        }
69
70        $responseData = $this->response->getData();
71        if ( !$responseData || !isset( $responseData['responses'] ) ) {
72            $message = $responseData['message'] ?? 'no message';
73            LoggerFactory::getInstance( 'CirrusSearch' )->warning(
74                'Elasticsearch response does not have any data. {response_message}',
75                [ 'response_message' => $message ]
76            );
77            return [];
78        }
79
80        // In a multi-search instance the plain string, as sent to elasticsearch,
81        // is returned by Request::getData(). Each single request is represented
82        // by two lines, first a metadata line about the request and second the
83        // actual query.
84        $lines = explode( "\n", trim( $this->request->getData(), "\n" ) );
85        $requestData = array_chunk( $lines, 2 );
86
87        if ( count( $requestData ) !== count( $responseData['responses'] ) ) {
88            // The world has ended...:(
89            // @todo add more context.
90            throw new \RuntimeException( 'Request and response data does not match' );
91        }
92
93        $meta = [
94            'queryType' => $this->queryType,
95            'tookMs' => $this->getTookMs(),
96        ] + $this->extra;
97        $requests = [];
98        foreach ( $responseData['responses'] as $singleResponseData ) {
99            $vars = $this->extractRequestVariables( array_shift( $requestData ) ) +
100                $this->extractResponseVariables( $singleResponseData );
101            $vars['hits'] = $this->extractHits( $singleResponseData );
102            // + $meta must come *after* extractResponseVariables, because
103            // items like 'suggestion' override data provided in $this->extra
104            $requests[] = $vars + $meta;
105        }
106
107        return $requests;
108    }
109
110    /**
111     * @param array $requestData
112     * @return array
113     */
114    protected function extractRequestVariables( $requestData ) {
115        // @todo error check decode
116        $meta = json_decode( $requestData[0], true );
117        $query = json_decode( $requestData[1], true );
118
119        return [
120            // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
121            'index' => implode( ',', $meta['index'] ),
122        ] + parent::extractRequestVariables( $query );
123    }
124}