Skip to content

Commit 984d275

Browse files
committed
feat(nodejs): load instrumentation packages dynamic
1 parent 3c93842 commit 984d275

File tree

5 files changed

+136
-84
lines changed

5 files changed

+136
-84
lines changed

nodejs/packages/layer/src/init.mjs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
const WRAPPER_INIT_START_TIME = Date.now();
2-
await import('./wrapper.js');
2+
const { default: wrapper } = await import('./wrapper.js');
3+
await wrapper.init();
4+
await wrapper.wrap();
35
console.log('OpenTelemetry wrapper init completed in', Date.now() - WRAPPER_INIT_START_TIME, 'ms');
46

57
const LOADER_INIT_START_TIME = Date.now();

nodejs/packages/layer/src/wrapper.ts

+76-34
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,6 @@ import {
5555
AwsLambdaInstrumentation,
5656
AwsLambdaInstrumentationConfig,
5757
} from '@opentelemetry/instrumentation-aws-lambda';
58-
import { DnsInstrumentation } from '@opentelemetry/instrumentation-dns';
59-
import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
60-
import { GraphQLInstrumentation } from '@opentelemetry/instrumentation-graphql';
61-
import { GrpcInstrumentation } from '@opentelemetry/instrumentation-grpc';
62-
import { HapiInstrumentation } from '@opentelemetry/instrumentation-hapi';
63-
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
64-
import { IORedisInstrumentation } from '@opentelemetry/instrumentation-ioredis';
65-
import { KoaInstrumentation } from '@opentelemetry/instrumentation-koa';
66-
import { MongoDBInstrumentation } from '@opentelemetry/instrumentation-mongodb';
67-
import { MySQLInstrumentation } from '@opentelemetry/instrumentation-mysql';
68-
import { NetInstrumentation } from '@opentelemetry/instrumentation-net';
69-
import { PgInstrumentation } from '@opentelemetry/instrumentation-pg';
70-
import { RedisInstrumentation } from '@opentelemetry/instrumentation-redis';
7158
import { AWSXRayPropagator } from '@opentelemetry/propagator-aws-xray';
7259
import { AWSXRayLambdaPropagator } from '@opentelemetry/propagator-aws-xray-lambda';
7360

@@ -137,54 +124,91 @@ function getActiveInstumentations(): Set<string> {
137124
return instrumentationSet;
138125
}
139126

