@senzops/apm-node 1.2.7 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +479 -398
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.global.js +1 -1
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/register.js +1 -1
- package/dist/register.js.map +1 -1
- package/dist/register.mjs +1 -1
- package/dist/register.mjs.map +1 -1
- package/package.json +1 -1
- package/src/core/client.ts +57 -0
- package/src/core/context.ts +71 -9
- package/src/core/transport.ts +20 -3
- package/src/core/types.ts +5 -1
- package/src/index.ts +4 -0
- package/src/instrumentation/amqplib.ts +371 -0
- package/src/instrumentation/anthropic.ts +245 -0
- package/src/instrumentation/aws-sdk.ts +403 -0
- package/src/instrumentation/azure-openai.ts +177 -0
- package/src/instrumentation/bunyan.ts +93 -0
- package/src/instrumentation/cassandra.ts +367 -0
- package/src/instrumentation/cohere.ts +227 -0
- package/src/instrumentation/connect.ts +200 -0
- package/src/instrumentation/dataloader.ts +291 -0
- package/src/instrumentation/dns.ts +220 -0
- package/src/instrumentation/firebase.ts +445 -0
- package/src/instrumentation/fs.ts +260 -0
- package/src/instrumentation/generic-pool.ts +317 -0
- package/src/instrumentation/google-genai.ts +426 -0
- package/src/instrumentation/graphql.ts +434 -0
- package/src/instrumentation/grpc.ts +666 -0
- package/src/instrumentation/hapi.ts +257 -0
- package/src/instrumentation/kafka.ts +360 -0
- package/src/instrumentation/knex.ts +249 -0
- package/src/instrumentation/lru-memoizer.ts +175 -0
- package/src/instrumentation/memcached.ts +190 -0
- package/src/instrumentation/mistral.ts +254 -0
- package/src/instrumentation/nestjs.ts +243 -0
- package/src/instrumentation/net.ts +171 -0
- package/src/instrumentation/openai.ts +281 -0
- package/src/instrumentation/pino.ts +170 -0
- package/src/instrumentation/restify.ts +213 -0
- package/src/instrumentation/runtime.ts +352 -0
- package/src/instrumentation/socketio.ts +272 -0
- package/src/instrumentation/tedious.ts +509 -0
- package/src/instrumentation/winston.ts +149 -0
- package/src/register.ts +22 -3
- package/src/wrappers/lambda.ts +417 -0
- package/tsup.config.ts +3 -3
- package/wiki.md +1547 -852
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import { client } from '../core/client';
|
|
2
|
+
import { normalizePath } from '../core/normalizer';
|
|
3
|
+
import { getClientIp } from '../utils/getClientIp';
|
|
4
|
+
import { generateTraceId } from '../utils/ids';
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// AWS Lambda Handler Wrapper
|
|
8
|
+
//
|
|
9
|
+
// Provides automatic APM instrumentation for AWS Lambda functions.
|
|
10
|
+
//
|
|
11
|
+
// Features:
|
|
12
|
+
// 1. Cold start detection — tags first invocation per container
|
|
13
|
+
// 2. Lambda context extraction — faas.*, cloud.*, aws.* attributes
|
|
14
|
+
// 3. Trigger-type detection — API Gateway v1/v2, ALB, SQS, SNS,
|
|
15
|
+
// DynamoDB Streams, EventBridge, S3, Scheduled, generic
|
|
16
|
+
// 4. Forced flush before response — ensures telemetry delivery
|
|
17
|
+
// since Lambda freezes the process between invocations
|
|
18
|
+
// 5. Lambda Extensions API registration — registers as internal
|
|
19
|
+
// extension for SHUTDOWN lifecycle event as a safety-net flush
|
|
20
|
+
//
|
|
21
|
+
// Usage:
|
|
22
|
+
// import Senzor from '@anthropic/senzor-node';
|
|
23
|
+
// export const handler = Senzor.wrapLambda(async (event, context) => {
|
|
24
|
+
// return { statusCode: 200, body: 'OK' };
|
|
25
|
+
// });
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Cold start detection
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
/** Module-level flag — true only for the very first invocation in this container. */
|
|
33
|
+
let isColdStart = true;
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Trigger type detection
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
type LambdaTrigger =
|
|
40
|
+
| 'api-gateway-v1'
|
|
41
|
+
| 'api-gateway-v2'
|
|
42
|
+
| 'alb'
|
|
43
|
+
| 'sqs'
|
|
44
|
+
| 'sns'
|
|
45
|
+
| 'dynamodb-streams'
|
|
46
|
+
| 'eventbridge'
|
|
47
|
+
| 's3'
|
|
48
|
+
| 'scheduled'
|
|
49
|
+
| 'generic';
|
|
50
|
+
|
|
51
|
+
const detectTrigger = (event: any): LambdaTrigger => {
|
|
52
|
+
if (!event || typeof event !== 'object') return 'generic';
|
|
53
|
+
|
|
54
|
+
// API Gateway v2 (HTTP API)
|
|
55
|
+
if (event.requestContext?.http?.method) return 'api-gateway-v2';
|
|
56
|
+
|
|
57
|
+
// API Gateway v1 (REST API)
|
|
58
|
+
if (event.requestContext?.httpMethod || event.httpMethod) return 'api-gateway-v1';
|
|
59
|
+
|
|
60
|
+
// ALB (Application Load Balancer)
|
|
61
|
+
if (event.requestContext?.elb) return 'alb';
|
|
62
|
+
|
|
63
|
+
// SQS
|
|
64
|
+
if (Array.isArray(event.Records) && event.Records[0]?.eventSource === 'aws:sqs') return 'sqs';
|
|
65
|
+
|
|
66
|
+
// SNS
|
|
67
|
+
if (Array.isArray(event.Records) && event.Records[0]?.EventSource === 'aws:sns') return 'sns';
|
|
68
|
+
|
|
69
|
+
// DynamoDB Streams
|
|
70
|
+
if (Array.isArray(event.Records) && event.Records[0]?.eventSource === 'aws:dynamodb') return 'dynamodb-streams';
|
|
71
|
+
|
|
72
|
+
// S3
|
|
73
|
+
if (Array.isArray(event.Records) && event.Records[0]?.eventSource === 'aws:s3') return 's3';
|
|
74
|
+
|
|
75
|
+
// EventBridge / CloudWatch Events
|
|
76
|
+
if (event.source && event['detail-type'] && event.detail) return 'eventbridge';
|
|
77
|
+
|
|
78
|
+
// CloudWatch Scheduled Event
|
|
79
|
+
if (event.source === 'aws.events' || event['detail-type'] === 'Scheduled Event') return 'scheduled';
|
|
80
|
+
|
|
81
|
+
return 'generic';
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// HTTP event extraction (API Gateway v1/v2, ALB)
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
interface HttpEventInfo {
|
|
89
|
+
method: string;
|
|
90
|
+
path: string;
|
|
91
|
+
headers: Record<string, string>;
|
|
92
|
+
statusCode?: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const extractHttpEvent = (event: any, trigger: LambdaTrigger): HttpEventInfo | null => {
|
|
96
|
+
if (trigger === 'api-gateway-v2') {
|
|
97
|
+
const http = event.requestContext?.http;
|
|
98
|
+
return {
|
|
99
|
+
method: http?.method || 'GET',
|
|
100
|
+
path: event.rawPath || event.requestContext?.http?.path || '/',
|
|
101
|
+
headers: normalizeHeaders(event.headers),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (trigger === 'api-gateway-v1') {
|
|
106
|
+
return {
|
|
107
|
+
method: event.httpMethod || event.requestContext?.httpMethod || 'GET',
|
|
108
|
+
path: event.path || event.resource || '/',
|
|
109
|
+
headers: normalizeHeaders(event.headers),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (trigger === 'alb') {
|
|
114
|
+
return {
|
|
115
|
+
method: event.httpMethod || 'GET',
|
|
116
|
+
path: event.path || '/',
|
|
117
|
+
headers: normalizeHeaders(event.headers),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return null;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const normalizeHeaders = (headers: any): Record<string, string> => {
|
|
125
|
+
if (!headers || typeof headers !== 'object') return {};
|
|
126
|
+
const normalized: Record<string, string> = {};
|
|
127
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
128
|
+
normalized[key.toLowerCase()] = String(value);
|
|
129
|
+
}
|
|
130
|
+
return normalized;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// Lambda context attribute extraction
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
const extractLambdaAttributes = (
|
|
138
|
+
event: any,
|
|
139
|
+
context: any,
|
|
140
|
+
trigger: LambdaTrigger,
|
|
141
|
+
coldStart: boolean
|
|
142
|
+
): Record<string, any> => {
|
|
143
|
+
const attrs: Record<string, any> = {
|
|
144
|
+
'faas.trigger': mapTriggerToFaasTrigger(trigger),
|
|
145
|
+
'faas.coldstart': coldStart,
|
|
146
|
+
'cloud.provider': 'aws',
|
|
147
|
+
'cloud.platform': 'aws_lambda',
|
|
148
|
+
'firebase.service': undefined, // clear any inherited
|
|
149
|
+
library: 'aws-lambda',
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Lambda context fields
|
|
153
|
+
if (context) {
|
|
154
|
+
if (context.functionName) attrs['faas.name'] = context.functionName;
|
|
155
|
+
if (context.functionVersion) attrs['faas.version'] = context.functionVersion;
|
|
156
|
+
if (context.awsRequestId) attrs['faas.execution'] = context.awsRequestId;
|
|
157
|
+
if (context.invokedFunctionArn) attrs['cloud.resource_id'] = context.invokedFunctionArn;
|
|
158
|
+
if (context.memoryLimitInMB) attrs['faas.max_memory'] = Number(context.memoryLimitInMB);
|
|
159
|
+
if (context.logGroupName) attrs['aws.log.group.names'] = context.logGroupName;
|
|
160
|
+
if (context.logStreamName) attrs['aws.log.stream.names'] = context.logStreamName;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Region from environment
|
|
164
|
+
const region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION;
|
|
165
|
+
if (region) attrs['cloud.region'] = region;
|
|
166
|
+
|
|
167
|
+
// Account ID from ARN
|
|
168
|
+
if (context?.invokedFunctionArn) {
|
|
169
|
+
const arnParts = context.invokedFunctionArn.split(':');
|
|
170
|
+
if (arnParts.length >= 5) {
|
|
171
|
+
attrs['cloud.account.id'] = arnParts[4];
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Trigger-specific attributes
|
|
176
|
+
switch (trigger) {
|
|
177
|
+
case 'sqs':
|
|
178
|
+
if (Array.isArray(event.Records)) {
|
|
179
|
+
attrs['messaging.system'] = 'aws_sqs';
|
|
180
|
+
attrs['messaging.batch.message_count'] = event.Records.length;
|
|
181
|
+
const arnSrc = event.Records[0]?.eventSourceARN;
|
|
182
|
+
if (arnSrc) attrs['messaging.source.name'] = arnSrc.split(':').pop();
|
|
183
|
+
}
|
|
184
|
+
break;
|
|
185
|
+
|
|
186
|
+
case 'sns':
|
|
187
|
+
if (Array.isArray(event.Records)) {
|
|
188
|
+
attrs['messaging.system'] = 'aws_sns';
|
|
189
|
+
const topicArn = event.Records[0]?.Sns?.TopicArn;
|
|
190
|
+
if (topicArn) attrs['messaging.source.name'] = topicArn.split(':').pop();
|
|
191
|
+
}
|
|
192
|
+
break;
|
|
193
|
+
|
|
194
|
+
case 'dynamodb-streams':
|
|
195
|
+
if (Array.isArray(event.Records)) {
|
|
196
|
+
attrs['aws.dynamodb.table_names'] = [
|
|
197
|
+
...new Set(
|
|
198
|
+
event.Records
|
|
199
|
+
.map((r: any) => r.eventSourceARN?.split('/')[1])
|
|
200
|
+
.filter(Boolean)
|
|
201
|
+
),
|
|
202
|
+
];
|
|
203
|
+
attrs['messaging.batch.message_count'] = event.Records.length;
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
|
|
207
|
+
case 's3':
|
|
208
|
+
if (Array.isArray(event.Records) && event.Records[0]?.s3) {
|
|
209
|
+
attrs['aws.s3.bucket'] = event.Records[0].s3.bucket?.name;
|
|
210
|
+
attrs['aws.s3.key'] = event.Records[0].s3.object?.key;
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
213
|
+
|
|
214
|
+
case 'eventbridge':
|
|
215
|
+
if (event.source) attrs['aws.eventbridge.source'] = event.source;
|
|
216
|
+
if (event['detail-type']) attrs['aws.eventbridge.detail_type'] = event['detail-type'];
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return attrs;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const mapTriggerToFaasTrigger = (trigger: LambdaTrigger): string => {
|
|
224
|
+
switch (trigger) {
|
|
225
|
+
case 'api-gateway-v1':
|
|
226
|
+
case 'api-gateway-v2':
|
|
227
|
+
case 'alb':
|
|
228
|
+
return 'http';
|
|
229
|
+
case 'sqs':
|
|
230
|
+
case 'sns':
|
|
231
|
+
return 'pubsub';
|
|
232
|
+
case 'dynamodb-streams':
|
|
233
|
+
return 'datasource';
|
|
234
|
+
case 's3':
|
|
235
|
+
return 'datasource';
|
|
236
|
+
case 'eventbridge':
|
|
237
|
+
case 'scheduled':
|
|
238
|
+
return 'timer';
|
|
239
|
+
default:
|
|
240
|
+
return 'other';
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
// Response status extraction
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
|
|
248
|
+
const extractStatusFromResponse = (result: any, trigger: LambdaTrigger): number => {
|
|
249
|
+
// HTTP triggers: response has statusCode
|
|
250
|
+
if (
|
|
251
|
+
(trigger === 'api-gateway-v1' || trigger === 'api-gateway-v2' || trigger === 'alb') &&
|
|
252
|
+
result &&
|
|
253
|
+
typeof result === 'object'
|
|
254
|
+
) {
|
|
255
|
+
return typeof result.statusCode === 'number' ? result.statusCode : 200;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Non-HTTP: success = 200
|
|
259
|
+
return 200;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
// Lambda Extensions API — internal extension registration
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Register as a Lambda internal extension to receive SHUTDOWN events.
|
|
268
|
+
* This is a safety-net: if the process is about to be terminated,
|
|
269
|
+
* we get a last chance to flush telemetry.
|
|
270
|
+
*
|
|
271
|
+
* Only runs when the Lambda Extensions API is available (runtime >= 2020-01-01).
|
|
272
|
+
* Failures are silently ignored — the wrapper's own forced flush is the primary mechanism.
|
|
273
|
+
*/
|
|
274
|
+
const registerExtension = (() => {
|
|
275
|
+
let registered = false;
|
|
276
|
+
|
|
277
|
+
return (onShutdown: () => Promise<void>) => {
|
|
278
|
+
if (registered) return;
|
|
279
|
+
registered = true;
|
|
280
|
+
|
|
281
|
+
const runtimeApi = process.env.AWS_LAMBDA_RUNTIME_API;
|
|
282
|
+
if (!runtimeApi) return;
|
|
283
|
+
|
|
284
|
+
const extensionName = 'senzor-apm';
|
|
285
|
+
const registerUrl = `http://${runtimeApi}/2020-01-01/extension/register`;
|
|
286
|
+
|
|
287
|
+
// Fire-and-forget registration
|
|
288
|
+
(async () => {
|
|
289
|
+
try {
|
|
290
|
+
const registerResponse = await fetch(registerUrl, {
|
|
291
|
+
method: 'POST',
|
|
292
|
+
headers: {
|
|
293
|
+
'Content-Type': 'application/json',
|
|
294
|
+
'Lambda-Extension-Name': extensionName,
|
|
295
|
+
},
|
|
296
|
+
body: JSON.stringify({ events: ['SHUTDOWN'] }),
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
if (!registerResponse.ok) return;
|
|
300
|
+
|
|
301
|
+
const extensionId = registerResponse.headers.get('Lambda-Extension-Identifier');
|
|
302
|
+
if (!extensionId) return;
|
|
303
|
+
|
|
304
|
+
// Event loop: wait for SHUTDOWN
|
|
305
|
+
const nextUrl = `http://${runtimeApi}/2020-01-01/extension/event/next`;
|
|
306
|
+
|
|
307
|
+
// This blocks until Lambda sends SHUTDOWN — runs on a background "thread"
|
|
308
|
+
// via the microtask queue; does not block the handler.
|
|
309
|
+
const waitForShutdown = async () => {
|
|
310
|
+
try {
|
|
311
|
+
const eventResponse = await fetch(nextUrl, {
|
|
312
|
+
method: 'GET',
|
|
313
|
+
headers: { 'Lambda-Extension-Identifier': extensionId },
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
if (eventResponse.ok) {
|
|
317
|
+
const event = await eventResponse.json() as any;
|
|
318
|
+
if (event.eventType === 'SHUTDOWN') {
|
|
319
|
+
await onShutdown();
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
} catch {
|
|
323
|
+
// Extension event loop failure — primary flush in handler covers this
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// Start the event loop (non-blocking)
|
|
328
|
+
waitForShutdown();
|
|
329
|
+
} catch {
|
|
330
|
+
// Registration failure is expected outside Lambda or in older runtimes
|
|
331
|
+
}
|
|
332
|
+
})();
|
|
333
|
+
};
|
|
334
|
+
})();
|
|
335
|
+
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
// Public API: wrapLambda
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
339
|
+
|
|
340
|
+
type LambdaHandler<TEvent = any, TResult = any> = (
|
|
341
|
+
event: TEvent,
|
|
342
|
+
context: any,
|
|
343
|
+
) => Promise<TResult>;
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Wraps an AWS Lambda handler function with Senzor APM instrumentation.
|
|
347
|
+
*
|
|
348
|
+
* @example
|
|
349
|
+
* ```typescript
|
|
350
|
+
* import Senzor from '@senzor/apm-node';
|
|
351
|
+
*
|
|
352
|
+
* Senzor.init({ apiKey: process.env.SENZOR_API_KEY });
|
|
353
|
+
*
|
|
354
|
+
* export const handler = Senzor.wrapLambda(async (event, context) => {
|
|
355
|
+
* // Your Lambda logic here
|
|
356
|
+
* return { statusCode: 200, body: JSON.stringify({ ok: true }) };
|
|
357
|
+
* });
|
|
358
|
+
* ```
|
|
359
|
+
*/
|
|
360
|
+
export const wrapLambda = <TEvent = any, TResult = any>(
|
|
361
|
+
handler: LambdaHandler<TEvent, TResult>,
|
|
362
|
+
): LambdaHandler<TEvent, TResult> => {
|
|
363
|
+
// Register extension on first wrap (idempotent)
|
|
364
|
+
registerExtension(() => client.flush());
|
|
365
|
+
|
|
366
|
+
return async (event: TEvent, context: any): Promise<TResult> => {
|
|
367
|
+
const coldStart = isColdStart;
|
|
368
|
+
if (isColdStart) isColdStart = false;
|
|
369
|
+
|
|
370
|
+
const trigger = detectTrigger(event);
|
|
371
|
+
const lambdaAttrs = extractLambdaAttributes(event, context, trigger, coldStart);
|
|
372
|
+
const httpInfo = extractHttpEvent(event, trigger);
|
|
373
|
+
|
|
374
|
+
// For HTTP triggers, use proper HTTP trace semantics
|
|
375
|
+
const method = httpInfo?.method || trigger.toUpperCase();
|
|
376
|
+
const path = httpInfo?.path || `/${context?.functionName || 'lambda'}`;
|
|
377
|
+
const route = httpInfo ? normalizePath(httpInfo.path) : context?.functionName || 'lambda';
|
|
378
|
+
const headers = httpInfo?.headers || {};
|
|
379
|
+
|
|
380
|
+
return client.startTrace(
|
|
381
|
+
{
|
|
382
|
+
method,
|
|
383
|
+
path,
|
|
384
|
+
route,
|
|
385
|
+
ip: httpInfo ? getClientIp({ headers }) : undefined,
|
|
386
|
+
userAgent: headers['user-agent'],
|
|
387
|
+
headers,
|
|
388
|
+
...lambdaAttrs,
|
|
389
|
+
},
|
|
390
|
+
async () => {
|
|
391
|
+
let status = 500;
|
|
392
|
+
try {
|
|
393
|
+
const result = await handler(event, context);
|
|
394
|
+
status = extractStatusFromResponse(result, trigger);
|
|
395
|
+
return result;
|
|
396
|
+
} catch (err: any) {
|
|
397
|
+
client.captureError(err, {
|
|
398
|
+
'faas.name': context?.functionName,
|
|
399
|
+
'faas.execution': context?.awsRequestId,
|
|
400
|
+
trigger,
|
|
401
|
+
});
|
|
402
|
+
throw err;
|
|
403
|
+
} finally {
|
|
404
|
+
client.endTrace(status, {
|
|
405
|
+
route,
|
|
406
|
+
...lambdaAttrs,
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// Force flush before Lambda freezes the execution environment.
|
|
410
|
+
// This is critical: Lambda may freeze or terminate the process
|
|
411
|
+
// immediately after the handler returns.
|
|
412
|
+
await client.flush();
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
);
|
|
416
|
+
};
|
|
417
|
+
};
|
package/tsup.config.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { defineConfig } from 'tsup';
|
|
2
2
|
|
|
3
3
|
const NODE_BUILTINS = [
|
|
4
|
-
'http', 'https', 'url', 'net', 'module', 'crypto', 'async_hooks',
|
|
5
|
-
'node:http', 'node:https', 'node:url', 'node:net', 'node:module',
|
|
6
|
-
'node:crypto', 'node:async_hooks'
|
|
4
|
+
'http', 'https', 'url', 'net', 'dns', 'module', 'crypto', 'async_hooks', 'perf_hooks',
|
|
5
|
+
'node:http', 'node:https', 'node:url', 'node:net', 'node:dns', 'node:module',
|
|
6
|
+
'node:crypto', 'node:async_hooks', 'node:perf_hooks'
|
|
7
7
|
];
|
|
8
8
|
|
|
9
9
|
export default defineConfig([
|