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