diff --git a/package-lock.json b/package-lock.json index 8c8980eff1..4f110e65d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31938,6 +31938,7 @@ "devDependencies": { "@opentelemetry/api": "^1.3.0", "@opentelemetry/context-async-hooks": "^2.0.0", + "@opentelemetry/contrib-test-utils": "^0.49.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@types/mocha": "10.0.10", @@ -41825,6 +41826,7 @@ "requires": { "@opentelemetry/api": "^1.3.0", "@opentelemetry/context-async-hooks": "^2.0.0", + "@opentelemetry/contrib-test-utils": "^0.49.0", "@opentelemetry/instrumentation": "^0.203.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", diff --git a/packages/instrumentation-dataloader/package.json b/packages/instrumentation-dataloader/package.json index e134de1937..63e86a6179 100644 --- a/packages/instrumentation-dataloader/package.json +++ b/packages/instrumentation-dataloader/package.json @@ -48,6 +48,7 @@ "devDependencies": { "@opentelemetry/api": "^1.3.0", "@opentelemetry/context-async-hooks": "^2.0.0", + "@opentelemetry/contrib-test-utils": "^0.49.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@types/mocha": "10.0.10", diff --git a/packages/instrumentation-dataloader/src/instrumentation.ts b/packages/instrumentation-dataloader/src/instrumentation.ts index 719aae44e8..015b6fae62 100644 --- a/packages/instrumentation-dataloader/src/instrumentation.ts +++ b/packages/instrumentation-dataloader/src/instrumentation.ts @@ -44,6 +44,13 @@ type PrimeFn = (typeof Dataloader.prototype)['prime']; type ClearFn = (typeof Dataloader.prototype)['clear']; type ClearAllFn = (typeof Dataloader.prototype)['clearAll']; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function extractModuleExports(module: any) { + return module[Symbol.toStringTag] === 'Module' + ? module.default // ESM + : module; // CommonJS +} + export class DataloaderInstrumentation extends InstrumentationBase { constructor(config: DataloaderInstrumentationConfig = {}) { super(PACKAGE_NAME, PACKAGE_VERSION, config); @@ -54,7 +61,8 @@ export class DataloaderInstrumentation extends InstrumentationBase=2.0.0 <3'], - dataloader => { + module => { + const dataloader = extractModuleExports(module); this._patchLoad(dataloader.prototype); this._patchLoadMany(dataloader.prototype); this._patchPrime(dataloader.prototype); @@ -63,14 +71,15 @@ export class DataloaderInstrumentation extends InstrumentationBase { + module => { + const dataloader = extractModuleExports(module); ['load', 'loadMany', 'prime', 'clear', 'clearAll'].forEach(method => { if (isWrapped(dataloader.prototype[method])) { this._unwrap(dataloader.prototype, method); } }); } - ) as InstrumentationNodeModuleDefinition, + ), ]; } diff --git a/packages/instrumentation-dataloader/test/dataloader.test.ts b/packages/instrumentation-dataloader/test/dataloader.test.ts index b43e2959a4..2f495d946f 100644 --- a/packages/instrumentation-dataloader/test/dataloader.test.ts +++ b/packages/instrumentation-dataloader/test/dataloader.test.ts @@ -21,6 +21,10 @@ import { import { context, SpanKind, SpanStatusCode, trace } from '@opentelemetry/api'; import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks'; +import { + runTestFixture, + TestCollector, +} from '@opentelemetry/contrib-test-utils'; import { DataloaderInstrumentation } from '../src'; const instrumentation = new DataloaderInstrumentation(); @@ -641,3 +645,25 @@ describe('DataloaderInstrumentation', () => { assert.strictEqual(batchSpan.name, 'dataloader.batch'); }); }); + +describe('ESM usage', () => { + it('should work with ESM default import', async function () { + await runTestFixture({ + cwd: __dirname, + argv: ['fixtures/use-dataloader-default-import.mjs'], + env: { + NODE_OPTIONS: + '--experimental-loader=@opentelemetry/instrumentation/hook.mjs', + NODE_NO_WARNINGS: '1', + }, + checkCollector: (collector: TestCollector) => { + const spans = collector.sortedSpans; + assert.strictEqual(spans[0].name, 'manual'); + assert.strictEqual(spans[1].name, 'dataloader.load'); + assert.strictEqual(spans[1].parentSpanId, spans[0].spanId); + assert.strictEqual(spans[2].name, 'dataloader.batch'); + assert.strictEqual(spans[2].parentSpanId, spans[1].spanId); + }, + }); + }); +}); diff --git a/packages/instrumentation-dataloader/test/fixtures/use-dataloader-default-import.mjs b/packages/instrumentation-dataloader/test/fixtures/use-dataloader-default-import.mjs new file mode 100644 index 0000000000..64dc34635a --- /dev/null +++ b/packages/instrumentation-dataloader/test/fixtures/use-dataloader-default-import.mjs @@ -0,0 +1,53 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Use dataloader from an ES module: +// node --experimental-loader=@opentelemetry/instrumentation/hook.mjs use-dataloader-default-import.mjs + +import { trace } from '@opentelemetry/api'; +import { createTestNodeSdk } from '@opentelemetry/contrib-test-utils'; +import * as crypto from 'crypto'; + +import { DataloaderInstrumentation } from '../../build/src/index.js'; + +const sdk = createTestNodeSdk({ + serviceName: 'use-dataloader', + instrumentations: [ + new DataloaderInstrumentation() + ] +}) +sdk.start(); + +import Dataloader from 'dataloader'; + +function getMd5HashFromIdx(idx ) { + return crypto.createHash('md5').update(String(idx)).digest('hex'); +} +const dataloader = new Dataloader( + async keys => + keys.map((_, idx) => { + return getMd5HashFromIdx(idx); + }), + { cache: true } +); + +const tracer = trace.getTracer(); +await tracer.startActiveSpan('manual', async (span) => { + await dataloader.load(1); + span.end(); +}); + +await sdk.shutdown();