Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@bengl
Copy link
Collaborator

@bengl bengl commented Sep 8, 2025

What does this PR do?

See title. Plus:

  • appropriate testing
  • modifying instrumentations when needed

Motivation

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.

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

@bengl bengl requested review from a team as code owners September 8, 2025 18:09
@github-actions
Copy link

github-actions bot commented Sep 8, 2025

Overall package size

Self size: 12.42 MB
Deduped: 112.51 MB
No deduping: 112.91 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
Copy link

codecov bot commented Sep 8, 2025

Codecov Report

❌ Patch coverage is 85.29412% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.07%. Comparing base (964886d) to head (a6fe881).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
...kages/datadog-instrumentations/src/helpers/hook.js 78.57% 3 Missing ⚠️
...s/datadog-instrumentations/src/helpers/register.js 71.42% 2 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Collaborator

@BridgeAR BridgeAR left a 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?

@pr-commenter
Copy link

pr-commenter bot commented Sep 8, 2025

Benchmarks

Benchmark execution time: 2025-09-24 15:02:41

Comparing candidate commit a6fe881 in PR branch bengl/no-default-special-case with baseline commit 964886d in branch master.

Found 0 performance improvements and 0 performance regressions! Performance is the same for 1612 metrics, 68 unstable metrics.

@datadog-official

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) => {
Copy link
Member

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.

if (newExports &&
(typeof newExports === 'object' || typeof newExports === 'function') &&
(moduleName === 'protobufjs' || !moduleName.includes('protobuf'))) {
(moduleName === 'protobufjs' || !moduleName.includes('protobuf')) && !moduleName.includes('aws-sdk')) {
Copy link
Collaborator Author

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')) {
Copy link
Collaborator Author

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.

@perhardhernandez perhardhernandez force-pushed the bengl/no-default-special-case branch from 65db538 to 607fb1a Compare September 19, 2025 20:12
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\'',
Copy link
Collaborator

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.

Copy link
Contributor

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.

Copy link
Collaborator

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 :)

Copy link
Contributor

@perhardhernandez perhardhernandez Sep 22, 2025

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.

Comment on lines 26 to 28
default: 'import connect from \'connect\'',
star: 'import * as starConnect from \'connect\'; const connect = starConnect.default;',
destructure: 'import { default as connect } from \'connect\';'
Copy link
Collaborator

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.
Copy link
Collaborator

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)
Copy link
Collaborator

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? :)

Copy link
Member

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?

Copy link
Contributor

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) => {
Copy link
Collaborator

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.

Copy link
Member

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.

Copy link
Contributor

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.

Copy link
Collaborator

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' ||
Copy link
Member

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.

Copy link
Contributor

@perhardhernandez perhardhernandez Sep 22, 2025

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.

Copy link
Member

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?

Copy link
Contributor

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)
Copy link
Member

@rochdev rochdev Sep 22, 2025

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)

Copy link
Contributor

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.

@perhardhernandez perhardhernandez force-pushed the bengl/no-default-special-case branch 2 times, most recently from 0b4a374 to 1acd2c7 Compare September 22, 2025 16:15
}

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) => {
Copy link
Member

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.

Copy link
Contributor

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.

Copy link
Member

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?

rochdev
rochdev previously approved these changes Sep 23, 2025
@perhardhernandez perhardhernandez force-pushed the bengl/no-default-special-case branch from fe6096d to 3ec4b08 Compare September 24, 2025 13:42
@perhardhernandez perhardhernandez merged commit a38a294 into master Sep 24, 2025
774 of 776 checks passed
@perhardhernandez perhardhernandez deleted the bengl/no-default-special-case branch September 24, 2025 18:18
dd-octo-sts bot pushed a commit that referenced this pull request Sep 25, 2025
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]>
This was referenced Sep 25, 2025
dd-octo-sts bot pushed a commit that referenced this pull request Oct 1, 2025
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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants