import { diag , trace , context , SpanKind } from '@opentelemetry/api' ; import type * as ioredisTypes from 'ioredis' ; import { InstrumentationBase , InstrumentationNodeModuleDefinition , isWrapped , } from '@opentelemetry/instrumentation' ; import { IORedisInstrumentationConfig , IORedisCommand } from './types' ; import { SemanticAttributes } from '@opentelemetry/semantic-conventions' ; import { safeExecuteInTheMiddle } from '@opentelemetry/instrumentation' ; import { endSpan , defaultDbStatementSerializer } from './utils' ; import { VERSION } from './version' ; const DEFAULT_CONFIG = { requireParentSpan : true , } ; export class IORedisInstrumentation extends InstrumentationBase < typeof ioredisTypes > { static readonly DB_SYSTEM = 'redis' ; constructor ( _config = { } ) { super ( '@opentelemetry/instrumentation-ioredis' , VERSION , Object . assign ( { } , DEFAULT_CONFIG , _config ) ) ; } init ( ) { return [ new InstrumentationNodeModuleDefinition < typeof ioredisTypes > ( 'ioredis' , [ '>1 <5' ] , ( moduleExports , moduleVersion ? ) => { diag . debug ( 'Applying patch for ioredis' ) ; if ( isWrapped ( moduleExports . prototype . sendCommand ) ) { this . _unwrap ( moduleExports . prototype , 'sendCommand' ) ; } this . _wrap ( moduleExports . prototype , 'sendCommand' , this . _patchSendCommand ( moduleVersion ) ) ; if ( isWrapped ( moduleExports . prototype . connect ) ) { this . _unwrap ( moduleExports . prototype , 'connect' ) ; } this . _wrap ( moduleExports . prototype , 'connect' , this . _patchConnection ( ) ) ; return moduleExports ; } , moduleExports => { if ( moduleExports === undefined ) return ; diag . debug ( 'Removing patch for ioredis' ) ; this . _unwrap ( moduleExports . prototype , 'sendCommand' ) ; this . _unwrap ( moduleExports . prototype , 'connect' ) ; } ) , ] ; } /**
     * Patch send command internal to trace requests
     */ private _patchSendCommand ( moduleVersion ? ) { return ( original ) => { return this . traceSendCommand ( original , moduleVersion ) ; } ; } private _patchConnection ( ) { return ( original ) => { return this . traceConnection ( original ) ; } ; } private traceSendCommand = ( original , moduleVersion ? ) => { const instrumentation = this ; return function ( this , cmd ? ) { if ( arguments . length < 1 || typeof cmd !== 'object' ) { return original . apply ( this , arguments ) ; } const config = instrumentation . getConfig ( ) as IORedisInstrumentationConfig ; const dbStatementSerializer = config ?. dbStatementSerializer || defaultDbStatementSerializer ; const hasNoParentSpan = trace . getSpan ( context . active ( ) ) === undefined ; if ( config ?. requireParentSpan === true && hasNoParentSpan ) { return original . apply ( this , arguments ) ; } const span = instrumentation . tracer . startSpan ( cmd . name , { kind : SpanKind . CLIENT , attributes : { [ SemanticAttributes . DB_SYSTEM ] : IORedisInstrumentation . DB_SYSTEM , [ SemanticAttributes . DB_STATEMENT ] : dbStatementSerializer ( cmd . name , cmd . args ) , } , } ) ; if ( config ?. requestHook ) { safeExecuteInTheMiddle ( ( ) => config ?. requestHook ! ( span , { moduleVersion , cmdName : cmd . name , cmdArgs : cmd . args , } ) , e => { if ( e ) { diag . error ( 'ioredis instrumentation: request hook failed' , e ) ; } } , true ) ; } const { host , port } = this . options ; span . setAttributes ( { [ SemanticAttributes . NET_PEER_NAME ] : host , [ SemanticAttributes . NET_PEER_PORT ] : port , [ SemanticAttributes . NET_PEER_IP ] : `redis://${ host }:${ port }` , } ) ; try { const result = original . apply ( this , arguments ) ; const origResolve = cmd . resolve ; cmd . resolve = function ( result ) { safeExecuteInTheMiddle ( ( ) => config ?. responseHook ?. ( span , cmd . name , cmd . args , result ) , e => { if ( e ) { diag . error ( 'ioredis instrumentation: response hook failed' , e ) ; } } , true ) ; endSpan ( span , null ) ; origResolve ( result ) ; } ; const origReject = cmd . reject ; cmd . reject = function ( err ) { endSpan ( span , err ) ; origReject ( err ) ; } ; return result ; } catch ( error ) { endSpan ( span , error ) ; throw error ; } } ; } ; private traceConnection = ( original ) => { const instrumentation = this ; return function ( this ) { const span = instrumentation . tracer . startSpan ( 'connect' , { kind : SpanKind . CLIENT , attributes : { [ SemanticAttributes . DB_SYSTEM ] : IORedisInstrumentation . DB_SYSTEM , [ SemanticAttributes . DB_STATEMENT ] : 'connect' , } , } ) ; const { host , port } = this . options ; span . setAttributes ( { [ SemanticAttributes . NET_PEER_NAME ] : host , [ SemanticAttributes . NET_PEER_PORT ] : port , [ SemanticAttributes . NET_PEER_IP ] : `redis://${ host }:${ port }` , } ) ; try { const client = original . apply ( this , arguments ) ; endSpan ( span , null ) ; return client ; } catch ( error ) { endSpan ( span , error ) ; throw error ; } } ; } ; }