Merge "Preferences: Remove href="#" from stub threshold sample link"
[lhc/web/wiklou.git] / includes / api / ApiMain.php
index 22232dd..8255269 100644 (file)
@@ -829,12 +829,17 @@ class ApiMain extends ApiBase {
                        'dnt',
                        'origin',
                        /* MediaWiki whitelist */
+                       'user-agent',
                        'api-user-agent',
                ] );
                foreach ( $requestedHeaders as $rHeader ) {
                        $rHeader = strtolower( trim( $rHeader ) );
                        if ( !isset( $allowedAuthorHeaders[$rHeader] ) ) {
-                               wfDebugLog( 'api', 'CORS preflight failed on requested header: ' . $rHeader );
+                               LoggerFactory::getInstance( 'api-warning' )->warning(
+                                       'CORS preflight failed on requested header: {header}', [
+                                               'header' => $rHeader
+                                       ]
+                               );
                                return false;
                        }
                }
@@ -1025,8 +1030,10 @@ class ApiMain extends ApiBase {
                } else {
                        // Something is seriously wrong
                        $config = $this->getConfig();
+                       // TODO: Avoid embedding arbitrary class names in the error code.
                        $class = preg_replace( '#^Wikimedia\\\Rdbms\\\#', '', get_class( $e ) );
                        $code = 'internal_api_error_' . $class;
+                       $data = [ 'errorclass' => get_class( $e ) ];
                        if ( $config->get( 'ShowExceptionDetails' ) ) {
                                if ( $e instanceof ILocalizedException ) {
                                        $msg = $e->getMessageObject();
@@ -1040,7 +1047,7 @@ class ApiMain extends ApiBase {
                                $params = [ 'apierror-exceptioncaughttype', WebRequest::getRequestId(), get_class( $e ) ];
                        }
 
-                       $messages[] = ApiMessage::create( $params, $code );
+                       $messages[] = ApiMessage::create( $params, $code, $data );
                }
                return $messages;
        }
@@ -1077,7 +1084,15 @@ class ApiMain extends ApiBase {
                // Add errors from the exception
                $modulePath = $e instanceof ApiUsageException ? $e->getModulePath() : null;
                foreach ( $this->errorMessagesFromException( $e, 'error' ) as $msg ) {
-                       $errorCodes[$msg->getApiCode()] = true;
+                       if ( ApiErrorFormatter::isValidApiCode( $msg->getApiCode() ) ) {
+                               $errorCodes[$msg->getApiCode()] = true;
+                       } else {
+                               LoggerFactory::getInstance( 'api-warning' )->error( 'Invalid API error code "{code}"', [
+                                       'code' => $msg->getApiCode(),
+                                       'exception' => $e,
+                               ] );
+                               $errorCodes['<invalid-code>'] = true;
+                       }
                        $formatter->addError( $modulePath, $msg );
                }
                foreach ( $this->errorMessagesFromException( $e, 'warning' ) as $msg ) {
@@ -1262,8 +1277,8 @@ class ApiMain extends ApiBase {
                        if ( $lagInfo['lag'] > $maxLag ) {
                                $response = $this->getRequest()->response();
 
-                               $response->header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
-                               $response->header( 'X-Database-Lag: ' . intval( $lagInfo['lag'] ) );
+                               $response->header( 'Retry-After: ' . max( (int)$maxLag, 5 ) );
+                               $response->header( 'X-Database-Lag: ' . (int)$lagInfo['lag'] );
 
                                if ( $this->getConfig()->get( 'ShowHostnames' ) ) {
                                        $this->dieWithError(
@@ -1464,9 +1479,14 @@ class ApiMain extends ApiBase {
                if ( $numLagged >= ceil( $replicaCount / 2 ) ) {
                        $laggedServers = implode( ', ', $laggedServers );
                        wfDebugLog(
-                               'api-readonly',
+                               'api-readonly', // Deprecate this channel in favor of api-warning?
                                "Api request failed as read only because the following DBs are lagged: $laggedServers"
                        );
+                       LoggerFactory::getInstance( 'api-warning' )->warning(
+                               "Api request failed as read only because the following DBs are lagged: {laggeddbs}", [
+                                       'laggeddbs' => $laggedServers,
+                               ]
+                       );
 
                        $this->dieWithError(
                                'readonly_lag',
@@ -1612,7 +1632,7 @@ class ApiMain extends ApiBase {
         */
        protected function logRequest( $time, $e = null ) {
                $request = $this->getRequest();
-               $logCtx = [
+               $legacyLogCtx = [
                        'ts' => time(),
                        'ip' => $request->getIP(),
                        'userAgent' => $this->getUserAgent(),
@@ -1623,17 +1643,43 @@ class ApiMain extends ApiBase {
                        'params' => [],
                ];
 
+               $logCtx = [
+                       '$schema' => '/mediawiki/api/request/0.0.1',
+                       'meta' => [
+                               'id' => UIDGenerator::newUUIDv1(),
+                               'dt' => gmdate( 'c' ),
+                               'domain' => $this->getConfig()->get( 'ServerName' ),
+                               'stream' => 'mediawiki.api-request'
+                       ],
+                       'http' => [
+                               'method' => $request->getMethod(),
+                               'client_ip' => $request->getIP(),
+                               'request_headers' => [
+                                       'user-agent' => $request->getHeader( 'User-agent' ),
+                                       'api-user-agent' => $request->getHeader( 'Api-user-agent' )
+                               ],
+                       ],
+                       'database' => wfWikiID(),
+                       'backend_time_ms' => (int)round( $time * 1000 ),
+                       'params' => []
+               ];
+
+               $logCtx['meta']['request_id'] =
+                       $logCtx['http']['request_headers']['x-request-id'] = WebRequest::getRequestId();
+
                if ( $e ) {
+                       $logCtx['api_error_codes'] = [];
                        foreach ( $this->errorMessagesFromException( $e ) as $msg ) {
-                               $logCtx['errorCodes'][] = $msg->getApiCode();
+                               $legacyLogCtx['errorCodes'][] = $msg->getApiCode();
+                               $logCtx['api_error_codes'][] = $msg->getApiCode();
                        }
                }
 
                // Construct space separated message for 'api' log channel
                $msg = "API {$request->getMethod()} " .
                        wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) .
-                       " {$logCtx['ip']} " .
-                       "T={$logCtx['timeSpentBackend']}ms";
+                       " {$legacyLogCtx['ip']} " .
+                       "T={$legacyLogCtx['timeSpentBackend']}ms";
 
                $sensitive = array_flip( $this->getSensitiveParams() );
                foreach ( $this->getParamsUsed() as $name ) {
@@ -1652,13 +1698,17 @@ class ApiMain extends ApiBase {
                                $encValue = $this->encodeRequestLogValue( $value );
                        }
 
+                       $legacyLogCtx['params'][$name] = $value;
                        $logCtx['params'][$name] = $value;
                        $msg .= " {$name}={$encValue}";
                }
 
                wfDebugLog( 'api', $msg, 'private' );
-               // ApiAction channel is for structured data consumers
-               wfDebugLog( 'ApiAction', '', 'private', $logCtx );
+               // ApiAction channel is for structured data consumers.
+               // The ApiAction was using logging channel is deprecated and is replaced
+               // by the api-request channel.
+               wfDebugLog( 'ApiAction', '', 'private', $legacyLogCtx );
+               wfDebugLog( 'api-request', '', 'private', $logCtx );
        }
 
        /**