@senzops/apm-node 1.2.8 → 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.
Files changed (54) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +479 -398
  3. package/dist/index.d.mts +5 -0
  4. package/dist/index.d.ts +5 -0
  5. package/dist/index.global.js +1 -1
  6. package/dist/index.global.js.map +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +1 -1
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/register.js +1 -1
  12. package/dist/register.js.map +1 -1
  13. package/dist/register.mjs +1 -1
  14. package/dist/register.mjs.map +1 -1
  15. package/package.json +1 -1
  16. package/src/core/client.ts +57 -0
  17. package/src/core/transport.ts +20 -3
  18. package/src/core/types.ts +5 -1
  19. package/src/index.ts +4 -0
  20. package/src/instrumentation/amqplib.ts +371 -0
  21. package/src/instrumentation/anthropic.ts +245 -0
  22. package/src/instrumentation/aws-sdk.ts +403 -0
  23. package/src/instrumentation/azure-openai.ts +177 -0
  24. package/src/instrumentation/bunyan.ts +93 -0
  25. package/src/instrumentation/cassandra.ts +367 -0
  26. package/src/instrumentation/cohere.ts +227 -0
  27. package/src/instrumentation/connect.ts +200 -0
  28. package/src/instrumentation/dataloader.ts +291 -0
  29. package/src/instrumentation/dns.ts +220 -0
  30. package/src/instrumentation/firebase.ts +445 -0
  31. package/src/instrumentation/fs.ts +260 -0
  32. package/src/instrumentation/generic-pool.ts +317 -0
  33. package/src/instrumentation/google-genai.ts +426 -0
  34. package/src/instrumentation/graphql.ts +434 -0
  35. package/src/instrumentation/grpc.ts +666 -0
  36. package/src/instrumentation/hapi.ts +257 -0
  37. package/src/instrumentation/kafka.ts +360 -0
  38. package/src/instrumentation/knex.ts +249 -0
  39. package/src/instrumentation/lru-memoizer.ts +175 -0
  40. package/src/instrumentation/memcached.ts +190 -0
  41. package/src/instrumentation/mistral.ts +254 -0
  42. package/src/instrumentation/nestjs.ts +243 -0
  43. package/src/instrumentation/net.ts +171 -0
  44. package/src/instrumentation/openai.ts +281 -0
  45. package/src/instrumentation/pino.ts +170 -0
  46. package/src/instrumentation/restify.ts +213 -0
  47. package/src/instrumentation/runtime.ts +352 -0
  48. package/src/instrumentation/socketio.ts +272 -0
  49. package/src/instrumentation/tedious.ts +509 -0
  50. package/src/instrumentation/winston.ts +149 -0
  51. package/src/register.ts +22 -3
  52. package/src/wrappers/lambda.ts +417 -0
  53. package/tsup.config.ts +3 -3
  54. package/wiki.md +1547 -852
