- 
                Notifications
    
You must be signed in to change notification settings  - Fork 351
 
remove special-casing of default in hook.js #6401
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
          Overall package sizeSelf size: 12.42 MB Dependency sizes| name | version | self size | total size | |------|---------|-----------|------------| | @datadog/libdatadog | 0.7.0 | 35.02 MB | 35.02 MB | | @datadog/native-appsec | 10.2.1 | 20.64 MB | 20.65 MB | | @datadog/native-iast-taint-tracking | 4.0.0 | 11.72 MB | 11.73 MB | | @datadog/pprof | 5.10.0 | 9.91 MB | 10.3 MB | | @opentelemetry/core | 1.30.1 | 908.66 kB | 7.16 MB | | protobufjs | 7.5.4 | 2.95 MB | 5.6 MB | | @datadog/wasm-js-rewriter | 4.0.1 | 2.85 MB | 3.58 MB | | @datadog/native-metrics | 3.1.1 | 1.02 MB | 1.43 MB | | @opentelemetry/api | 1.9.0 | 1.22 MB | 1.22 MB | | jsonpath-plus | 10.3.0 | 617.18 kB | 1.08 MB | | import-in-the-middle | 1.14.2 | 122.36 kB | 850.93 kB | | lru-cache | 10.4.3 | 804.3 kB | 804.3 kB | | opentracing | 0.14.7 | 194.81 kB | 194.81 kB | | source-map | 0.7.6 | 185.63 kB | 185.63 kB | | pprof-format | 2.2.1 | 163.06 kB | 163.06 kB | | @datadog/sketches-js | 2.1.1 | 109.9 kB | 109.9 kB | | lodash.sortby | 4.7.0 | 75.76 kB | 75.76 kB | | ignore | 7.0.5 | 63.38 kB | 63.38 kB | | istanbul-lib-coverage | 3.2.2 | 34.37 kB | 34.37 kB | | rfdc | 1.4.1 | 27.15 kB | 27.15 kB | | dc-polyfill | 0.1.10 | 26.73 kB | 26.73 kB | | @isaacs/ttlcache | 1.4.1 | 25.2 kB | 25.2 kB | | tlhunter-sorted-set | 0.1.0 | 24.94 kB | 24.94 kB | | shell-quote | 1.8.3 | 23.74 kB | 23.74 kB | | limiter | 1.1.5 | 23.17 kB | 23.17 kB | | retry | 0.13.1 | 18.85 kB | 18.85 kB | | semifies | 1.0.0 | 15.84 kB | 15.84 kB | | jest-docblock | 29.7.0 | 8.99 kB | 12.76 kB | | crypto-randomuuid | 1.0.0 | 11.18 kB | 11.18 kB | | ttl-set | 1.0.0 | 4.61 kB | 9.69 kB | | mutexify | 1.4.0 | 5.71 kB | 8.74 kB | | path-to-regexp | 0.1.12 | 6.6 kB | 6.6 kB | | koalas | 1.0.2 | 6.47 kB | 6.47 kB | | module-details-from-path | 1.0.4 | 3.96 kB | 3.96 kB |🤖 This report was automatically generated by heaviest-objects-in-the-universe  | 
    
          Codecov Report❌ Patch coverage is  
 Additional details and impacted files@@           Coverage Diff           @@
##           master    #6401   +/-   ##
=======================================
  Coverage   84.07%   84.07%           
=======================================
  Files         484      484           
  Lines       20246    20257   +11     
=======================================
+ Hits        17021    17031   +10     
- Misses       3225     3226    +1     ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
  | 
    
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, just the deactivation of the warnings is not ideal in my opinion. Could we limit that a version, if that's only applying in a particular version?
          BenchmarksBenchmark execution time: 2025-09-24 15:02:41 Comparing candidate commit a6fe881 in PR branch  Found 0 performance improvements and 0 performance regressions! Performance is the same for 1612 metrics, 68 unstable metrics.  | 
    
      
        
              This comment has been minimized.
        
        
      
    
  This comment has been minimized.
| 
               | 
          ||
