import type { TPromsterOptions , TDefaultedPromsterOptions , THttpMetrics , TGcMetrics , } from '@promster/types' ; import type { TRequestRecorder , TPromsterTiming } from '@promster/metrics' ; import type { Plugin , Request , ResponseObject , ResponseToolkit , } from '@hapi/hapi' ; import type { Boom } from '@hapi/boom' ; import semver from 'semver' ; import merge from 'merge-options' ; import pkg from '../../package.json' ; import { Prometheus , createHttpMetrics , createGcMetrics , createRequestRecorder , createGcObserver , defaultNormalizers , skipMetricsInEnvironment , timing , } from '@promster/metrics' ; interface TPromsterRequest extends Request { plugins : { promster : { timing : TPromsterTiming ; } ; } ; } const extractPath = ( request ) => request . route . path . replace ( /\?/g , '' ) ; type TResponse = ResponseObject | Boom ; const isBoomResponse = ( response ) : response is Boom => ( response as Boom ) . isBoom ; const extractStatusCode = ( request ) => { const { response } = request ; if ( ! response ) { return 0 ; } if ( isBoomResponse ( response ) ) { return response . output . statusCode ; } return response . statusCode ; } ; let recordRequest ; let upMetric ; const getRequestRecorder = ( ) => recordRequest ; const signalIsUp = ( ) => { if ( ! upMetric ) { return ; } upMetric . forEach ( ( upMetricType ) => { upMetricType . set ( 1 ) ; } ) ; } ; const signalIsNotUp = ( ) => { if ( ! upMetric ) { return ; } upMetric . forEach ( ( upMetricType ) => { upMetricType . set ( 0 ) ; } ) ; } ; const getAreServerEventsSupported = ( actualVersion ) => Boolean ( actualVersion && semver . satisfies ( actualVersion , '>= 17.0.0' ) ) ; const getDoesResponseNeedInvocation = ( actualVersion ) => Boolean ( actualVersion && semver . satisfies ( actualVersion , '< 17.0.0' ) ) ; const createPlugin = ( { options : pluginOptions , } = { options : undefined } ) => { const allDefaultedOptions = merge ( createHttpMetrics . defaultOptions , createGcMetrics . defaultOptions , createRequestRecorder . defaultOptions , createGcObserver . defaultOptions , defaultNormalizers , pluginOptions ) ; const shouldSkipMetricsByEnvironment = skipMetricsInEnvironment ( allDefaultedOptions ) ; const httpMetrics = createHttpMetrics ( allDefaultedOptions ) ; const gcMetrics = createGcMetrics ( allDefaultedOptions ) ; const observeGc = createGcObserver ( gcMetrics , allDefaultedOptions ) ; recordRequest = createRequestRecorder ( httpMetrics , allDefaultedOptions ) ; upMetric = gcMetrics ?. up ; if ( ! shouldSkipMetricsByEnvironment ) { observeGc ( ) ; } const plugin = { name : pkg . name , version : pkg . version , register ( server , _registrationOptions , onRegistrationFinished = ( ) => null ) { const areServerEventsSupported = getAreServerEventsSupported ( server . version ) ; const doesResponseNeedInvocation = getDoesResponseNeedInvocation ( server . version ) ; const onRequestHandler = ( request , h ) => { request . plugins . promster = { timing : timing . start ( ) } ; return doesResponseNeedInvocation ? h . continue ( ) : h . continue ; } ; const onResponseHandler = ( request , response ) => { const labels = Object . assign ( { } , { path : allDefaultedOptions . normalizePath ( extractPath ( request ) , { req : request , res : response , } ) , method : allDefaultedOptions . normalizeMethod ( request . method , { req : request , res : response , } ) , status_code : allDefaultedOptions . normalizeStatusCode ( extractStatusCode ( request ) , { req : request , res : response , } ) , } , allDefaultedOptions . getLabelValues ?. ( request , { } ) ) ; const requestContentLength = Number ( request ?. headers ?. [ 'content-length' ] ?? 0 ) ; const responseContentLength = Number ( request ?. response ?. headers ?. [ 'content-length' ] ?? 0 ) ; const shouldSkipByRequest = allDefaultedOptions . skip ?. ( request , response , labels ) ; if ( ! shouldSkipByRequest && ! shouldSkipMetricsByEnvironment ) { recordRequest ( request . plugins . promster . timing , { labels , requestContentLength , responseContentLength , } ) ; } if ( doesResponseNeedInvocation ) response . continue ( ) ; } ; if ( areServerEventsSupported ) { server . ext ( 'onRequest' , onRequestHandler ) ; server . events . on ( 'response' , onResponseHandler ) ; } else { server . ext ( 'onRequest' , onRequestHandler ) ; server . ext ( 'onPreResponse' , onResponseHandler ) ; } server . decorate ( 'server' , 'Prometheus' , Prometheus ) ; server . decorate ( 'server' , 'recordRequest' , recordRequest ) ; return onRegistrationFinished ?. ( ) ; } , } ; plugin . register . attributes = { pkg , } ; return plugin ; } ; export { createPlugin , getRequestRecorder , signalIsUp , signalIsNotUp , getAreServerEventsSupported , getDoesResponseNeedInvocation , } ;