@@ -0,0 +1,509 @@
1
+ import { getSqlOperation, normalizeSql } from '../core/sanitizer';
2
+ import { SenzorOptions } from '../core/types';
3
+ import { hookRequire } from './hook';
4
+ import { patchMethod } from './patch';
5
+ import { runWithCapturedSpan, startCapturedSpan } from './span';
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // Tedious (SQL Server / MSSQL) Instrumentation
9
+ //
10
+ // Instruments the `tedious` package — the primary pure-JS driver for
11
+ // Microsoft SQL Server used by node-mssql, Knex (mssql dialect),
12
+ // TypeORM, Sequelize, and Prisma (sqlserver provider).
13
+ //
14
+ // Patches Connection.prototype methods:
15
+ // - execSql() — standard parameterized queries
16
+ // - execSqlBatch() — raw SQL batch execution
17
+ // - execBulkLoad() — bulk insert operations
18
+ // - callProcedure() — stored procedure calls
19
+ // - prepare() — prepared statement creation
20
+ // - execute() — prepared statement execution
21
+ //
22
+ // Also instruments `mssql` (node-mssql) — the popular high-level wrapper
23
+ // around tedious — by patching Request.prototype.query/execute/batch.
24
+ //
25
+ // Captured attributes (OTel semantic conventions):
26
+ // - db.system.name: 'mssql'
27
+ // - db.operation.name: operation type
28
+ // - db.query.text: normalized SQL
29
+ // - db.namespace: database name
30
+ // - server.address: SQL Server hostname
31
+ // - server.port: SQL Server port
32
+ // ---------------------------------------------------------------------------
33
+
34
+ /** Extract connection metadata from a tedious Connection instance. */
35
+ const getConnectionMeta = (connection: any): Record<string, any> => {
36
+ const config = connection?.config;
37
+ if (!config) return {};
38
+
39
+ const meta: Record<string, any> = {
40
+ 'db.system.name': 'mssql',
41
+ };
42
+
43
+ if (config.server) meta['server.address'] = config.server;
44
+ if (config.options?.port) meta['server.port'] = config.options.port;
45
+ if (config.options?.database) meta['db.namespace'] = config.options.database;
46
+
47
+ return meta;
48
+ };
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // Connection method patching
52
+ // ---------------------------------------------------------------------------
53
+
54
+ const patchTediousConnection = (tedious: any, options?: SenzorOptions) => {
55
+ const Connection = tedious?.Connection;
56
+ if (!Connection?.prototype) return;
57
+
58
+ const proto = Connection.prototype;
59
+
60
+ // --- execSql(request) ---
61
+ patchMethod(
62
+ proto,
63
+ 'execSql',
64
+ 'senzor.tedious.connection.execSql',
65
+ (original) =>
66
+ function patchedExecSql(this: any, request: any) {
67
+ const sql = request?.sqlTextOrProcedure;
68
+ const operation = getSqlOperation(sql) || 'QUERY';
69
+ const connMeta = getConnectionMeta(this);
70
+
71
+ const span = startCapturedSpan(
72
+ `MSSQL ${operation}`,
73
+ 'db',
74
+ {
75
+ ...connMeta,
76
+ 'db.operation.name': operation,
77
+ 'db.query.text': normalizeSql(sql, options),
78
+ 'tedious.method': 'execSql',
79
+ library: 'tedious',
80
+ },
81
+ options
82
+ );
83
+
84
+ if (!span) return original.call(this, request);
85
+
86
+ return runWithCapturedSpan(span, () => {
87
+ wrapTediousRequest(request, span);
88
+ return original.call(this, request);
89
+ });
90
+ }
91
+ );
92
+
93
+ // --- execSqlBatch(request) ---
94
+ patchMethod(
95
+ proto,
96
+ 'execSqlBatch',
97
+ 'senzor.tedious.connection.execSqlBatch',
98
+ (original) =>
99
+ function patchedExecSqlBatch(this: any, request: any) {
100
+ const sql = request?.sqlTextOrProcedure;
101
+ const operation = getSqlOperation(sql) || 'BATCH';
102
+ const connMeta = getConnectionMeta(this);
103
+
104
+ const span = startCapturedSpan(
105
+ `MSSQL BATCH ${operation}`,
106
+ 'db',
107
+ {
108
+ ...connMeta,
109
+ 'db.operation.name': `BATCH_${operation}`,
110
+ 'db.query.text': normalizeSql(sql, options),
111
+ 'tedious.method': 'execSqlBatch',
112
+ library: 'tedious',
113
+ },
114
+ options
115
+ );
116
+
117
+ if (!span) return original.call(this, request);
118
+
119
+ return runWithCapturedSpan(span, () => {
120
+ wrapTediousRequest(request, span);
121
+ return original.call(this, request);
122
+ });
123
+ }
124
+ );
125
+
126
+ // --- callProcedure(request) ---
127
+ patchMethod(
128
+ proto,
129
+ 'callProcedure',
130
+ 'senzor.tedious.connection.callProcedure',
131
+ (original) =>
132
+ function patchedCallProcedure(this: any, request: any) {
133
+ const procName = request?.sqlTextOrProcedure || 'unknown';
134
+ const connMeta = getConnectionMeta(this);
135
+
136
+ const span = startCapturedSpan(
137
+ `MSSQL CALL ${procName}`,
138
+ 'db',
139
+ {
140
+ ...connMeta,
141
+ 'db.operation.name': 'CALL',
142
+ 'db.query.text': procName,
143
+ 'db.collection.name': procName,
144
+ 'tedious.method': 'callProcedure',
145
+ library: 'tedious',
146
+ },
147
+ options
148
+ );
149
+
150
+ if (!span) return original.call(this, request);
151
+
152
+ return runWithCapturedSpan(span, () => {
153
+ wrapTediousRequest(request, span);
154
+ return original.call(this, request);
155
+ });
156
+ }
157
+ );
158
+
159
+ // --- execBulkLoad(bulkLoad, rows, callback) ---
160
+ patchMethod(
161
+ proto,
162
+ 'execBulkLoad',
163
+ 'senzor.tedious.connection.execBulkLoad',
164
+ (original) =>
165
+ function patchedExecBulkLoad(this: any, bulkLoad: any, ...args: any[]) {
166
+ const tableName = bulkLoad?.table || 'unknown';
167
+ const connMeta = getConnectionMeta(this);
168
+
169
+ const span = startCapturedSpan(
170
+ `MSSQL BULK INSERT ${tableName}`,
171
+ 'db',
172
+ {
173
+ ...connMeta,
174
+ 'db.operation.name': 'BULK_INSERT',
175
+ 'db.collection.name': tableName,
176
+ 'tedious.method': 'execBulkLoad',
177
+ library: 'tedious',
178
+ },
179
+ options
180
+ );
181
+
182
+ if (!span) return original.call(this, bulkLoad, ...args);
183
+
184
+ return runWithCapturedSpan(span, () => {
185
+ // Wrap the bulkLoad callback
186
+ if (bulkLoad && typeof bulkLoad.callback === 'function') {
187
+ const originalCallback = bulkLoad.callback;
188
+ bulkLoad.callback = function (err: any, rowCount: any) {
189
+ if (err) {
190
+ span.end(500, {
191
+ 'error.message': err.message,
192
+ 'error.type': err.name || 'Error',
193
+ });
194
+ } else {
195
+ span.end(0, { 'db.response.row_count': rowCount });
196
+ }
197
+ return originalCallback.call(this, err, rowCount);
198
+ };
199
+ } else {
200
+ // If no callback on bulkLoad, try wrapping the last arg
201
+ const lastIdx = args.length - 1;
202
+ if (lastIdx >= 0 && typeof args[lastIdx] === 'function') {
203
+ const cb = args[lastIdx];
204
+ args[lastIdx] = function (err: any, rowCount: any) {
205
+ if (err) {
206
+ span.end(500, { 'error.message': err.message });
207
+ } else {
208
+ span.end(0, { 'db.response.row_count': rowCount });
209
+ }
210
+ return cb.apply(this, arguments);
211
+ };
212
+ } else {
213
+ span.end(0);
214
+ }
215
+ }
216
+
217
+ return original.call(this, bulkLoad, ...args);
218
+ });
219
+ }
220
+ );
221
+
222
+ // --- prepare(request) ---
223
+ if (typeof proto.prepare === 'function') {
224
+ patchMethod(
225
+ proto,
226
+ 'prepare',
227
+ 'senzor.tedious.connection.prepare',
228
+ (original) =>
229
+ function patchedPrepare(this: any, request: any) {
230
+ const sql = request?.sqlTextOrProcedure;
231
+ const connMeta = getConnectionMeta(this);
232
+
233
+ const span = startCapturedSpan(
234
+ `MSSQL PREPARE`,
235
+ 'db',
236
+ {
237
+ ...connMeta,
238
+ 'db.operation.name': 'PREPARE',
239
+ 'db.query.text': normalizeSql(sql, options),
240
+ 'tedious.method': 'prepare',
241
+ library: 'tedious',
242
+ },
243
+ options
244
+ );
245
+
246
+ if (!span) return original.call(this, request);
247
+
248
+ return runWithCapturedSpan(span, () => {
249
+ wrapTediousRequest(request, span);
250
+ return original.call(this, request);
251
+ });
252
+ }
253
+ );
254
+ }
255
+
256
+ // --- execute(request, parameters) ---
257
+ if (typeof proto.execute === 'function') {
258
+ patchMethod(
259
+ proto,
260
+ 'execute',
261
+ 'senzor.tedious.connection.execute',
262
+ (original) =>
263
+ function patchedExecute(this: any, request: any, parameters: any) {
264
+ const sql = request?.sqlTextOrProcedure;
265
+ const operation = getSqlOperation(sql) || 'EXECUTE';
266
+ const connMeta = getConnectionMeta(this);
267
+
268
+ const span = startCapturedSpan(
269
+ `MSSQL EXECUTE ${operation}`,
270
+ 'db',
271
+ {
272
+ ...connMeta,
273
+ 'db.operation.name': `EXECUTE_${operation}`,
274
+ 'db.query.text': normalizeSql(sql, options),
275
+ 'tedious.method': 'execute',
276
+ library: 'tedious',
277
+ },
278
+ options
279
+ );
280
+
281
+ if (!span) return original.call(this, request, parameters);
282
+
283
+ return runWithCapturedSpan(span, () => {
284
+ wrapTediousRequest(request, span);
285
+ return original.call(this, request, parameters);
286
+ });
287
+ }
288
+ );
289
+ }
290
+ };
291
+
292
+ // ---------------------------------------------------------------------------
293
+ // Tedious Request callback wrapping
294
+ // ---------------------------------------------------------------------------
295
+
296
+ /** Wrap a tedious Request's completion callback to end the span. */
297
+ const wrapTediousRequest = (request: any, span: any) => {
298
+ if (!request || !span) return;
299
+
300
+ // Tedious Request uses an event-based completion model
301
+ // The 'requestCompleted' callback is the final callback passed to the Request constructor
302
+ if (typeof request.callback === 'function') {
303
+ const originalCallback = request.callback;
304
+ request.callback = function (err: any, rowCount: any, rows: any) {
305
+ if (err) {
306
+ span.end(500, {
307
+ 'error.message': err.message,
308
+ 'error.type': err.name || 'RequestError',
309
+ 'db.error.code': err.code,
310
+ });
311
+ } else {
312
+ span.end(0, {
313
+ 'db.response.row_count': rowCount,
314
+ });
315
+ }
316
+ return originalCallback.call(this, err, rowCount, rows);
317
+ };
318
+ return;
319
+ }
320
+
321
+ // Fallback: listen for events
322
+ if (typeof request.on === 'function') {
323
+ let ended = false;
324
+ request.on('requestCompleted', () => {
325
+ if (!ended) { ended = true; span.end(0); }
326
+ });
327
+ request.on('error', (err: any) => {
328
+ if (!ended) {
329
+ ended = true;
330
+ span.end(500, { 'error.message': err?.message });
331
+ }
332
+ });
333
+ }
334
+ };
335
+
336
+ // ---------------------------------------------------------------------------
337
+ // node-mssql (mssql) wrapper patching
338
+ // ---------------------------------------------------------------------------
339
+
340
+ const patchMssql = (mssql: any, options?: SenzorOptions) => {
341
+ // mssql exports Request, ConnectionPool, etc.
342
+ const RequestClass = mssql?.Request;
343
+ if (!RequestClass?.prototype) return;
344
+
345
+ const proto = RequestClass.prototype;
346
+
347
+ // Patch Request.prototype.query(sql)
348
+ patchMethod(
349
+ proto,
350
+ 'query',
351
+ 'senzor.mssql.request.query',
352
+ (original) =>
353
+ function patchedMssqlQuery(this: any, sql: string) {
354
+ const operation = getSqlOperation(sql) || 'QUERY';
355
+
356
+ const span = startCapturedSpan(
357
+ `MSSQL ${operation}`,
358
+ 'db',
359
+ {
360
+ 'db.system.name': 'mssql',
361
+ 'db.operation.name': operation,
362
+ 'db.query.text': normalizeSql(sql, options),
363
+ library: 'mssql',
364
+ },
365
+ options
366
+ );
367
+
368
+ if (!span) return original.call(this, sql);
369
+
370
+ return runWithCapturedSpan(span, () => {
371
+ try {
372
+ const result = original.call(this, sql);
373
+
374
+ if (result && typeof result.then === 'function') {
375
+ return result.then(
376
+ (value: any) => {
377
+ span.end(0, {
378
+ 'db.response.row_count': value?.recordset?.length ?? value?.rowsAffected?.[0],
379
+ });
380
+ return value;
381
+ },
382
+ (error: any) => {
383
+ span.end(500, {
384
+ 'error.message': error?.message,
385
+ 'error.type': error?.name || 'Error',
386
+ 'db.error.code': error?.code,
387
+ });
388
+ throw error;
389
+ }
390
+ );
391
+ }
392
+
393
+ span.end(0);
394
+ return result;
395
+ } catch (error: any) {
396
+ span.end(500, { 'error.message': error?.message });
397
+ throw error;
398
+ }
399
+ });
400
+ }
401
+ );
402
+
403
+ // Patch Request.prototype.execute(procedure)
404
+ patchMethod(
405
+ proto,
406
+ 'execute',
407
+ 'senzor.mssql.request.execute',
408
+ (original) =>
409
+ function patchedMssqlExecute(this: any, procedure: string) {
410
+ const span = startCapturedSpan(
411
+ `MSSQL CALL ${procedure}`,
412
+ 'db',
413
+ {
414
+ 'db.system.name': 'mssql',
415
+ 'db.operation.name': 'CALL',
416
+ 'db.query.text': procedure,
417
+ 'db.collection.name': procedure,
418
+ library: 'mssql',
419
+ },
420
+ options
421
+ );
422
+
423
+ if (!span) return original.call(this, procedure);
424
+
425
+ return runWithCapturedSpan(span, () => {
426
+ try {
427
+ const result = original.call(this, procedure);
428
+
429
+ if (result && typeof result.then === 'function') {
430
+ return result.then(
431
+ (value: any) => { span.end(0); return value; },
432
+ (error: any) => {
433
+ span.end(500, { 'error.message': error?.message });
434
+ throw error;
435
+ }
436
+ );
437
+ }
438
+
439
+ span.end(0);
440
+ return result;
441
+ } catch (error: any) {
442
+ span.end(500, { 'error.message': error?.message });
443
+ throw error;
444
+ }
445
+ });
446
+ }
447
+ );
448
+
449
+ // Patch Request.prototype.batch(sql)
450
+ if (typeof proto.batch === 'function') {
451
+ patchMethod(
452
+ proto,
453
+ 'batch',
454
+ 'senzor.mssql.request.batch',
455
+ (original) =>
456
+ function patchedMssqlBatch(this: any, sql: string) {
457
+ const operation = getSqlOperation(sql) || 'BATCH';
458
+
459
+ const span = startCapturedSpan(
460
+ `MSSQL BATCH ${operation}`,
461
+ 'db',
462
+ {
463
+ 'db.system.name': 'mssql',
464
+ 'db.operation.name': `BATCH_${operation}`,
465
+ 'db.query.text': normalizeSql(sql, options),
466
+ library: 'mssql',
467
+ },
468
+ options
469
+ );
470
+
471
+ if (!span) return original.call(this, sql);
472
+
473
+ return runWithCapturedSpan(span, () => {
474
+ try {
475
+ const result = original.call(this, sql);
476
+ if (result && typeof result.then === 'function') {
477
+ return result.then(
478
+ (value: any) => { span.end(0); return value; },
479
+ (error: any) => {
480
+ span.end(500, { 'error.message': error?.message });
481
+ throw error;
482
+ }
483
+ );
484
+ }
485
+ span.end(0);
486
+ return result;
487
+ } catch (error: any) {
488
+ span.end(500, { 'error.message': error?.message });
489
+ throw error;
490
+ }
491
+ });
492
+ }
493
+ );
494
+ }
495
+ };
496
+
497
+ // ---------------------------------------------------------------------------
498
+ // Public API
499
+ // ---------------------------------------------------------------------------
500
+
501
+ export const instrumentTedious = (options?: SenzorOptions) => {
502
+ hookRequire('tedious', (exports: any) => {
503
+ patchTediousConnection(exports, options);
504
+ });
505
+
506
+ hookRequire('mssql', (exports: any) => {
507
+ patchMssql(exports, options);
508
+ });
509
+ };
@@ -0,0 +1,149 @@
1
+ import { Context } from '../core/context';
2
+ import { SenzorOptions } from '../core/types';
3
+ import { hookRequire } from './hook';
4
+ import { patchMethod } from './patch';
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Winston Log Correlation
8
+ //
9
+ // Injects traceId and spanId from the active Senzor context into every
10
+ // winston log entry. Enables log-to-trace correlation in the dashboard.
11
+ //
12
+ // Strategy: Patch Logger.prototype.write() to inject trace fields into
13
+ // the info object before it flows to transports. This covers all log
14
+ // methods (info, error, warn, debug, etc.) since they all call write().
15
+ //
16
+ // Injected fields:
17
+ // - traceId: string
18
+ // - spanId: string
19
+ // - senzor.context: 'apm' | 'task'
20
+ // ---------------------------------------------------------------------------
21
+
22
+ /** Get trace correlation fields from the current async context. */
23
+ const getTraceFields = (): Record<string, string> | null => {
24
+ const trace = Context.current();
25
+ if (!trace) return null;
26
+
27
+ const fields: Record<string, string> = {
28
+ traceId: trace.id,
29
+ };
30
+
31
+ if (trace.activeSpanId) {
32
+ fields.spanId = trace.activeSpanId;
33
+ }
34
+
35
+ fields['senzor.context'] = trace.contextType;
36
+
37
+ return fields;
38
+ };
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Logger.prototype.write patching
42
+ // ---------------------------------------------------------------------------
43
+
44
+ const patchWinstonLogger = (winston: any, _options?: SenzorOptions) => {
45
+ // winston exports createLogger, Logger, etc.
46
+ // Logger is at winston.Logger or winston.transports (for older versions)
47
+
48
+ // Try to get Logger from several locations
49
+ let LoggerClass = winston?.Logger;
50
+
51
+ // In winston 3.x, Logger is also accessible via the createLogger factory
52
+ if (!LoggerClass) {
53
+ try {
54
+ const loggerModule = require('winston/lib/winston/logger');
55
+ LoggerClass = loggerModule?.Logger || loggerModule;
56
+ } catch { }
57
+ }
58
+
59
+ if (!LoggerClass?.prototype) return;
60
+
61
+ // Patch write() — the core method all log calls funnel through
62
+ patchMethod(
63
+ LoggerClass.prototype,
64
+ 'write',
65
+ 'senzor.winston.logger.write',
66
+ (original) =>
67
+ function patchedWrite(this: any, info: any) {
68
+ if (info && typeof info === 'object') {
69
+ const traceFields = getTraceFields();
70
+ if (traceFields) {
71
+ // Inject trace fields into the info object
72
+ // Use non-enumerable setter to avoid conflicts with symbols
73
+ info.traceId = traceFields.traceId;
74
+ if (traceFields.spanId) info.spanId = traceFields.spanId;
75
+ info['senzor.context'] = traceFields['senzor.context'];
76
+ }
77
+ }
78
+ return original.call(this, info);
79
+ }
80
+ );
81
+
82
+ // Also patch log() as a safety net for custom transports that call log directly
83
+ patchMethod(
84
+ LoggerClass.prototype,
85
+ 'log',
86
+ 'senzor.winston.logger.log',
87
+ (original) =>
88
+ function patchedLog(this: any, ...args: any[]) {
89
+ // log() normalizes args into an info object, then calls write()
90
+ // Since write() is already patched, we just need to ensure the
91
+ // info object exists for direct log() calls with string args
92
+ //
93
+ // log(level, message) or log({ level, message }) or log(info)
94
+ const traceFields = getTraceFields();
95
+
96
+ if (traceFields && args.length > 0) {
97
+ // If first arg is an info-like object, inject directly
98
+ if (typeof args[0] === 'object' && args[0] !== null && !(args[0] instanceof Error)) {
99
+ args[0] = { ...args[0], ...traceFields };
100
+ }
101
+ // For other signatures, write() patch handles it
102
+ }
103
+
104
+ return original.apply(this, args);
105
+ }
106
+ );
107
+ };
108
+
109
+ // ---------------------------------------------------------------------------
110
+ // createLogger wrapping
111
+ // ---------------------------------------------------------------------------
112
+
113
+ const patchCreateLogger = (winston: any, _options?: SenzorOptions) => {
114
+ if (typeof winston?.createLogger !== 'function') return;
115
+
116
+ patchMethod(
117
+ winston,
118
+ 'createLogger',
119
+ 'senzor.winston.createLogger',
120
+ (original) =>
121
+ function patchedCreateLogger(this: any, opts: any) {
122
+ const logger = original.call(this, opts);
123
+
124
+ // Ensure the logger instance has patched write/log
125
+ // (in case the prototype wasn't patched yet)
126
+ if (logger && !logger.__senzorPatched) {
127
+ const proto = Object.getPrototypeOf(logger);
128
+ if (proto && !proto.__senzorPatched) {
129
+ patchWinstonLogger({ Logger: { prototype: proto } }, _options);
130
+ proto.__senzorPatched = true;
131
+ }
132
+ logger.__senzorPatched = true;
133
+ }
134
+
135
+ return logger;
136
+ }
137
+ );
138
+ };
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // Public API
142
+ // ---------------------------------------------------------------------------
143
+
144
+ export const instrumentWinston = (options?: SenzorOptions) => {
145
+ hookRequire('winston', (exports: any) => {
146
+ patchWinstonLogger(exports, options);
147
+ patchCreateLogger(exports, options);
148
+ });
149
+ };
package/src/register.ts CHANGED
@@ -19,13 +19,27 @@ const endpoint =
19
19
  getEnv('SENZOR_ENDPOINT') ||
