import { Buffer } from 'buffer' ; import { stringify } from 'querystring' ; import { errors , DiagnosticResult , RequestBody , Client } from '@elastic/elasticsearch' ; import type { ElasticsearchErrorDetails } from './types' ; import { Logger } from '../../logging' ; const convertQueryString = ( qs ) : string => { if ( qs === undefined || typeof qs === 'string' ) { return qs ?? '' ; } return stringify ( qs ) ; } ; function ensureString ( body ) { if ( typeof body === 'string' ) return body ; if ( Buffer . isBuffer ( body ) ) return '[buffer]' ; if ( 'readable' in body && body . readable && typeof body . _read === 'function' ) return '[stream]' ; return JSON . stringify ( body ) ; } /**
 * Returns a debug message from an Elasticsearch error in the following format:
 * [error type] error reason
 */ export function getErrorMessage ( error ) { if ( error instanceof errors . ResponseError ) { const errorBody = error . meta . body as ElasticsearchErrorDetails ; return `[${ errorBody ?. error ?. type }]: ${ errorBody ?. error ?. reason ?? error . message }` ; } return `[${ error . name }]: ${ error . message }` ; } /**
 * returns a string in format:
 *
 * status code
 * method URL
 * request body
 *
 * so it could be copy-pasted into the Dev console
 */ function getResponseMessage ( event ) { const errorMeta = getRequestDebugMeta ( event ) ; const body = errorMeta . body ? `\n${ errorMeta . body }` : '' ; return `${ errorMeta . statusCode }\n${ errorMeta . method } ${ errorMeta . url }${ body }` ; } /**
 * Returns stringified debug information from an Elasticsearch request event
 * useful for logging in case of an unexpected failure.
 */ export function getRequestDebugMeta ( event ) { const params = event . meta . request . params ; const querystring = convertQueryString ( params . querystring ) ; return { url : `${ params . path }${ querystring ? `?${ querystring }` : '' }` , body : params . body ? `${ ensureString ( params . body ) }` : '' , method : params . method , statusCode : event . statusCode ! , } ; } /** HTTP Warning headers have the following syntax:
 * <warn-code> <warn-agent> <warn-text> (where warn-code is a three digit number)
 * This function tests if a warning comes from an Elasticsearch warn-agent
 * */ const isEsWarning = ( warning ) => /\d\d\d Elasticsearch-/ . test ( warning ) ; export const instrumentEsQueryAndDeprecationLogger = ( { logger , client , type , } ) => { const queryLogger = logger . get ( 'query' , type ) ; const deprecationLogger = logger . get ( 'deprecation' ) ; client . diagnostic . on ( 'response' , ( error , event ) => { if ( event ) { const opaqueId = event . meta . request . options . opaqueId ; const meta = opaqueId ? { http : { request : { id : event . meta . request . options . opaqueId } } , } : undefined ; let queryMsg = '' ; if ( error ) { if ( error instanceof errors . ResponseError ) { queryMsg = `${ getResponseMessage ( event ) } ${ getErrorMessage ( error ) }` ; } else { queryMsg = getErrorMessage ( error ) ; } } else { queryMsg = getResponseMessage ( event ) ; } queryLogger . debug ( queryMsg , meta ) ; if ( event . warnings && event . warnings . filter ( isEsWarning ) . length > 0 ) { const requestOrigin = ( event . meta . request . params . headers != null && ( event . meta . request . params . headers [ 'x-elastic-product-origin' ] as unknown as string ) ) === 'kibana' ? 'kibana' : 'user' ; const stackTrace = new Error ( ) . stack ?. split ( '\n' ) . slice ( 5 ) . join ( '\n' ) ; const deprecationMsg = `Elasticsearch deprecation: ${ event . warnings }\nOrigin:${ requestOrigin }\nStack trace:\n${ stackTrace }\nQuery:\n${ queryMsg }` ; if ( requestOrigin === 'kibana' ) { deprecationLogger . info ( deprecationMsg ) ; } else { deprecationLogger . debug ( deprecationMsg ) ; } } } } ) ; } ;