Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 49
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 / 49
0.00% covered (danger)
0.00%
0 / 3
210
0.00% covered (danger)
0.00%
0 / 1
 getLogVariables
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
42
 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        // @phan-suppress-next-line PhanImpossibleCondition
39        if ( !empty( $lines ) ) {
40            $vars += $this->extractRequestVariables(
41                array_slice( $lines, 0, 2 )
42            );
43        }
44
45        $responseData = $this->response->getData();
46        if ( !empty( $responseData['responses'] ) ) {
47            // Have to use + $vars, rather than +=, to
48            // allow 'suggestion' returned from here to
49            // override 'suggestion' provided by $this->extra
50            $vars = $this->extractResponseVariables(
51                reset( $responseData['responses'] )
52            ) + $vars;
53        }
54
55        // in case of failures from Elastica
56        if ( isset( $responseData['message'] ) ) {
57            $vars['error_message'] = $responseData['message'];
58        }
59
60        return $vars;
61    }
62
63    /**
64     * @return array[]
65     */
66    public function getRequests() {
67        if ( !$this->request || !$this->response ) {
68            // we don't actually know at this point how many searches there were,
69            // or how many results to return...so just bail and return nothing
70            return [];
71        }
72
73        $responseData = $this->response->getData();
74        if ( !$responseData || !isset( $responseData['responses'] ) ) {
75            $message = $responseData['message'] ?? 'no message';
76            LoggerFactory::getInstance( 'CirrusSearch' )->warning(
77                'Elasticsearch response does not have any data. {response_message}',
78                [ 'response_message' => $message ]
79            );
80            return [];
81        }
82
83        // In a multi-search instance the plain string, as sent to elasticsearch,
84        // is returned by Request::getData(). Each single request is represented
85        // by two lines, first a metadata line about the request and second the
86        // actual query.
87        $lines = explode( "\n", trim( $this->request->getData(), "\n" ) );
88        $requestData = array_chunk( $lines, 2 );
89
90        if ( count( $requestData ) !== count( $responseData['responses'] ) ) {
91            // The world has ended...:(
92            // @todo add more context.
93            throw new \RuntimeException( 'Request and response data does not match' );
94        }
95
96        $meta = [
97            'queryType' => $this->queryType,
98            'tookMs' => $this->getTookMs(),
99        ] + $this->extra;
100        $requests = [];
101        foreach ( $responseData['responses'] as $singleResponseData ) {
102            $vars = $this->extractRequestVariables( array_shift( $requestData ) ) +
103                $this->extractResponseVariables( $singleResponseData );
104            $vars['hits'] = $this->extractHits( $singleResponseData );
105            // + $meta must come *after* extractResponseVariables, because
106            // items like 'suggestion' override data provided in $this->extra
107            $requests[] = $vars + $meta;
108        }
109
110        return $requests;
111    }
112
113    /**
114     * @param array $requestData
115     * @return array
116     */
117    protected function extractRequestVariables( $requestData ) {
118        // @todo error check decode
119        $meta = json_decode( $requestData[0], true );
120        $query = json_decode( $requestData[1], true );
121
122        return [
123            // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
124            'index' => implode( ',', $meta['index'] ),
125        ] + parent::extractRequestVariables( $query );
126    }
127}