'use strict' ; import { LogCorrelationContext , Logger , TraceLevel } from '../../logger' ; import { Functions } from '../function' ; import { Strings } from '../string' ; const emptyStr = '' ; const correlationContext = new Map < number , LogCorrelationContext > ( ) ; let correlationCounter = 0 ; export function getCorrelationContext ( ) { return correlationContext . get ( correlationCounter ) ; } export function getCorrelationId ( ) { return correlationCounter ; } function getNextCorrelationId ( ) { if ( correlationCounter === Number . MAX_SAFE_INTEGER ) { correlationCounter = 0 ; } return ++ correlationCounter ; } function clearCorrelationContext ( correlationId ) { correlationContext . delete ( correlationId ) ; } function setCorrelationContext ( correlationId , context ) { correlationContext . set ( correlationId , context ) ; } export interface LogContext < T > { id : number ; instance : T ; instanceName : string ; name : string ; prefix : string ; } export const LogInstanceNameFn = Symbol ( 'logInstanceNameFn' ) ; export function logName < T > ( fn ) { return ( target ) => { ( target as any ) [ LogInstanceNameFn ] = fn ; } ; } export function debug < T extends ( ... arg : any ) => any > ( options = { timed : true } ) { return log < T > ( { debug : true , ... options } ) ; } type PromiseType < T > = T extends Promise < infer U > ? U : T ; export function log < T extends ( ... arg : any ) => any > ( options = { timed : true } ) { options = { timed : true , ... options } ; const logFn = ( options . debug ? Logger . debug . bind ( Logger ) : Logger . log . bind ( Logger ) ) as typeof Logger . debug | typeof Logger . log ; return ( target , key , descriptor ) => { let fn ; if ( typeof descriptor . value === 'function' ) { fn = descriptor . value ; } else if ( typeof descriptor . get === 'function' ) { fn = descriptor . get ; } if ( fn == null ) throw new Error ( 'Not supported' ) ; const parameters = Functions . getParameters ( fn ) ; descriptor . value = function ( this , ... args ) { const correlationId = getNextCorrelationId ( ) ; if ( ( ! Logger . isDebugging && Logger . level !== TraceLevel . Debug && ! ( Logger . level === TraceLevel . Verbose && ! options . debug ) ) || ( typeof options . condition === 'function' && ! options . condition ( ... args ) ) ) { return fn ! . apply ( this , args ) ; } let instanceName ; if ( this != null ) { instanceName = Logger . toLoggableName ( this ) ; if ( this . constructor != null && this . constructor [ LogInstanceNameFn ] ) { instanceName = target . constructor [ LogInstanceNameFn ] ( this , instanceName ) ; } } else { instanceName = emptyStr ; } let { correlate } = options ; if ( ! correlate && options . timed ) { correlate = true ; } let prefix = `${ correlate ? `[${ correlationId . toString ( 16 ) }] ` : emptyStr }${ instanceName ? `${ instanceName }.` : emptyStr }${ key }` ; if ( options . prefix != null ) { prefix = options . prefix ( { id : correlationId , instance : this , instanceName : instanceName , name : key , prefix : prefix } , ... args ) ; } let correlationContext ; if ( correlate ) { correlationContext = { correlationId : correlationId , prefix : prefix } ; setCorrelationContext ( correlationId , correlationContext ) ; } const enter = options . enter != null ? options . enter ( ... args ) : emptyStr ; let loggableParams ; if ( options . args === false || args . length === 0 ) { loggableParams = emptyStr ; if ( ! options . singleLine ) { logFn ( `${ prefix }${ enter }` ) ; } } else { const argFns = typeof options . args === 'object' ? options . args : undefined ; let argFn ; let loggable ; loggableParams = args . map ( ( v , index ) => { const p = parameters [ index ] ; argFn = argFns !== undefined ? argFns [ index ] : undefined ; if ( argFn !== undefined ) { loggable = argFn ( v ) ; if ( loggable === false ) return undefined ; } else { loggable = Logger . toLoggable ( v , options . sanitize ) ; } return p ? `${ p }=${ loggable }` : loggable ; } ) . filter ( Boolean ) . join ( ', ' ) ; if ( ! options . singleLine ) { if ( ! options . debug ) { Logger . logWithDebugParams ( `${ prefix }${ enter }` , loggableParams ) ; } else { logFn ( `${ prefix }${ enter }` , loggableParams ) ; } } } if ( options . singleLine || options . timed || options . exit != null ) { const start = options . timed ? process . hrtime ( ) : undefined ; const logError = ( ex ) => { const timing = start !== undefined ? ` \u2022 ${ Strings . getDurationMilliseconds ( start ) } ms` : emptyStr ; if ( options . singleLine ) { Logger . error ( ex , `${ prefix }${ enter }` , `failed${ correlationContext !== undefined && correlationContext . exitDetails ? correlationContext . exitDetails : emptyStr }${ timing }` , loggableParams ) ; } else { Logger . error ( ex , prefix , `failed${ correlationContext !== undefined && correlationContext . exitDetails ? correlationContext . exitDetails : emptyStr }${ timing }` ) ; } if ( correlate ) { clearCorrelationContext ( correlationId ) ; } } ; let result ; try { result = fn ! . apply ( this , args ) ; } catch ( ex ) { logError ( ex ) ; throw ex ; } const logResult = ( r ) => { const timing = start !== undefined ? ` \u2022 ${ Strings . getDurationMilliseconds ( start ) } ms` : emptyStr ; let exit ; if ( options . exit != null ) { try { exit = options . exit ( r ) ; } catch ( ex ) { exit = `@log.exit error: ${ ex }` ; } } else { exit = 'completed' ; } if ( options . singleLine ) { if ( ! options . debug ) { Logger . logWithDebugParams ( `${ prefix }${ enter } ${ exit }${ correlationContext !== undefined && correlationContext . exitDetails ? correlationContext . exitDetails : emptyStr }${ timing }` , loggableParams ) ; } else { logFn ( `${ prefix }${ enter } ${ exit }${ correlationContext !== undefined && correlationContext . exitDetails ? correlationContext . exitDetails : emptyStr }${ timing }` , loggableParams ) ; } } else { logFn ( `${ prefix } ${ exit }${ correlationContext !== undefined && correlationContext . exitDetails ? correlationContext . exitDetails : emptyStr }${ timing }` ) ; } if ( correlate ) { clearCorrelationContext ( correlationId ) ; } } ; if ( result != null && Functions . isPromise ( result ) ) { const promise = result . then ( logResult ) ; promise . catch ( logError ) ; } else { logResult ( result ) ; } return result ; } return fn ! . apply ( this , args ) ; } ; } ; }