Skip to content

Commit b61f912

Browse files
chentsulintrentm
andauthored
fix(koa): fix instrumentation of ESM-imported koa (#1736)
Co-authored-by: Trent Mick <[email protected]>
1 parent 8f2a195 commit b61f912

File tree

5 files changed

+123
-4
lines changed

5 files changed

+123
-4
lines changed

package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/node/opentelemetry-instrumentation-koa/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949
"@koa/router": "12.0.0",
5050
"@opentelemetry/api": "^1.3.0",
5151
"@opentelemetry/context-async-hooks": "^1.8.0",
52+
"@opentelemetry/contrib-test-utils": "^0.35.0",
53+
"@opentelemetry/instrumentation-http": "^0.45.1",
5254
"@opentelemetry/sdk-trace-base": "^1.8.0",
5355
"@opentelemetry/sdk-trace-node": "^1.8.0",
5456
"@types/mocha": "7.0.2",

plugins/node/opentelemetry-instrumentation-koa/src/instrumentation.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,11 @@ export class KoaInstrumentation extends InstrumentationBase<typeof koa> {
5555
return new InstrumentationNodeModuleDefinition<typeof koa>(
5656
'koa',
5757
['^2.0.0'],
58-
moduleExports => {
58+
(module: any) => {
59+
const moduleExports: typeof koa =
60+
module[Symbol.toStringTag] === 'Module'
61+
? module.default // ESM
62+
: module; // CommonJS
5963
if (moduleExports == null) {
6064
return moduleExports;
6165
}
@@ -68,9 +72,13 @@ export class KoaInstrumentation extends InstrumentationBase<typeof koa> {
6872
'use',
6973
this._getKoaUsePatch.bind(this)
7074
);
71-
return moduleExports;
75+
return module;
7276
},
73-
moduleExports => {
77+
(module: any) => {
78+
const moduleExports: typeof koa =
79+
module[Symbol.toStringTag] === 'Module'
80+
? module.default // ESM
81+
: module; // CommonJS
7482
api.diag.debug('Unpatching Koa');
7583
if (isWrapped(moduleExports.prototype.use)) {
7684
this._unwrap(moduleExports.prototype, 'use');
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
// Use koa from an ES module:
18+
// node --experimental-loader=@opentelemetry/instrumentation/hook.mjs use-koa.mjs
19+
20+
import { promisify } from 'util';
21+
import { createTestNodeSdk } from '@opentelemetry/contrib-test-utils';
22+
23+
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
24+
import { KoaInstrumentation } from '../../build/src/index.js';
25+
26+
const sdk = createTestNodeSdk({
27+
serviceName: 'use-koa',
28+
instrumentations: [
29+
new KoaInstrumentation(),
30+
new HttpInstrumentation()
31+
]
32+
})
33+
sdk.start();
34+
35+
import Koa from 'koa';
36+
import KoaRouter from '@koa/router';
37+
import * as http from 'http';
38+
39+
const app = new Koa();
40+
41+
app.use(async function simpleMiddleware(ctx, next) {
42+
// Wait a short delay to ensure this "middleware - ..." span clearly starts
43+
// before the "router - ..." span. The test rely on a start-time-based sort
44+
// of the produced spans. If they start in the same millisecond, then tests
45+
// can be flaky.
46+
await promisify(setTimeout)(10);
47+
await next();
48+
});
49+
50+
const router = new KoaRouter();
51+
router.get('/post/:id', ctx => {
52+
ctx.body = `Post id: ${ctx.params.id}`;
53+
});
54+
55+
app.use(router.routes());
56+
57+
const server = http.createServer(app.callback());
58+
await new Promise(resolve => server.listen(0, resolve));
59+
const port = server.address().port;
60+
61+
await new Promise(resolve => {
62+
http.get(`http://localhost:${port}/post/0`, (res) => {
63+
res.resume();
64+
res.on('end', () => {
65+
resolve();
66+
});
67+
})
68+
});
69+
70+
await new Promise(resolve => server.close(resolve));
71+
await sdk.shutdown();

plugins/node/opentelemetry-instrumentation-koa/test/koa.test.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@
1515
*/
1616

1717
import * as KoaRouter from '@koa/router';
18-
import { context, trace, Span } from '@opentelemetry/api';
18+
import { context, trace, Span, SpanKind } from '@opentelemetry/api';
1919
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
2020
import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks';
21+
import * as testUtils from '@opentelemetry/contrib-test-utils';
2122
import {
2223
InMemorySpanExporter,
2324
SimpleSpanProcessor,
@@ -709,4 +710,36 @@ describe('Koa Instrumentation', () => {
709710
);
710711
});
711712
});
713+
714+
it('should work with ESM usage', async () => {
715+
await testUtils.runTestFixture({
716+
cwd: __dirname,
717+
argv: ['fixtures/use-koa.mjs'],
718+
env: {
719+
NODE_OPTIONS:
720+
'--experimental-loader=@opentelemetry/instrumentation/hook.mjs',
721+
NODE_NO_WARNINGS: '1',
722+
},
723+
checkResult: (err, stdout, stderr) => {
724+
assert.ifError(err);
725+
},
726+
checkCollector: (collector: testUtils.TestCollector) => {
727+
// use-koa.mjs creates a Koa app with a 'GET /post/:id' endpoint and
728+
// a `simpleMiddleware`, then makes a single 'GET /post/0' request. We
729+
// expect to see spans like this:
730+
// span 'GET /post/:id'
731+
// `- span 'middleware - simpleMiddleware'
732+
// `- span 'router - /post/:id'
733+
const spans = collector.sortedSpans;
734+
assert.strictEqual(spans[0].name, 'GET /post/:id');
735+
assert.strictEqual(spans[0].kind, SpanKind.CLIENT);
736+
assert.strictEqual(spans[1].name, 'middleware - simpleMiddleware');
737+
assert.strictEqual(spans[1].kind, SpanKind.SERVER);
738+
assert.strictEqual(spans[1].parentSpanId, spans[0].spanId);
739+
assert.strictEqual(spans[2].name, 'router - /post/:id');
740+
assert.strictEqual(spans[2].kind, SpanKind.SERVER);
741+
assert.strictEqual(spans[2].parentSpanId, spans[1].spanId);
742+
},
743+
});
744+
});
712745
});

0 commit comments

Comments
 (0)