140-
function defaultConfigureInstrumentations() {
127+
async function defaultConfigureInstrumentations() {
141128
const instrumentations = [];
142129
const activeInstrumentations = getActiveInstumentations();
143-
// Use require statements for instrumentation
144-
// to avoid having to have transitive dependencies on all the typescript definitions.
145130
if (activeInstrumentations.has('dns')) {
131+
const { DnsInstrumentation } = await import(
132+
'@opentelemetry/instrumentation-dns'
133+
);
146134
instrumentations.push(new DnsInstrumentation());
147135
}
148136
if (activeInstrumentations.has('express')) {
137+
const { ExpressInstrumentation } = await import(
138+
'@opentelemetry/instrumentation-express'
139+
);
149140
instrumentations.push(new ExpressInstrumentation());
150141
}
151142
if (activeInstrumentations.has('graphql')) {
143+
const { GraphQLInstrumentation } = await import(
144+
'@opentelemetry/instrumentation-graphql'
145+
);
152146
instrumentations.push(new GraphQLInstrumentation());
153147
}
154148
if (activeInstrumentations.has('grpc')) {
149+
const { GrpcInstrumentation } = await import(
150+
'@opentelemetry/instrumentation-grpc'
151+
);
155152
instrumentations.push(new GrpcInstrumentation());
156153
}
157154
if (activeInstrumentations.has('hapi')) {
155+
const { HapiInstrumentation } = await import(
156+
'@opentelemetry/instrumentation-hapi'
157+
);
158158
instrumentations.push(new HapiInstrumentation());
159159
}
160160
if (activeInstrumentations.has('http')) {
161+
const { HttpInstrumentation } = await import(
162+
'@opentelemetry/instrumentation-http'
163+
);
161164
instrumentations.push(new HttpInstrumentation());
162165
}
163166
if (activeInstrumentations.has('ioredis')) {
167+
const { IORedisInstrumentation } = await import(
168+
'@opentelemetry/instrumentation-ioredis'
169+
);
164170
instrumentations.push(new IORedisInstrumentation());
165171
}
166172
if (activeInstrumentations.has('koa')) {
173+
const { KoaInstrumentation } = await import(
174+
'@opentelemetry/instrumentation-koa'
175+
);
167176
instrumentations.push(new KoaInstrumentation());
168177
}
169178
if (activeInstrumentations.has('mongodb')) {
179+
const { MongoDBInstrumentation } = await import(
180+
'@opentelemetry/instrumentation-mongodb'
181+
);
170182
instrumentations.push(new MongoDBInstrumentation());
171183
}
172184
if (activeInstrumentations.has('mysql')) {
185+
const { MySQLInstrumentation } = await import(
186+
'@opentelemetry/instrumentation-mysql'
187+
);
173188
instrumentations.push(new MySQLInstrumentation());
174189
}
175190
if (activeInstrumentations.has('net')) {
191+
const { NetInstrumentation } = await import(
192+
'@opentelemetry/instrumentation-net'
193+
);
176194
instrumentations.push(new NetInstrumentation());
177195
}
178196
if (activeInstrumentations.has('pg')) {
197+
const { PgInstrumentation } = await import(
198+
'@opentelemetry/instrumentation-pg'
199+
);
179200
instrumentations.push(new PgInstrumentation());
180201
}
181202
if (activeInstrumentations.has('redis')) {
203+
const { RedisInstrumentation } = await import(
204+
'@opentelemetry/instrumentation-redis'
205+
);
182206
instrumentations.push(new RedisInstrumentation());
183207
}
184208
return instrumentations;
185209
}
186210

187-
function createInstrumentations() {
211+
async function createInstrumentations() {
188212
return [
189213
new AwsInstrumentation(
190214
typeof configureAwsInstrumentation === 'function'
@@ -197,8 +221,8 @@ function createInstrumentations() {
197221
: {},
198222
),
199223
...(typeof configureInstrumentations === 'function'
200-
? configureInstrumentations
201-
: defaultConfigureInstrumentations)(),
224+
? configureInstrumentations()
225+
: await defaultConfigureInstrumentations()),
202226
];
203227
}
204228

@@ -240,7 +264,7 @@ function getPropagator(): TextMapPropagator {
240264
return new CompositePropagator({ propagators });
241265
}
242266

243-
function initializeProvider() {
267+
async function initializeProvider() {
244268
const resource = detectResourcesSync({
245269
detectors: [awsLambdaDetector, envDetector, processDetector],
246270
});
@@ -324,7 +348,7 @@ function initializeProvider() {
324348
// to prevent additional coldstart overhead
325349
// caused by creations and initializations of instrumentations.
326350
if (!instrumentations || !instrumentations.length) {
327-
instrumentations = createInstrumentations();
351+
instrumentations = await createInstrumentations();
328352
}
329353

330354
// Re-register instrumentation with initialized provider. Patched code will see the update.
@@ -336,35 +360,53 @@ function initializeProvider() {
336360
});
337361
}
338362

339-
export function wrap() {
340-
initializeProvider();
363+
export async function wrap() {
364+
if (!initialized) {
365+
throw new Error('Not initialized yet');
366+
}
367+
368+
await initializeProvider();
341369
}
342370

343-
export function unwrap() {
371+
export async function unwrap() {
372+
if (!initialized) {
373+
throw new Error('Not initialized yet');
374+
}
375+
344376
if (disableInstrumentations) {
345377
disableInstrumentations();
346378
disableInstrumentations = () => {};
347379
}
348380
instrumentations = [];
381+
349382
context.disable();
350383
propagation.disable();
351384
trace.disable();
352385
metrics.disable();
353386
logs.disable();
354387
}
355388

389+
export async function init() {
390+
if (initialized) {
391+
return;
392+
}
393+
394+
instrumentations = await createInstrumentations();
395+
396+
// Register instrumentations synchronously to ensure code is patched even before provider is ready.
397+
disableInstrumentations = registerInstrumentations({
398+
instrumentations,
399+
});
400+
401+
initialized = true;
402+
}
403+
356404
console.log('Registering OpenTelemetry');
357405

406+
let initialized = false;
407+
let instrumentations: Instrumentation[];
408+
let disableInstrumentations: () => void;
409+
358410
// Configure lambda logging
359411
const logLevel = getEnv().OTEL_LOG_LEVEL;
360412
diag.setLogger(new DiagConsoleLogger(), logLevel);
361-
362-
let instrumentations = createInstrumentations();
363-
let disableInstrumentations: () => void;
364-
365-
// Register instrumentations synchronously to ensure code is patched even before provider is ready.
366-
disableInstrumentations = registerInstrumentations({
367-
instrumentations,
368-
});
369-
370-
wrap();

nodejs/packages/layer/test/handler.spec.mjs

+9-7
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
} from '@opentelemetry/sdk-trace-base';
1414

1515
import { registerLoader } from '../src/loader.mjs';
16-
import { wrap, unwrap } from '../build/src/wrapper.js';
16+
import { init, wrap, unwrap } from '../build/src/wrapper.js';
1717

1818
const DIR_NAME = path.dirname(url.fileURLToPath(import.meta.url));
1919

@@ -25,7 +25,7 @@ const assertHandlerSpan = (span) => {
2525
assert.strictEqual(span.status.message, undefined);
2626
};
2727

28-
describe('when loading ESM module', () => {
28+
describe('when loading ESM module', async () => {
2929
let oldEnv;
3030
const memoryExporter = new InMemorySpanExporter();
3131

@@ -35,6 +35,8 @@ describe('when loading ESM module', () => {
3535
awsRequestId: 'aws_request_id',
3636
};
3737

38+
await init();
39+
3840
const initializeHandler = async (handler) => {
3941
process.env._HANDLER = handler;
4042

@@ -45,7 +47,7 @@ describe('when loading ESM module', () => {
4547
global.configureMeterProvider = (_) => {};
4648
global.configureLoggerProvider = (_) => {};
4749

48-
wrap();
50+
await wrap();
4951
};
5052

5153
const loadHandler = async (handler) => {
@@ -61,18 +63,18 @@ describe('when loading ESM module', () => {
6163
registerLoader();
6264
});
6365

64-
beforeEach(() => {
66+
beforeEach(async () => {
6567
oldEnv = { ...process.env };
6668
process.env.LAMBDA_TASK_ROOT = DIR_NAME;
6769

68-
unwrap();
70+
await unwrap();
6971
});
7072

71-
afterEach(() => {
73+
afterEach(async () => {
7274
process.env = oldEnv;
7375
memoryExporter.reset();
7476

75-
unwrap();
77+
await unwrap();
7678
});
7779

7880
it('should wrap ESM file handler with .mjs extension', async () => {

nodejs/packages/layer/test/handler.spec.ts

+9-7
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from '@opentelemetry/sdk-trace-base';
1111
import { Context } from 'aws-lambda';
1212

13-
import { wrap, unwrap } from '../src/wrapper';
13+
import { init, wrap, unwrap } from '../src/wrapper';
1414

1515
const assertHandlerSpan = (span: ReadableSpan) => {
1616
assert.strictEqual(span.kind, SpanKind.SERVER);
@@ -20,7 +20,7 @@ const assertHandlerSpan = (span: ReadableSpan) => {
2020
assert.strictEqual(span.status.message, undefined);
2121
};
2222

23-
describe('when loading ESM module', () => {
23+
describe('when loading ESM module', async () => {
2424
let oldEnv: NodeJS.ProcessEnv;
2525
const memoryExporter = new InMemorySpanExporter();
2626

@@ -30,6 +30,8 @@ describe('when loading ESM module', () => {
3030
awsRequestId: 'aws_request_id',
3131
} as Context;
3232

33+
await init();
34+
3335
const initializeHandler = async (handler: string) => {
3436
process.env._HANDLER = handler;
3537

@@ -42,7 +44,7 @@ describe('when loading ESM module', () => {
4244
global.configureMeterProvider = _ => {};
4345
global.configureLoggerProvider = _ => {};
4446

45-
wrap();
47+
await wrap();
4648
};
4749

4850
const loadHandler = async (handler: string) => {
@@ -57,18 +59,18 @@ describe('when loading ESM module', () => {
5759
return await loadHandler(handlerFileName);
5860
};
5961

60-
beforeEach(() => {
62+
beforeEach(async () => {
6163
oldEnv = { ...process.env };
6264
process.env.LAMBDA_TASK_ROOT = __dirname;
6365

64-
unwrap();
66+
await unwrap();
6567
});
6668

67-
afterEach(() => {
69+
afterEach(async () => {
6870
process.env = oldEnv;
6971
memoryExporter.reset();
7072

71-
unwrap();
73+
await unwrap();
7274
});
7375

7476
it('should wrap CommonJS file handler with .cjs extension', async () => {

0 commit comments

Comments
 (0)