@@ -51,6 +51,7 @@ if (DD_TRACE_DEBUG && DD_TRACE_DEBUG.toLowerCase() !== 'false') {
51
51
}
52
52
53
53
const seenCombo = new Set ( )
54
+ const allInstrumentations = { }
54
55
55
56
// TODO: make this more efficient
56
57
for ( const packageName of names ) {
@@ -67,6 +68,9 @@ for (const packageName of names) {
67
68
hook = hook . fn
68
69
}
69
70
71
+ // get the instrumentation file name to save all hooked versions
72
+ const instrumentationFileName = parseHookInstrumentationFileName ( packageName )
73
+
70
74
Hook ( [ packageName ] , hookOptions , ( moduleExports , moduleName , moduleBaseDir , moduleVersion ) => {
71
75
moduleName = moduleName . replace ( pathSepExpr , '/' )
72
76
@@ -105,6 +109,7 @@ for (const packageName of names) {
105
109
let version = moduleVersion
106
110
try {
107
111
version = version || getVersion ( moduleBaseDir )
112
+ allInstrumentations [ instrumentationFileName ] = allInstrumentations [ instrumentationFileName ] || false
108
113
} catch ( e ) {
109
114
log . error ( 'Error getting version for "%s": %s' , name , e . message , e )
110
115
continue
@@ -114,6 +119,8 @@ for (const packageName of names) {
114
119
}
115
120
116
121
if ( matchVersion ( version , versions ) ) {
122
+ allInstrumentations [ instrumentationFileName ] = true
123
+
117
124
// Check if the hook already has a set moduleExport
118
125
if ( hook [ HOOK_SYMBOL ] . has ( moduleExports ) ) {
119
126
namesAndSuccesses [ `${ name } @${ version } ` ] = true
@@ -143,7 +150,8 @@ for (const packageName of names) {
143
150
for ( const nameVersion of Object . keys ( namesAndSuccesses ) ) {
144
151
const [ name , version ] = nameVersion . split ( '@' )
145
152
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 ] ) {
147
155
telemetry ( 'abort.integration' , [
148
156
`integration:${ name } ` ,
149
157
`integration_version:${ version } `
@@ -171,6 +179,38 @@ function filename (name, file) {
171
179
return [ name , file ] . filter ( val => val ) . join ( '/' )
172
180
}
173
181
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
+
174
214
module . exports = {
175
215
filename,
176
216
pathSepExpr,
0 commit comments