@@ -51,6 +51,7 @@ if (DD_TRACE_DEBUG && DD_TRACE_DEBUG.toLowerCase() !== 'false') {
5151}
5252
5353const seenCombo = new Set ( )
54+ const allInstrumentations = { }
5455
5556// TODO: make this more efficient
5657for ( const packageName of names ) {
@@ -67,6 +68,9 @@ for (const packageName of names) {
6768 hook = hook . fn
6869 }
6970
71+ // get the instrumentation file name to save all hooked versions
72+ const instrumentationFileName = parseHookInstrumentationFileName ( packageName )
73+
7074 Hook ( [ packageName ] , hookOptions , ( moduleExports , moduleName , moduleBaseDir , moduleVersion ) => {
7175 moduleName = moduleName . replace ( pathSepExpr , '/' )
7276
@@ -105,6 +109,7 @@ for (const packageName of names) {
105109 let version = moduleVersion
106110 try {
107111 version = version || getVersion ( moduleBaseDir )
112+ allInstrumentations [ instrumentationFileName ] = allInstrumentations [ instrumentationFileName ] || false
108113 } catch ( e ) {
109114 log . error ( 'Error getting version for "%s": %s' , name , e . message , e )
110115 continue
@@ -114,6 +119,8 @@ for (const packageName of names) {
114119 }
115120
116121 if ( matchVersion ( version , versions ) ) {
122+ allInstrumentations [ instrumentationFileName ] = true
123+
117124 // Check if the hook already has a set moduleExport
118125 if ( hook [ HOOK_SYMBOL ] . has ( moduleExports ) ) {
119126 namesAndSuccesses [ `${ name } @${ version } ` ] = true
@@ -143,7 +150,8 @@ for (const packageName of names) {
143150 for ( const nameVersion of Object . keys ( namesAndSuccesses ) ) {
144151 const [ name , version ] = nameVersion . split ( '@' )
145152 const success = namesAndSuccesses [ nameVersion ]
146- if ( ! success && ! seenCombo . has ( nameVersion ) ) {
153+ // we check allVersions to see if any version of the integration was successfully instrumented
154+ if ( ! success && ! seenCombo . has ( nameVersion ) && ! allInstrumentations [ instrumentationFileName ] ) {
147155 telemetry ( 'abort.integration' , [
148156 `integration:${ name } ` ,
149157 `integration_version:${ version } `
@@ -171,6 +179,38 @@ function filename (name, file) {
171179 return [ name , file ] . filter ( val => val ) . join ( '/' )
172180}
173181
182+ // This function captures the instrumentation file name for a given package by parsing the hook require
183+ // function given the module name. It is used to ensure that instrumentations such as redis
184+ // that have several different modules being hooked, ie: 'redis' main package, and @redis/client submodule
185+ // return a consistent instrumentation name. This is used later to ensure that atleast some portion of
186+ // the integration was successfully instrumented. Prevents incorrect `Found incompatible integration version: ` messages
187+ // Example:
188+ // redis -> "() => require('../redis')" -> redis
189+ // @redis /client -> "() => require('../redis')" -> redis
190+ //
191+ function parseHookInstrumentationFileName ( packageName ) {
192+ let hook = hooks [ packageName ]
193+ if ( hook . fn ) {
194+ hook = hook . fn
195+ }
196+ const hookString = hook . toString ( )
197+
198+ const regex = / r e q u i r e \( ' ( [ ^ ' ] * ) ' \) /
199+ const match = hookString . match ( regex )
200+
201+ // try to capture the hook require file location.
202+ if ( match && match [ 1 ] ) {
203+ let moduleName = match [ 1 ]
204+ // Remove leading '../' if present
205+ if ( moduleName . startsWith ( '../' ) ) {
206+ moduleName = moduleName . substring ( 3 )
207+ }
208+ return moduleName
209+ }
210+
211+ return null
212+ }
213+
174214module . exports = {
175215 filename,
176216 pathSepExpr,
0 commit comments