import debugLevels , { LeveledDebugger } from '@resolve-js/debug-levels' ; import type { MonitoringAdapter , MonitoringDimension , MonitoringMetric , MonitoringCustomMetric , } from '@resolve-js/core' ; import type { MonitoringContext , MonitoringGroupContext } from './types' ; const createErrorDimensions = ( error ) => [ { name : 'ErrorName' , value : error . name ?? 'Unknown' } , { name : 'ErrorMessage' , value : error . message ?? 'Unknown' } , ] ; const areDimensionsEqual = ( left , right ) => { if ( left . length !== right . length ) { return false ; } for ( let i = 0 ; i < left . length ; i ++ ) { if ( left [ i ] . name !== right [ i ] . name || left [ i ] . value !== right [ i ] . value ) { return false ; } } return true ; } ; const putMetric = ( log , monitoringContext , groupContext , metricItem ) => { try { log . verbose ( `Collecting metric` ) ; const timestamp = monitoringContext . getTimestamp ( ) ; let foundMetric = monitoringContext . metrics . find ( ( metric ) => metric . metricName === metricItem . metricName && metric . timestamp === timestamp && metric . unit === metricItem . unit && areDimensionsEqual ( metric . dimensions , metricItem . dimensions ) ) ; if ( foundMetric != null ) { let isValueFound = false ; for ( let i = 0 ; i < foundMetric . values . length ; i ++ ) { if ( foundMetric . values [ i ] === metricItem . value ) { foundMetric . counts [ i ] += metricItem . count ; isValueFound = true ; break ; } } if ( ! isValueFound ) { foundMetric . values . push ( metricItem . value ) ; foundMetric . counts . push ( metricItem . count ) ; } } else { monitoringContext . metrics . push ( { metricName : metricItem . metricName , timestamp , unit : metricItem . unit , dimensions : metricItem . dimensions , values : [ metricItem . value ] , counts : [ metricItem . count ] , } ) ; } } catch ( error ) { log . verbose ( `Failed to collect metric` ) ; log . verbose ( error ) ; } } ; const monitoringError = ( log , monitoringContext , groupContext , error ) => { putMetric ( log , monitoringContext , groupContext , { metricName : 'Errors' , unit : 'Count' , dimensions : groupContext . dimensions . concat ( createErrorDimensions ( error ) ) , value : 1 , count : 1 , } ) ; } ; const monitoringExecution = ( log , monitoringContext , groupContext , error ? ) => { putMetric ( log , monitoringContext , groupContext , { metricName : 'Executions' , unit : 'Count' , dimensions : groupContext . dimensions , value : 1 , count : 1 , } ) ; if ( error != null ) { putMetric ( log , monitoringContext , groupContext , { metricName : 'Errors' , unit : 'Count' , dimensions : groupContext . dimensions . concat ( createErrorDimensions ( error ) ) , value : 1 , count : 1 , } ) ; } } ; const monitoringDuration = ( log , monitoringContext , groupContext , label , duration , count = 1 ) => { if ( ! Number . isFinite ( duration ) ) { log . warn ( `Duration '${ label }' is not recorded because duration must be a finite number` ) ; return ; } putMetric ( log , monitoringContext , groupContext , { metricName : 'Duration' , unit : 'Milliseconds' , dimensions : groupContext . dimensions . concat ( { name : 'Label' , value : label } ) , value : duration , count , } ) ; } ; const monitoringCustom = ( log , monitoringContext , groupContext , { metricName , unit , value = 1 , count = 1 , dimensions = [ ] , } ) => { if ( ! Number . isSafeInteger ( count ) ) { log . warn ( `Count for metric '${ metricName }' is not recorded because it's not an integer` ) ; return ; } if ( ! Number . isSafeInteger ( value ) ) { log . warn ( `Value for metric '${ metricName }' is not recorded because it's not an integer` ) ; return ; } putMetric ( log , monitoringContext , groupContext , { metricName : metricName , unit : unit , dimensions : groupContext . dimensions . concat ( dimensions ) , value , count , } ) ; } ; const monitoringTime = ( log , monitoringContext , groupContext , label , timestamp = Date . now ( ) ) => { if ( ! Number . isFinite ( timestamp ) ) { log . warn ( `Timer '${ label }' is not started because timestamp must be a finite number` ) ; return ; } if ( typeof groupContext . timerMap [ label ] !== 'number' ) { groupContext . timerMap [ label ] = timestamp ; } else { log . warn ( `Timer '${ label }' already exists` ) ; } } ; const monitoringTimeEnd = ( log , monitoringContext , groupContext , label , timestamp = Date . now ( ) ) => { if ( ! Number . isFinite ( timestamp ) ) { log . warn ( `Timer '${ label }' is not ended because timestamp must be a finite number` ) ; return ; } if ( typeof groupContext . timerMap [ label ] === 'number' ) { const duration = timestamp - groupContext . timerMap [ label ] ; delete groupContext . timerMap [ label ] ; return monitoringDuration ( log , monitoringContext , groupContext , label , duration ) ; } else { log . warn ( `Timer '${ label }' does not exist` ) ; } } ; const createGroupDimensions = ( config ) => Object . keys ( config ) . reduce ( ( acc , key ) => config [ key ] != null ? acc . concat ( { name : key , value : config [ key ] , } ) : acc , [ ] as MonitoringDimension [ ] ) ; const monitoringRate = ( log , monitoringContext , groupContext , metricName , count , seconds = 1 ) => { if ( ! Number . isFinite ( count ) ) { log . warn ( `Count per second '${ metricName }' is not recorded because count must be a finite number` ) ; return ; } putMetric ( log , monitoringContext , groupContext , { metricName , unit : 'Count/Second' , dimensions : groupContext . dimensions , value : count / seconds , count : 1 , } ) ; } ; const getMetrics = ( log , monitoringContext ) : { metrics : MonitoringMetric [ ] ; } => ( { metrics : monitoringContext . metrics . map ( ( metric ) => ( { ... metric , dimensions : metric . dimensions . map ( ( dimension ) => ( { ... dimension , } ) ) , values : metric . values . concat ( ) , counts : metric . counts . concat ( ) , } ) ) , } ) ; const clearMetrics = ( log , monitoringContext ) => { monitoringContext . metrics = [ ] ; } ; const createMonitoringImplementation = ( log , monitoringContext , groupContext ) : MonitoringAdapter => { return { group : ( config ) => createMonitoringImplementation ( log , monitoringContext , { timerMap : { } , dimensions : groupContext . dimensions . concat ( createGroupDimensions ( config ) ) , } ) , error : monitoringError . bind ( null , log , monitoringContext , groupContext ) , execution : monitoringExecution . bind ( null , log , monitoringContext , groupContext ) , duration : monitoringDuration . bind ( null , log , monitoringContext , groupContext ) , time : monitoringTime . bind ( null , log , monitoringContext , groupContext ) , timeEnd : monitoringTimeEnd . bind ( null , log , monitoringContext , groupContext ) , rate : monitoringRate . bind ( null , log , monitoringContext , groupContext ) , custom : monitoringCustom . bind ( null , log , monitoringContext , groupContext ) , getMetrics : getMetrics . bind ( null , log , monitoringContext ) , clearMetrics : clearMetrics . bind ( null , log , monitoringContext ) , publish : async ( ) => void 0 , } ; } ; const createMonitoring = ( { getTimestamp = ( ) => null , } = { } ) : MonitoringAdapter => createMonitoringImplementation ( debugLevels ( `resolve:monitoring-base` ) , { getTimestamp : getTimestamp , metrics : [ ] , } , { timerMap : { } , dimensions : [ ] , } ) ; export default createMonitoring ;