| addHook({ name: 'fastify', versions: ['>=3'] }, fastify => { | ||
| const wrapped = shimmer.wrapFunction(fastify, fastify => wrapFastify(fastify, true)) | ||
| addHook({ name: 'fastify', versions: ['>=3'] }, (fastify, _1, _2, isIitm) => { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed offline, the fact that this is needed is probably due to a bug or something not properly handled in the new implementation. Let's fix it before landing.
b290d9c    to
    ae9e749      
    Compare
  
    6eed43b    to
    e9fc4f0      
    Compare
  
    | if (newExports && | ||
| (typeof newExports === 'object' || typeof newExports === 'function') && | ||
| (moduleName === 'protobufjs' || !moduleName.includes('protobuf'))) { | ||
| (moduleName === 'protobufjs' || !moduleName.includes('protobuf')) && !moduleName.includes('aws-sdk')) { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@perhardhernandez Lets put a comment here indicating why these are special-cased. We ought to also be careful with includes, and instead specify as precisely as we can.
| ) { | ||
| newExports.default = onrequire(moduleExports.default, moduleName, moduleBaseDir, moduleVersion, isIitm) | ||
| defaultWrapResult = onrequire(moduleExports.default, moduleName, moduleBaseDir, moduleVersion, isIitm) | ||
| if (moduleName.includes('express')) { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@perhardhernandez Lets put a comment here indicating why this special-cased. We ought to also be careful with includes, and instead specify as precisely as we can.
65db538    to
    607fb1a      
    Compare
  
            
          
                packages/datadog-plugin-pino/test/integration-test/client.spec.js
              
                Outdated
          
            Show resolved
            Hide resolved
        
      | variants = varySandbox(sandbox, 'server.mjs', { | ||
| default: 'import pino from \'pino\'', | ||
| star: 'import * as modPino from \'pino\'; const pino = (...a) => modPino.default(...a)', | ||
| destructure: 'import { default as pino } from \'pino\'', | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we miss one more case that we should also test:
import { pino } from 'pino'
I believe that might not be the case for all modules, but definitely for some.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That export is not possible as 'pino' is not directly exposed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not check if it's pino in new versions or Node.js that allows it, but it's definitely possible to load pino like that with new versions :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, it does seem like it's pino version issue. If we want to add into the tests we would need to something like in the pg integration test file.
| default: 'import connect from \'connect\'', | ||
| star: 'import * as starConnect from \'connect\'; const connect = starConnect.default;', | ||
| destructure: 'import { default as connect } from \'connect\';' | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since most imports always look like these, should we just have shortcut that accepts the name of the module and that creates these three possible test cases automatically?
That way we only have to define them in more detail in case the default case does not work as expected, which is less frequent.
| // TODO(bengl): The `varySandbox` helper function isn't well set-up for dealing | ||
| // with Azure Functions and the way the `func` command expects to find files. I | ||
| // have manually tested that all the usual import variants work, but really we ought | ||
| // to figure out a way of automating this. | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have to memorize to do that again before landing with the latest changes.
| const filename = path.join(...parts) | ||
| 
               | 
          ||
| if (this._patched[filename]) return moduleExports | ||
| if (this._patched[filename] && patched.has(moduleExports)) return patched.get(moduleExports) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am uncertain why we use both cases. Would it be possible to get a comment explaining this? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure using both doesn't even do anything? What happens if the set is removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea of keeping both was to ensure that if we ever encounter the same filename twice, we return the patched export, if that's not possible, is this._patched even needed (considering onrequire takes case of filename verification)? And if it is possible, my understanding is that moduleExports always enters our process as the original export object.
| }) | ||
| 
               | 
          ||
| addHook({ name: 'cassandra-driver', versions: ['>=4.4'] }, cassandra => { | ||
| addHook({ name: 'cassandra-driver', versions: ['>=4.4'] }, (cassandra, _1, _2, isIitm) => { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before landing, it is probable good to rename _1 and _2 to the actual variable names, just with a leading underscore. That applies to all cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this same check is in multiple places, could it be an option instead? For example:
// Could be another name too, I'm not 100% what this even does.
{ name: '@azure/functions', versions: ['>=4'], patchDefault: false }Then each individual hook doesn't need to know how to handle it and it fixes the weird signature of the function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The above, was to prevent double wrapping when moduleExports.default and moduleExports share a same prototype, handling it as Roch suggested would work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am fine either way
| if ( | ||
| isIitm && | ||
| moduleExports.default && | ||
| (typeof moduleExports.default === 'object' || | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this check needed? If a hook is requesting the export it should always be called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It coming from isIitm does not ensure that .default exist, hence the condition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, but why the explicit type check? Wouldn't checking just for the presence enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Express and next have some weird exports that are not of those types, hence the check.
| this._patched[filename] = true | ||
| if (newExports && | ||
| (typeof newExports === 'object' || typeof newExports === 'function')) { | ||
| patched.set(moduleExports, newExports) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does this do at this point since this code should never run more than once for an export now (since we switched back to using the filename)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did we confirm that an export never runs more than once? If so why did we have the following
if (this._patched[filename]) return moduleExports
The idea of keeping both was to make sure we were always returning the patched export.
0b4a374    to
    1acd2c7      
    Compare
  
    | } | ||
| 
               | 
          ||
| addHook({ name: 'pino', versions: ['2 - 3', '4', '>=5 <5.14.0'] }, pino => { | ||
| addHook({ name: 'pino', versions: ['2 - 3', '4'], patchDefault: false }, (pino, _1, _2, isIitm) => { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is isIitm still needed here? The hook shouldn't have to know how to get the right export.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, with out it pino tests fail. I'm guessing this is related to some of the weird things that pino do with their exports.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you look into it a bit more? I'd love to be able to just remove that parameter entirely if we can, and the hook should already be receiving both the export and the default. I wonder if it's because Pino needs the opposite of patchDefault?
fe6096d    to
    3ec4b08      
    Compare
  
    Fixed ESM instrumentation, the previous model of assuming default === exports was very incorrect for ESM since they're never the exact same object. We also can't assume they should have the same properties, or even that they're both objects/functions, at least not in the general case. Therefore, we need to be specific about what's being instrumented in every single instrumentation. Added appropriate testing and modifications to ensure any known broken instrumentation is properly instrumented with all import scenarios. --------- Co-authored-by: perhardhernandez <[email protected]>
Fixed ESM instrumentation, the previous model of assuming default === exports was very incorrect for ESM since they're never the exact same object. We also can't assume they should have the same properties, or even that they're both objects/functions, at least not in the general case. Therefore, we need to be specific about what's being instrumented in every single instrumentation. Added appropriate testing and modifications to ensure any known broken instrumentation is properly instrumented with all import scenarios. --------- Co-authored-by: perhardhernandez <[email protected]>
What does this PR do?
See title. Plus:
Motivation
The previous model of assuming
default === exportswas very incorrect for ESM since they're never the exact same object. We also can't assume they should have the same properties, or even that they're both objects/functions, at least not in the general case.Therefore, we need to be specific about what's being instrumented in every single instrumentation.
Additional Notes
This is obviously in a draft state. The only instrumentation edited so far is
http2.This should fix these issues:
Refs: #3500
Refs: #5479