20
20
  getEnv('SENZOR_APM_ENDPOINT');
21
21
 
22
+ // ---------------------------------------------------------------------------
23
+ // Lambda environment auto-detection
24
+ //
25
+ // When running inside AWS Lambda, the execution model differs fundamentally
26
+ // from long-running servers:
27
+ // - Runtime metrics (event loop, GC, heap) are meaningless per-invocation
28
+ // - Interval-based flushing wastes resources between frozen invocations
29
+ // - Batch size should be small since each invocation is short-lived
30
+ //
31
+ // We detect Lambda via the AWS_LAMBDA_FUNCTION_NAME env var (always set by
32
+ // the Lambda runtime) and auto-configure optimal defaults.
33
+ // ---------------------------------------------------------------------------
34
+ const isLambda = !!getEnv('AWS_LAMBDA_FUNCTION_NAME');
35
+
22
36
  const options = {
23
37
  apiKey: apiKey || '',
24
38
  endpoint,
25
39
  debug: truthy(getEnv('SENZOR_DEBUG')),
26
40
  autoLogs: getEnv('SENZOR_AUTO_LOGS') === 'false' ? false : undefined,
27
- batchSize: numberFromEnv(getEnv('SENZOR_BATCH_SIZE')),
28
- flushInterval: numberFromEnv(getEnv('SENZOR_FLUSH_INTERVAL')),
41
+ batchSize: numberFromEnv(getEnv('SENZOR_BATCH_SIZE')) ?? (isLambda ? 10 : undefined),
42
+ flushInterval: numberFromEnv(getEnv('SENZOR_FLUSH_INTERVAL')) ?? (isLambda ? 0 : undefined),
29
43
  flushTimeoutMs: numberFromEnv(getEnv('SENZOR_FLUSH_TIMEOUT_MS')),
30
44
  maxQueueSize: numberFromEnv(getEnv('SENZOR_MAX_QUEUE_SIZE')),
31
45
  maxSpansPerTrace: numberFromEnv(getEnv('SENZOR_MAX_SPANS_PER_TRACE')),
@@ -49,7 +63,12 @@ const options = {
49
63
  captureLifecycleHookSpans:
50
64
  getEnv('SENZOR_CAPTURE_LIFECYCLE_HOOK_SPANS') === 'false'
51
65
  ? false
52
- : undefined
66
+ : undefined,
67
+ runtimeMetrics:
68
+ getEnv('SENZOR_RUNTIME_METRICS') === 'false' || isLambda
69
+ ? false
70
+ : undefined,
71
+ runtimeMetricsInterval: numberFromEnv(getEnv('SENZOR_RUNTIME_METRICS_INTERVAL')),
53
72
  };
54
73
 
55
74
  if (apiKey) {