@senzops/apm-node 1.2.8 → 1.3.1
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 +13 -0
- package/README.md +527 -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/lambda-handler.d.mts +13 -0
- package/dist/lambda-handler.d.ts +13 -0
- package/dist/lambda-handler.js +2 -0
- package/dist/lambda-handler.js.map +1 -0
- package/dist/lambda-handler.mjs +2 -0
- package/dist/lambda-handler.mjs.map +1 -0
- 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 +6 -1
- package/src/core/client.ts +57 -0
- 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/lambda-handler.ts +262 -0
- package/src/register.ts +22 -3
- package/src/wrappers/lambda.ts +417 -0
- package/tsup.config.ts +4 -4
- package/wiki.md +1693 -852
package/wiki.md
CHANGED
|
@@ -1,852 +1,1693 @@
|
|
|
1
|
-
# Senzor Node APM SDK
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
The
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
###
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
Senzor.init({
|
|
112
|
-
apiKey: process.env.SENZOR_API_KEY!,
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
```ts
|
|
166
|
-
import
|
|
167
|
-
import Senzor from '@senzops/apm-node';
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
import
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
Senzor.
|
|
286
|
-
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
import {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
- `
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
-
|
|
351
|
-
- `
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
- `
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
}
|
|
553
|
-
```
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
```
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
- `
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
- `
|
|
728
|
-
|
|
729
|
-
- `
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
-
|
|
746
|
-
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
```
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
});
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
```ts
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
```
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
});
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
```
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
1
|
+
# Senzor Node APM SDK — Complete Guide
|
|
2
|
+
|
|
3
|
+
Comprehensive integration, configuration, and operational reference for `@senzops/apm-node`.
|
|
4
|
+
|
|
5
|
+
The SDK captures distributed traces, child spans, errors, logs, background task runs, and runtime metrics from Node.js services and sends them to Senzor. It replaces OpenTelemetry auto-instrumentation with a zero-dependency, Senzor-native alternative.
|
|
6
|
+
|
|
7
|
+
**43 auto-instrumentations. 8 framework wrappers. Zero runtime dependencies.**
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Table of Contents
|
|
12
|
+
|
|
13
|
+
1. [Getting Started](#1-getting-started)
|
|
14
|
+
2. [Startup Modes](#2-startup-modes)
|
|
15
|
+
3. [Framework Integration Guide](#3-framework-integration-guide)
|
|
16
|
+
4. [AWS Lambda Integration](#4-aws-lambda-integration)
|
|
17
|
+
5. [Cloudflare Workers Integration](#5-cloudflare-workers-integration)
|
|
18
|
+
6. [Auto-Instrumentation Reference](#6-auto-instrumentation-reference)
|
|
19
|
+
7. [AI / LLM SDK Instrumentation](#7-ai--llm-sdk-instrumentation)
|
|
20
|
+
8. [Database & Cache Instrumentation](#8-database--cache-instrumentation)
|
|
21
|
+
9. [Messaging & Queue Instrumentation](#9-messaging--queue-instrumentation)
|
|
22
|
+
10. [Cloud Provider Instrumentation](#10-cloud-provider-instrumentation)
|
|
23
|
+
11. [gRPC, GraphQL & Network](#11-grpc-graphql--network)
|
|
24
|
+
12. [Log Correlation](#12-log-correlation)
|
|
25
|
+
13. [Background Task Monitoring](#13-background-task-monitoring)
|
|
26
|
+
14. [Manual Traces & Spans](#14-manual-traces--spans)
|
|
27
|
+
15. [Error Tracking](#15-error-tracking)
|
|
28
|
+
16. [Runtime Metrics](#16-runtime-metrics)
|
|
29
|
+
17. [Distributed Tracing & Context Propagation](#17-distributed-tracing--context-propagation)
|
|
30
|
+
18. [Configuration Reference](#18-configuration-reference)
|
|
31
|
+
19. [Environment Variables](#19-environment-variables)
|
|
32
|
+
20. [Security, Privacy & Cardinality](#20-security-privacy--cardinality)
|
|
33
|
+
21. [Transport Behavior](#21-transport-behavior)
|
|
34
|
+
22. [Ingestion Payload Format](#22-ingestion-payload-format)
|
|
35
|
+
23. [Deployment Patterns](#23-deployment-patterns)
|
|
36
|
+
24. [Troubleshooting](#24-troubleshooting)
|
|
37
|
+
25. [Public API Reference](#25-public-api-reference)
|
|
38
|
+
26. [Build & Publish](#26-build--publish)
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 1. Getting Started
|
|
43
|
+
|
|
44
|
+
### Install
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
npm install @senzops/apm-node
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Minimal Setup
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import Senzor from '@senzops/apm-node';
|
|
54
|
+
|
|
55
|
+
Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
That's it. Once initialized, the SDK automatically instruments all supported libraries that your application imports. No additional configuration or per-library setup is required.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 2. Startup Modes
|
|
63
|
+
|
|
64
|
+
### 2.1 Preload Mode (Recommended for Production)
|
|
65
|
+
|
|
66
|
+
Preload mode gives the SDK the best coverage because it installs instrumentation hooks before the application imports any library.
|
|
67
|
+
|
|
68
|
+
**CommonJS:**
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
SENZOR_API_KEY=sz_apm_xxx node -r @senzops/apm-node/register server.js
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**ESM:**
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
SENZOR_API_KEY=sz_apm_xxx node --import @senzops/apm-node/register server.mjs
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Docker:**
|
|
81
|
+
|
|
82
|
+
```dockerfile
|
|
83
|
+
ENV SENZOR_API_KEY=sz_apm_xxx
|
|
84
|
+
CMD ["node", "-r", "@senzops/apm-node/register", "dist/server.js"]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**PM2:**
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"apps": [{
|
|
92
|
+
"name": "orders-api",
|
|
93
|
+
"script": "dist/server.js",
|
|
94
|
+
"node_args": "-r @senzops/apm-node/register",
|
|
95
|
+
"env": {
|
|
96
|
+
"SENZOR_API_KEY": "sz_apm_xxx",
|
|
97
|
+
"NODE_ENV": "production"
|
|
98
|
+
}
|
|
99
|
+
}]
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 2.2 Programmatic Mode
|
|
104
|
+
|
|
105
|
+
When preload flags are unavailable (some serverless platforms, custom runtimes), initialize at the very top of your entrypoint.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
// init.ts — import this FIRST
|
|
109
|
+
import Senzor from '@senzops/apm-node';
|
|
110
|
+
|
|
111
|
+
Senzor.init({
|
|
112
|
+
apiKey: process.env.SENZOR_API_KEY!,
|
|
113
|
+
endpoint: process.env.SENZOR_ENDPOINT,
|
|
114
|
+
batchSize: 100,
|
|
115
|
+
flushInterval: 10000,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Then import your app
|
|
119
|
+
import './server';
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 2.3 When to Use Which
|
|
123
|
+
|
|
124
|
+
| Scenario | Mode |
|
|
125
|
+
|----------|------|
|
|
126
|
+
| Standard Node.js server (Express, Fastify, Koa, etc.) | Preload |
|
|
127
|
+
| Docker, Kubernetes, PM2 | Preload |
|
|
128
|
+
| Next.js API routes | Programmatic + wrapper |
|
|
129
|
+
| Nuxt / Nitro / H3 | Programmatic + wrapper |
|
|
130
|
+
| Cloudflare Workers | Programmatic + wrapper |
|
|
131
|
+
| AWS Lambda | Programmatic + `wrapLambda`, or Lambda Layer with preload |
|
|
132
|
+
| Serverless without `NODE_OPTIONS` support | Programmatic |
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 3. Framework Integration Guide
|
|
137
|
+
|
|
138
|
+
### 3.1 Express
|
|
139
|
+
|
|
140
|
+
Preload mode captures inbound HTTP automatically. Add middleware for route detection and error capture:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import express from 'express';
|
|
144
|
+
import Senzor from '@senzops/apm-node';
|
|
145
|
+
|
|
146
|
+
Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });
|
|
147
|
+
|
|
148
|
+
const app = express();
|
|
149
|
+
|
|
150
|
+
// MUST be the first middleware
|
|
151
|
+
app.use(Senzor.requestHandler());
|
|
152
|
+
|
|
153
|
+
app.get('/users/:id', async (req, res) => {
|
|
154
|
+
res.json({ id: req.params.id });
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// MUST be the last middleware
|
|
158
|
+
app.use(Senzor.errorHandler());
|
|
159
|
+
|
|
160
|
+
app.listen(3000);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 3.2 Fastify
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
import Fastify from 'fastify';
|
|
167
|
+
import Senzor from '@senzops/apm-node';
|
|
168
|
+
|
|
169
|
+
const fastify = Fastify();
|
|
170
|
+
|
|
171
|
+
fastify.register(Senzor.fastifyPlugin, {
|
|
172
|
+
apiKey: process.env.SENZOR_API_KEY!,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
fastify.get('/orders/:orderId', async (request) => {
|
|
176
|
+
return { orderId: request.params.orderId };
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
await fastify.listen({ port: 3000 });
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 3.3 NestJS
|
|
183
|
+
|
|
184
|
+
NestJS runs on Express (default) or Fastify. For Express:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
import Senzor from '@senzops/apm-node';
|
|
188
|
+
import { NestFactory } from '@nestjs/core';
|
|
189
|
+
import { AppModule } from './app.module';
|
|
190
|
+
|
|
191
|
+
// Initialize BEFORE creating the Nest app
|
|
192
|
+
Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });
|
|
193
|
+
|
|
194
|
+
async function bootstrap() {
|
|
195
|
+
const app = await NestFactory.create(AppModule);
|
|
196
|
+
app.use(Senzor.requestHandler());
|
|
197
|
+
await app.listen(3000);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
bootstrap();
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
The NestJS instrumentation auto-captures:
|
|
204
|
+
- Controller and method resolution
|
|
205
|
+
- Guards, Interceptors, Pipes execution
|
|
206
|
+
- Exception filter processing
|
|
207
|
+
|
|
208
|
+
### 3.4 Koa
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
import Koa from 'koa';
|
|
212
|
+
import Senzor from '@senzops/apm-node';
|
|
213
|
+
|
|
214
|
+
Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });
|
|
215
|
+
|
|
216
|
+
const app = new Koa();
|
|
217
|
+
|
|
218
|
+
app.use(async (ctx) => {
|
|
219
|
+
ctx.body = { ok: true };
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
app.listen(3000);
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
With preload mode, Koa middleware stack is automatically instrumented.
|
|
226
|
+
|
|
227
|
+
### 3.5 Hapi
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
import Hapi from '@hapi/hapi';
|
|
231
|
+
import Senzor from '@senzops/apm-node';
|
|
232
|
+
|
|
233
|
+
Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });
|
|
234
|
+
|
|
235
|
+
const server = Hapi.server({ port: 3000 });
|
|
236
|
+
|
|
237
|
+
server.route({
|
|
238
|
+
method: 'GET',
|
|
239
|
+
path: '/users/{id}',
|
|
240
|
+
handler: (request) => ({ id: request.params.id }),
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
await server.start();
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### 3.6 Restify
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
import restify from 'restify';
|
|
250
|
+
import Senzor from '@senzops/apm-node';
|
|
251
|
+
|
|
252
|
+
Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });
|
|
253
|
+
|
|
254
|
+
const server = restify.createServer();
|
|
255
|
+
|
|
256
|
+
server.get('/users/:id', (req, res, next) => {
|
|
257
|
+
res.send({ id: req.params.id });
|
|
258
|
+
next();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
server.listen(3000);
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### 3.7 Next.js
|
|
265
|
+
|
|
266
|
+
**App Router:**
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
import Senzor from '@senzops/apm-node';
|
|
270
|
+
|
|
271
|
+
Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });
|
|
272
|
+
|
|
273
|
+
export const GET = Senzor.wrapNextRoute(async (request: Request) => {
|
|
274
|
+
return Response.json({ ok: true });
|
|
275
|
+
});
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**Pages Router:**
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
import Senzor from '@senzops/apm-node';
|
|
282
|
+
|
|
283
|
+
Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });
|
|
284
|
+
|
|
285
|
+
export default Senzor.wrapNextPages(async (req, res) => {
|
|
286
|
+
res.status(200).json({ ok: true });
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### 3.8 H3 / Nuxt / Nitro
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
import Senzor from '@senzops/apm-node';
|
|
294
|
+
|
|
295
|
+
Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });
|
|
296
|
+
|
|
297
|
+
export default Senzor.wrapH3(defineEventHandler(async (event) => {
|
|
298
|
+
return { ok: true };
|
|
299
|
+
}));
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Nitro Plugin (for Cloudflare Workers preset):**
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
// server/plugins/senzor.ts
|
|
306
|
+
import { Senzor } from '@senzops/apm-node';
|
|
307
|
+
|
|
308
|
+
export default defineNitroPlugin((nitroApp) => {
|
|
309
|
+
Senzor.init({ apiKey: '<YOUR_APM_KEY>' });
|
|
310
|
+
Senzor.nitroPlugin(nitroApp);
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### 3.9 Vanilla Node HTTP Server
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
import http from 'http';
|
|
318
|
+
import Senzor from '@senzops/apm-node';
|
|
319
|
+
|
|
320
|
+
Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });
|
|
321
|
+
|
|
322
|
+
const server = http.createServer((req, res) => {
|
|
323
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
324
|
+
res.end(JSON.stringify({ ok: true }));
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
server.listen(3000);
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
With preload mode, no wrapper is needed — `http.createServer` is automatically instrumented.
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## 4. AWS Lambda Integration
|
|
335
|
+
|
|
336
|
+
Three deployment methods, from zero-code to code-level:
|
|
337
|
+
|
|
338
|
+
| Method | Code Changes | Setup | Coverage |
|
|
339
|
+
|--------|-------------|-------|----------|
|
|
340
|
+
| **Extension Layer** (recommended) | None | Add layer + set env vars | Full: auto-handler wrapping + all auto-instrumentation |
|
|
341
|
+
| **Handler Wrapper** | Modify handler file | `npm install` + code change | Full |
|
|
342
|
+
| **Preload Layer** | None | Add layer + `NODE_OPTIONS` | Partial: outgoing calls/DB only, no handler wrapping |
|
|
343
|
+
|
|
344
|
+
### 4.1 Lambda Extension Layer (Zero Code Changes, Recommended)
|
|
345
|
+
|
|
346
|
+
The Extension Layer approach works identically to New Relic and Datadog Lambda Layers. You package `@senzops/apm-node` as a Lambda Layer, point the function's handler to Senzor's auto-wrapper, and set `SENZOR_LAMBDA_HANDLER` to your original handler. No code changes.
|
|
347
|
+
|
|
348
|
+
**How it works:**
|
|
349
|
+
|
|
350
|
+
1. Lambda invokes `@senzops/apm-node/dist/lambda-handler.handler`
|
|
351
|
+
2. The auto-wrapper reads `SENZOR_LAMBDA_HANDLER` (e.g., `index.handler`)
|
|
352
|
+
3. It dynamically loads your original handler module from `LAMBDA_TASK_ROOT`
|
|
353
|
+
4. It wraps your handler with `wrapLambda()` for full APM coverage
|
|
354
|
+
5. It re-exports the wrapped function for Lambda to invoke
|
|
355
|
+
|
|
356
|
+
**Step 1: Build the Lambda Layer**
|
|
357
|
+
|
|
358
|
+
```sh
|
|
359
|
+
mkdir -p senzor-layer/nodejs
|
|
360
|
+
cd senzor-layer/nodejs
|
|
361
|
+
npm init -y
|
|
362
|
+
npm install @senzops/apm-node
|
|
363
|
+
cd ..
|
|
364
|
+
zip -r senzor-apm-layer.zip nodejs/
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**Step 2: Publish the Layer**
|
|
368
|
+
|
|
369
|
+
```sh
|
|
370
|
+
aws lambda publish-layer-version \
|
|
371
|
+
--layer-name senzor-apm-node \
|
|
372
|
+
--zip-file fileb://senzor-apm-layer.zip \
|
|
373
|
+
--compatible-runtimes nodejs18.x nodejs20.x nodejs22.x
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**Step 3: Configure Your Function**
|
|
377
|
+
|
|
378
|
+
```sh
|
|
379
|
+
aws lambda update-function-configuration \
|
|
380
|
+
--function-name my-function \
|
|
381
|
+
--layers <LAYER_ARN> \
|
|
382
|
+
--handler @senzops/apm-node/dist/lambda-handler.handler \
|
|
383
|
+
--environment Variables="{ \
|
|
384
|
+
SENZOR_API_KEY=sz_apm_xxx, \
|
|
385
|
+
SENZOR_LAMBDA_HANDLER=index.handler, \
|
|
386
|
+
NODE_OPTIONS=--require @senzops/apm-node/register \
|
|
387
|
+
}"
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
| Environment Variable | Required | Description |
|
|
391
|
+
|---------------------|----------|-------------|
|
|
392
|
+
| `SENZOR_API_KEY` | Yes | Your Senzor APM API key |
|
|
393
|
+
| `SENZOR_LAMBDA_HANDLER` | Yes | Original handler path (e.g., `index.handler`, `src/app.myHandler`) |
|
|
394
|
+
| `NODE_OPTIONS` | Recommended | `--require @senzops/apm-node/register` for full preload coverage |
|
|
395
|
+
|
|
396
|
+
The handler path supports nested exports: `SENZOR_LAMBDA_HANDLER=src/handlers.api.get` resolves to `require('src/handlers').api.get`.
|
|
397
|
+
|
|
398
|
+
### 4.2 Extension Layer with AWS CDK
|
|
399
|
+
|
|
400
|
+
```ts
|
|
401
|
+
import * as lambda from 'aws-cdk-lib/aws-lambda';
|
|
402
|
+
import * as path from 'path';
|
|
403
|
+
|
|
404
|
+
// Create the Senzor APM Layer
|
|
405
|
+
const senzorLayer = new lambda.LayerVersion(this, 'SenzorApmLayer', {
|
|
406
|
+
code: lambda.Code.fromAsset(path.join(__dirname, 'senzor-layer')),
|
|
407
|
+
compatibleRuntimes: [
|
|
408
|
+
lambda.Runtime.NODEJS_18_X,
|
|
409
|
+
lambda.Runtime.NODEJS_20_X,
|
|
410
|
+
lambda.Runtime.NODEJS_22_X,
|
|
411
|
+
],
|
|
412
|
+
description: 'Senzor APM Node.js Lambda Extension Layer',
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// Attach to your Lambda function
|
|
416
|
+
const fn = new lambda.Function(this, 'MyFunction', {
|
|
417
|
+
runtime: lambda.Runtime.NODEJS_20_X,
|
|
418
|
+
// Point handler to Senzor's auto-wrapper
|
|
419
|
+
handler: '@senzops/apm-node/dist/lambda-handler.handler',
|
|
420
|
+
code: lambda.Code.fromAsset('lambda'),
|
|
421
|
+
layers: [senzorLayer],
|
|
422
|
+
environment: {
|
|
423
|
+
SENZOR_API_KEY: senzorApiKey.stringValue,
|
|
424
|
+
// Your original handler path
|
|
425
|
+
SENZOR_LAMBDA_HANDLER: 'index.handler',
|
|
426
|
+
NODE_OPTIONS: '--require @senzops/apm-node/register',
|
|
427
|
+
},
|
|
428
|
+
});
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
Build the layer directory first:
|
|
432
|
+
|
|
433
|
+
```sh
|
|
434
|
+
mkdir -p senzor-layer/nodejs && cd senzor-layer/nodejs
|
|
435
|
+
npm init -y && npm install @senzops/apm-node
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### 4.3 Extension Layer with AWS SAM
|
|
439
|
+
|
|
440
|
+
```yaml
|
|
441
|
+
# template.yaml
|
|
442
|
+
AWSTemplateFormatVersion: '2010-09-09'
|
|
443
|
+
Transform: AWS::Serverless-2016-10-31
|
|
444
|
+
|
|
445
|
+
Globals:
|
|
446
|
+
Function:
|
|
447
|
+
Layers:
|
|
448
|
+
- !Ref SenzorApmLayer
|
|
449
|
+
Environment:
|
|
450
|
+
Variables:
|
|
451
|
+
SENZOR_API_KEY: !Ref SenzorApiKey
|
|
452
|
+
NODE_OPTIONS: '--require @senzops/apm-node/register'
|
|
453
|
+
|
|
454
|
+
Resources:
|
|
455
|
+
SenzorApmLayer:
|
|
456
|
+
Type: AWS::Serverless::LayerVersion
|
|
457
|
+
Properties:
|
|
458
|
+
LayerName: senzor-apm-node
|
|
459
|
+
ContentUri: senzor-layer/
|
|
460
|
+
CompatibleRuntimes:
|
|
461
|
+
- nodejs18.x
|
|
462
|
+
- nodejs20.x
|
|
463
|
+
- nodejs22.x
|
|
464
|
+
|
|
465
|
+
MyFunction:
|
|
466
|
+
Type: AWS::Serverless::Function
|
|
467
|
+
Properties:
|
|
468
|
+
Handler: '@senzops/apm-node/dist/lambda-handler.handler'
|
|
469
|
+
Runtime: nodejs20.x
|
|
470
|
+
CodeUri: src/
|
|
471
|
+
Environment:
|
|
472
|
+
Variables:
|
|
473
|
+
SENZOR_LAMBDA_HANDLER: index.handler
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### 4.4 Extension Layer with Serverless Framework
|
|
477
|
+
|
|
478
|
+
```yaml
|
|
479
|
+
# serverless.yml
|
|
480
|
+
service: my-service
|
|
481
|
+
|
|
482
|
+
provider:
|
|
483
|
+
name: aws
|
|
484
|
+
runtime: nodejs20.x
|
|
485
|
+
environment:
|
|
486
|
+
SENZOR_API_KEY: ${ssm:/senzor/api-key}
|
|
487
|
+
NODE_OPTIONS: '--require @senzops/apm-node/register'
|
|
488
|
+
|
|
489
|
+
layers:
|
|
490
|
+
senzorApm:
|
|
491
|
+
path: senzor-layer
|
|
492
|
+
compatibleRuntimes:
|
|
493
|
+
- nodejs18.x
|
|
494
|
+
- nodejs20.x
|
|
495
|
+
- nodejs22.x
|
|
496
|
+
|
|
497
|
+
functions:
|
|
498
|
+
api:
|
|
499
|
+
handler: '@senzops/apm-node/dist/lambda-handler.handler'
|
|
500
|
+
layers:
|
|
501
|
+
- !Ref SenzorApmLambdaLayer
|
|
502
|
+
environment:
|
|
503
|
+
SENZOR_LAMBDA_HANDLER: src/handlers/api.handler
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### 4.5 Extension Layer via AWS Console
|
|
507
|
+
|
|
508
|
+
1. **Create the Layer zip** locally:
|
|
509
|
+
```sh
|
|
510
|
+
mkdir -p senzor-layer/nodejs && cd senzor-layer/nodejs
|
|
511
|
+
npm init -y && npm install @senzops/apm-node
|
|
512
|
+
cd .. && zip -r senzor-apm-layer.zip nodejs/
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
2. **Upload the Layer**: Go to Lambda > Layers > Create layer. Upload `senzor-apm-layer.zip`. Set compatible runtimes to `nodejs18.x`, `nodejs20.x`, `nodejs22.x`.
|
|
516
|
+
|
|
517
|
+
3. **Attach to your function**: Go to your Lambda function > Layers > Add a layer. Choose "Custom layers" and select `senzor-apm-node`.
|
|
518
|
+
|
|
519
|
+
4. **Update function configuration**:
|
|
520
|
+
- **Handler**: `@senzops/apm-node/dist/lambda-handler.handler`
|
|
521
|
+
- **Environment variables**:
|
|
522
|
+
- `SENZOR_API_KEY` = your API key
|
|
523
|
+
- `SENZOR_LAMBDA_HANDLER` = your original handler (e.g., `index.handler`)
|
|
524
|
+
- `NODE_OPTIONS` = `--require @senzops/apm-node/register`
|
|
525
|
+
|
|
526
|
+
### 4.6 Extension Layer with Terraform
|
|
527
|
+
|
|
528
|
+
```hcl
|
|
529
|
+
resource "aws_lambda_layer_version" "senzor_apm" {
|
|
530
|
+
filename = "senzor-apm-layer.zip"
|
|
531
|
+
layer_name = "senzor-apm-node"
|
|
532
|
+
compatible_runtimes = ["nodejs18.x", "nodejs20.x", "nodejs22.x"]
|
|
533
|
+
description = "Senzor APM Node.js Lambda Extension Layer"
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
resource "aws_lambda_function" "api" {
|
|
537
|
+
function_name = "my-function"
|
|
538
|
+
runtime = "nodejs20.x"
|
|
539
|
+
handler = "@senzops/apm-node/dist/lambda-handler.handler"
|
|
540
|
+
filename = "function.zip"
|
|
541
|
+
role = aws_iam_role.lambda.arn
|
|
542
|
+
|
|
543
|
+
layers = [aws_lambda_layer_version.senzor_apm.arn]
|
|
544
|
+
|
|
545
|
+
environment {
|
|
546
|
+
variables = {
|
|
547
|
+
SENZOR_API_KEY = var.senzor_api_key
|
|
548
|
+
SENZOR_LAMBDA_HANDLER = "index.handler"
|
|
549
|
+
NODE_OPTIONS = "--require @senzops/apm-node/register"
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### 4.7 Code-Level Handler Wrapper
|
|
556
|
+
|
|
557
|
+
When you prefer code-level control or cannot use Lambda Layers:
|
|
558
|
+
|
|
559
|
+
```ts
|
|
560
|
+
import Senzor from '@senzops/apm-node';
|
|
561
|
+
|
|
562
|
+
Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });
|
|
563
|
+
|
|
564
|
+
export const handler = Senzor.wrapLambda(async (event, context) => {
|
|
565
|
+
// Your Lambda logic
|
|
566
|
+
return { statusCode: 200, body: JSON.stringify({ ok: true }) };
|
|
567
|
+
});
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### 4.8 What Gets Captured
|
|
571
|
+
|
|
572
|
+
Both the Extension Layer and code-level wrapper capture the same telemetry:
|
|
573
|
+
|
|
574
|
+
**Cold Start Detection:**
|
|
575
|
+
The first invocation in each container is tagged with `faas.coldstart: true`. Subsequent warm invocations are tagged `false`.
|
|
576
|
+
|
|
577
|
+
**Trigger-Type Detection:**
|
|
578
|
+
The wrapper inspects the event shape and automatically detects:
|
|
579
|
+
|
|
580
|
+
| Trigger | Detection |
|
|
581
|
+
|---------|-----------|
|
|
582
|
+
| API Gateway v1 (REST API) | `event.httpMethod` or `event.requestContext.httpMethod` |
|
|
583
|
+
| API Gateway v2 (HTTP API) | `event.requestContext.http.method` |
|
|
584
|
+
| Application Load Balancer | `event.requestContext.elb` |
|
|
585
|
+
| SQS | `event.Records[0].eventSource === 'aws:sqs'` |
|
|
586
|
+
| SNS | `event.Records[0].EventSource === 'aws:sns'` |
|
|
587
|
+
| DynamoDB Streams | `event.Records[0].eventSource === 'aws:dynamodb'` |
|
|
588
|
+
| S3 | `event.Records[0].eventSource === 'aws:s3'` |
|
|
589
|
+
| EventBridge | `event.source` + `event.detail-type` + `event.detail` |
|
|
590
|
+
| Scheduled (CloudWatch Events) | `event.source === 'aws.events'` |
|
|
591
|
+
|
|
592
|
+
For HTTP triggers (API Gateway, ALB), the wrapper extracts method, path, headers, client IP, and status code. For messaging triggers, it extracts queue/topic names, batch sizes, and table names.
|
|
593
|
+
|
|
594
|
+
**Lambda Context Extraction:**
|
|
595
|
+
|
|
596
|
+
| Attribute | Source |
|
|
597
|
+
|-----------|--------|
|
|
598
|
+
| `faas.name` | `context.functionName` |
|
|
599
|
+
| `faas.version` | `context.functionVersion` |
|
|
600
|
+
| `faas.execution` | `context.awsRequestId` |
|
|
601
|
+
| `faas.max_memory` | `context.memoryLimitInMB` |
|
|
602
|
+
| `faas.coldstart` | Module-level boolean flag |
|
|
603
|
+
| `faas.trigger` | `http`, `pubsub`, `datasource`, `timer`, `other` |
|
|
604
|
+
| `cloud.provider` | `aws` |
|
|
605
|
+
| `cloud.platform` | `aws_lambda` |
|
|
606
|
+
| `cloud.region` | `AWS_REGION` env var |
|
|
607
|
+
| `cloud.account.id` | Parsed from invoked ARN |
|
|
608
|
+
| `cloud.resource_id` | `context.invokedFunctionArn` |
|
|
609
|
+
| `aws.log.group.names` | `context.logGroupName` |
|
|
610
|
+
|
|
611
|
+
**Forced Flush:**
|
|
612
|
+
After every invocation, the wrapper calls `await Senzor.flush()` before returning to the Lambda runtime. Lambda freezes the process immediately after the handler returns.
|
|
613
|
+
|
|
614
|
+
**Lambda Extensions API:**
|
|
615
|
+
The wrapper registers as an internal Lambda extension to receive `SHUTDOWN` lifecycle events. This provides a safety-net flush when the execution environment is being terminated.
|
|
616
|
+
|
|
617
|
+
### 4.9 Lambda Auto-Detection
|
|
618
|
+
|
|
619
|
+
When running inside Lambda (detected via `AWS_LAMBDA_FUNCTION_NAME` env var), the SDK automatically optimizes settings:
|
|
620
|
+
|
|
621
|
+
| Setting | Default | Lambda Override |
|
|
622
|
+
|---------|---------|-----------------|
|
|
623
|
+
| `runtimeMetrics` | `true` | `false` (meaningless per-invocation) |
|
|
624
|
+
| `batchSize` | `100` | `10` (short-lived invocations) |
|
|
625
|
+
| `flushInterval` | `10000` | `0` (flush on demand only) |
|
|
626
|
+
|
|
627
|
+
---
|
|
628
|
+
|
|
629
|
+
## 5. Cloudflare Workers Integration
|
|
630
|
+
|
|
631
|
+
### 5.1 Direct Worker
|
|
632
|
+
|
|
633
|
+
```ts
|
|
634
|
+
import Senzor from '@senzops/apm-node';
|
|
635
|
+
|
|
636
|
+
Senzor.init({ apiKey: '<YOUR_APM_KEY>' });
|
|
637
|
+
|
|
638
|
+
export default {
|
|
639
|
+
fetch: Senzor.worker(async (request, env, ctx) => {
|
|
640
|
+
const url = new URL(request.url);
|
|
641
|
+
return new Response(JSON.stringify({ path: url.pathname }), {
|
|
642
|
+
headers: { 'content-type': 'application/json' },
|
|
643
|
+
});
|
|
644
|
+
}),
|
|
645
|
+
};
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
The worker wrapper automatically:
|
|
649
|
+
- Starts a trace with method, path, headers, and client IP
|
|
650
|
+
- Captures response status code
|
|
651
|
+
- Uses `ctx.waitUntil()` for non-blocking flush when available
|
|
652
|
+
- Falls back to `await flush()` otherwise
|
|
653
|
+
|
|
654
|
+
### 5.2 Nitro + Cloudflare Workers
|
|
655
|
+
|
|
656
|
+
```ts
|
|
657
|
+
// server/plugins/senzor.ts
|
|
658
|
+
import { Senzor } from '@senzops/apm-node';
|
|
659
|
+
|
|
660
|
+
export default defineNitroPlugin((nitroApp) => {
|
|
661
|
+
Senzor.init({ apiKey: '<YOUR_APM_KEY>' });
|
|
662
|
+
Senzor.nitroPlugin(nitroApp);
|
|
663
|
+
});
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
---
|
|
667
|
+
|
|
668
|
+
## 6. Auto-Instrumentation Reference
|
|
669
|
+
|
|
670
|
+
Complete table of all 43+ auto-instrumentations. All activate automatically when the library is present in `node_modules` and imported by your application.
|
|
671
|
+
|
|
672
|
+
### Web Frameworks & HTTP (10)
|
|
673
|
+
|
|
674
|
+
| Instrumentation Key | npm Package(s) | Captured Signals |
|
|
675
|
+
|---------------------|----------------|------------------|
|
|
676
|
+
| `http` | Node built-in `http`/`https` | Inbound requests, outbound calls, status codes, timing |
|
|
677
|
+
| `fetch` | Global `fetch` | Outbound HTTP, W3C Traceparent propagation |
|
|
678
|
+
| `undici` | `undici` | Outbound HTTP via Node's native client |
|
|
679
|
+
| `express` | `express` | Route matching, middleware chain, error capture |
|
|
680
|
+
| `fastify` | `fastify` | Route matching, hooks, lifecycle spans |
|
|
681
|
+
| `koa` | `koa` | Middleware stack, route detection |
|
|
682
|
+
| `nestjs` | `@nestjs/core` | Controllers, Guards, Interceptors, Pipes |
|
|
683
|
+
| `hapi` | `@hapi/hapi` | Route handling, request lifecycle |
|
|
684
|
+
| `restify` | `restify` | Route matching, handler chain |
|
|
685
|
+
| `connect` | `connect` | Middleware stack |
|
|
686
|
+
|
|
687
|
+
### Databases & Cache (9)
|
|
688
|
+
|
|
689
|
+
| Instrumentation Key | npm Package(s) | Captured Signals |
|
|
690
|
+
|---------------------|----------------|------------------|
|
|
691
|
+
| `pg` | `pg` | Queries, prepared statements, row counts, sanitized SQL |
|
|
692
|
+
| `mongo` | `mongodb` | find, insert, update, delete, aggregate, bulkWrite, cursor |
|
|
693
|
+
| `mongoose` | `mongoose` | Model operations, model/collection names |
|
|
694
|
+
| `mysql` | `mysql`, `mysql2` | Queries, sanitized SQL, row counts |
|
|
695
|
+
| `redis` | `redis`, `ioredis` | All commands (GET, SET, HGETALL, DEL, etc.) |
|
|
696
|
+
| `knex` | `knex` | Query builder, raw queries, transactions, streaming |
|
|
697
|
+
| `tedious` | `tedious` | T-SQL queries, stored procedures, row counts, batch SQL |
|
|
698
|
+
| `cassandra` | `cassandra-driver` | CQL queries, batch operations, prepared statements, consistency |
|
|
699
|
+
| `memcached` | `memcached` | get, set, gets, cas, append, prepend, incr, decr, del, flush |
|
|
700
|
+
|
|
701
|
+
### Messaging & Queues (5)
|
|
702
|
+
|
|
703
|
+
| Instrumentation Key | npm Package(s) | Captured Signals |
|
|
704
|
+
|---------------------|----------------|------------------|
|
|
705
|
+
| `kafka` | `kafkajs` | Producer send, consumer processing, topic, partition, offset |
|
|
706
|
+
| `amqplib` | `amqplib` | Publish, consume, ack/nack, queue, exchange, routing key |
|
|
707
|
+
| `socketio` | `socket.io` | Event emit/receive, namespace, room, acknowledgements |
|
|
708
|
+
| `bullmq` | `bullmq` | Worker jobs as task runs, queue delay, retries, dead-letter |
|
|
709
|
+
| `cron` | `node-cron` | Scheduled jobs as task runs, schedule expression |
|
|
710
|
+
|
|
711
|
+
### AI / LLM SDKs (7)
|
|
712
|
+
|
|
713
|
+
| Instrumentation Key | npm Package(s) | Captured Signals |
|
|
714
|
+
|---------------------|----------------|------------------|
|
|
715
|
+
| `openai` | `openai` | Chat completions, embeddings, images, audio, assistants, token usage |
|
|
716
|
+
| `anthropic` | `@anthropic-ai/sdk` | Messages, completions, model, input/output tokens, stop reason |
|
|
717
|
+
| `google-genai` | `@google/generative-ai`, `@google-cloud/vertexai` | generateContent, chat, embeddings, countTokens, token usage |
|
|
718
|
+
| `azure-openai` | `@azure/openai` | Chat, completions, embeddings, images, audio (v1.x) |
|
|
719
|
+
| `cohere` | `cohere-ai` | Chat, generate, embed, rerank, classify, summarize, tokenize |
|
|
720
|
+
| `mistral` | `@mistralai/mistralai` | Chat, FIM, embeddings, model, token usage |
|
|
721
|
+
| `aws-sdk` (Bedrock) | `@aws-sdk/client-bedrock-runtime` | InvokeModel, Converse, token usage, model ID, finish reason |
|
|
722
|
+
|
|
723
|
+
### Cloud & Infrastructure (3)
|
|
724
|
+
|
|
725
|
+
| Instrumentation Key | npm Package(s) | Captured Signals |
|
|
726
|
+
|---------------------|----------------|------------------|
|
|
727
|
+
| `aws-sdk` | `@aws-sdk/*`, `@smithy/smithy-client` | All AWS service calls: S3, DynamoDB, SQS, SNS, Lambda, SES, etc. |
|
|
728
|
+
| `firebase` | `firebase-admin`, `@google-cloud/firestore` | Firestore CRUD/queries, Auth (16 methods), FCM Messaging (9 methods) |
|
|
729
|
+
| `generic-pool` | `generic-pool` | Pool acquire/release, pool size, pending count, queue size |
|
|
730
|
+
|
|
731
|
+
### Logging (3)
|
|
732
|
+
|
|
733
|
+
| Instrumentation Key | npm Package(s) | Captured Signals |
|
|
734
|
+
|---------------------|----------------|------------------|
|
|
735
|
+
| `pino` | `pino` | Trace/span ID injection into log records |
|
|
736
|
+
| `winston` | `winston` | Trace/span ID injection into transport output |
|
|
737
|
+
| `bunyan` | `bunyan` | Trace/span ID injection into serialized records |
|
|
738
|
+
|
|
739
|
+
### RPC & Network (4)
|
|
740
|
+
|
|
741
|
+
| Instrumentation Key | npm Package(s) | Captured Signals |
|
|
742
|
+
|---------------------|----------------|------------------|
|
|
743
|
+
| `grpc` | `@grpc/grpc-js` | Unary/streaming calls, service/method, status codes, metadata |
|
|
744
|
+
| `graphql` | `graphql` | Resolver execution, operation name/type, field paths, errors |
|
|
745
|
+
| `dns` | Node built-in `dns` | Lookups, resolve, hostname, record types, timing |
|
|
746
|
+
| `net` | Node built-in `net` | TCP socket connect, data transfer, connection duration |
|
|
747
|
+
|
|
748
|
+
### Utilities (3)
|
|
749
|
+
|
|
750
|
+
| Instrumentation Key | npm Package(s) | Captured Signals |
|
|
751
|
+
|---------------------|----------------|------------------|
|
|
752
|
+
| `dataloader` | `dataloader` | Batch load calls, batch size, individual key loads |
|
|
753
|
+
| `lru-memoizer` | `lru-memoizer` | Memoized function calls, cache hit/miss |
|
|
754
|
+
| `fs` | Node built-in `fs` | readFile, writeFile, stat, readdir, mkdir, unlink, rename, etc. |
|
|
755
|
+
|
|
756
|
+
---
|
|
757
|
+
|
|
758
|
+
## 7. AI / LLM SDK Instrumentation
|
|
759
|
+
|
|
760
|
+
All AI SDK instrumentations follow [OpenTelemetry GenAI semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/).
|
|
761
|
+
|
|
762
|
+
### Common Attributes Captured
|
|
763
|
+
|
|
764
|
+
| Attribute | Description |
|
|
765
|
+
|-----------|-------------|
|
|
766
|
+
| `gen_ai.system` | `openai`, `anthropic`, `google_ai`, `vertex_ai`, `azure_openai`, `cohere`, `mistral`, `aws_bedrock` |
|
|
767
|
+
| `gen_ai.request.model` | Model name from request (e.g., `gpt-4o`, `claude-sonnet-4-20250514`, `gemini-1.5-pro`) |
|
|
768
|
+
| `gen_ai.response.model` | Actual model from response (may differ for aliases) |
|
|
769
|
+
| `gen_ai.operation.name` | `chat`, `embeddings`, `images`, `audio`, `fim`, `rerank`, `classify` |
|
|
770
|
+
| `gen_ai.usage.input_tokens` | Prompt/input token count |
|
|
771
|
+
| `gen_ai.usage.output_tokens` | Completion/output token count |
|
|
772
|
+
| `gen_ai.usage.total_tokens` | Total tokens (when available) |
|
|
773
|
+
| `gen_ai.response.finish_reason` | `stop`, `length`, `tool_calls`, `end_turn`, etc. |
|
|
774
|
+
|
|
775
|
+
### OpenAI
|
|
776
|
+
|
|
777
|
+
```ts
|
|
778
|
+
import OpenAI from 'openai';
|
|
779
|
+
|
|
780
|
+
const openai = new OpenAI();
|
|
781
|
+
const response = await openai.chat.completions.create({
|
|
782
|
+
model: 'gpt-4o',
|
|
783
|
+
messages: [{ role: 'user', content: 'Hello' }],
|
|
784
|
+
});
|
|
785
|
+
// Span: "OpenAI chat gpt-4o" with token usage
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
### Anthropic
|
|
789
|
+
|
|
790
|
+
```ts
|
|
791
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
792
|
+
|
|
793
|
+
const client = new Anthropic();
|
|
794
|
+
const message = await client.messages.create({
|
|
795
|
+
model: 'claude-sonnet-4-20250514',
|
|
796
|
+
max_tokens: 1024,
|
|
797
|
+
messages: [{ role: 'user', content: 'Hello' }],
|
|
798
|
+
});
|
|
799
|
+
// Span: "Anthropic messages claude-sonnet-4-20250514" with input/output tokens
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
### Google Gemini
|
|
803
|
+
|
|
804
|
+
```ts
|
|
805
|
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
806
|
+
|
|
807
|
+
const genai = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY!);
|
|
808
|
+
const model = genai.getGenerativeModel({ model: 'gemini-1.5-pro' });
|
|
809
|
+
const result = await model.generateContent('Hello');
|
|
810
|
+
// Span: "Gemini generateContent gemini-1.5-pro" with token usage
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
### Cohere
|
|
814
|
+
|
|
815
|
+
```ts
|
|
816
|
+
import { CohereClient } from 'cohere-ai';
|
|
817
|
+
|
|
818
|
+
const cohere = new CohereClient({ token: process.env.COHERE_API_KEY! });
|
|
819
|
+
const response = await cohere.chat({ model: 'command-r-plus', message: 'Hello' });
|
|
820
|
+
// Span: "Cohere chat command-r-plus" with billed units
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
### Mistral
|
|
824
|
+
|
|
825
|
+
```ts
|
|
826
|
+
import { Mistral } from '@mistralai/mistralai';
|
|
827
|
+
|
|
828
|
+
const mistral = new Mistral({ apiKey: process.env.MISTRAL_API_KEY! });
|
|
829
|
+
const response = await mistral.chat.complete({
|
|
830
|
+
model: 'mistral-large-latest',
|
|
831
|
+
messages: [{ role: 'user', content: 'Hello' }],
|
|
832
|
+
});
|
|
833
|
+
// Span: "Mistral chat mistral-large-latest" with token usage
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
### AWS Bedrock
|
|
837
|
+
|
|
838
|
+
AWS Bedrock calls made through `@aws-sdk/client-bedrock-runtime` are automatically captured with GenAI attributes via the AWS SDK instrumentation. Both InvokeModel and Converse APIs are supported.
|
|
839
|
+
|
|
840
|
+
```ts
|
|
841
|
+
import { BedrockRuntimeClient, InvokeModelCommand } from '@aws-sdk/client-bedrock-runtime';
|
|
842
|
+
|
|
843
|
+
const client = new BedrockRuntimeClient({ region: 'us-east-1' });
|
|
844
|
+
const response = await client.send(new InvokeModelCommand({
|
|
845
|
+
modelId: 'anthropic.claude-3-sonnet-20240229-v1:0',
|
|
846
|
+
body: JSON.stringify({ messages: [{ role: 'user', content: 'Hello' }] }),
|
|
847
|
+
}));
|
|
848
|
+
// Span: "AWS BedrockRuntime InvokeModel" with gen_ai.* attributes and token usage
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
### Azure OpenAI (v1.x)
|
|
852
|
+
|
|
853
|
+
```ts
|
|
854
|
+
import { OpenAIClient, AzureKeyCredential } from '@azure/openai';
|
|
855
|
+
|
|
856
|
+
const client = new OpenAIClient(endpoint, new AzureKeyCredential(key));
|
|
857
|
+
const response = await client.getChatCompletions('gpt-4-deployment', messages);
|
|
858
|
+
// Span: "Azure OpenAI chat gpt-4-deployment" with token usage
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
Note: Azure OpenAI SDK v2+ wraps the standard `openai` npm package internally, which is already covered by the OpenAI instrumentation.
|
|
862
|
+
|
|
863
|
+
---
|
|
864
|
+
|
|
865
|
+
## 8. Database & Cache Instrumentation
|
|
866
|
+
|
|
867
|
+
### PostgreSQL (`pg`)
|
|
868
|
+
|
|
869
|
+
```ts
|
|
870
|
+
const result = await pool.query('SELECT * FROM users WHERE id = $1', [userId]);
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
Span: `Postgres SELECT` with `db.system.name: 'postgresql'`, `db.operation.name: 'SELECT'`, sanitized SQL, row count.
|
|
874
|
+
|
|
875
|
+
### MongoDB (`mongodb`)
|
|
876
|
+
|
|
877
|
+
```ts
|
|
878
|
+
const user = await db.collection('users').findOne({ _id: userId });
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
Span: `Mongo users findOne` with `db.system.name: 'mongodb'`, `db.collection.name: 'users'`, `db.operation.name: 'findOne'`.
|
|
882
|
+
|
|
883
|
+
### Mongoose
|
|
884
|
+
|
|
885
|
+
```ts
|
|
886
|
+
const user = await User.findById(userId).exec();
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
Span includes model name, collection name, and operation.
|
|
890
|
+
|
|
891
|
+
### MySQL / MySQL2
|
|
892
|
+
|
|
893
|
+
```ts
|
|
894
|
+
const [rows] = await connection.execute('SELECT * FROM users WHERE id = ?', [userId]);
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
Span: `MySQL SELECT` with sanitized SQL and row count.
|
|
898
|
+
|
|
899
|
+
### Redis / ioredis
|
|
900
|
+
|
|
901
|
+
```ts
|
|
902
|
+
await redis.get(`user:${userId}`);
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
Span: `Redis GET` with command name and key.
|
|
906
|
+
|
|
907
|
+
### Knex
|
|
908
|
+
|
|
909
|
+
```ts
|
|
910
|
+
const users = await knex('users').where({ active: true }).select('*');
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
Span: `Knex SELECT users` with query builder context, sanitized SQL, and binding count.
|
|
914
|
+
|
|
915
|
+
### SQL Server (Tedious)
|
|
916
|
+
|
|
917
|
+
```ts
|
|
918
|
+
const request = new Request('SELECT * FROM Users WHERE Id = @id', callback);
|
|
919
|
+
request.addParameter('id', TYPES.Int, userId);
|
|
920
|
+
connection.execSql(request);
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
Span: `SQLServer SELECT` with sanitized T-SQL, row count, and stored procedure name.
|
|
924
|
+
|
|
925
|
+
### Cassandra
|
|
926
|
+
|
|
927
|
+
```ts
|
|
928
|
+
const result = await client.execute('SELECT * FROM users WHERE id = ?', [userId], { prepare: true });
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
Span: `Cassandra SELECT` with `db.system.name: 'cassandra'`, keyspace, consistency level, sanitized CQL.
|
|
932
|
+
|
|
933
|
+
### Memcached
|
|
934
|
+
|
|
935
|
+
```ts
|
|
936
|
+
memcached.get('session:abc', (err, data) => { });
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
Span: `Memcached GET` with command name and key.
|
|
940
|
+
|
|
941
|
+
---
|
|
942
|
+
|
|
943
|
+
## 9. Messaging & Queue Instrumentation
|
|
944
|
+
|
|
945
|
+
### Kafka (kafkajs)
|
|
946
|
+
|
|
947
|
+
```ts
|
|
948
|
+
// Producer
|
|
949
|
+
await producer.send({ topic: 'orders', messages: [{ value: JSON.stringify(order) }] });
|
|
950
|
+
// Span: "Kafka SEND orders" with topic, partition, message count
|
|
951
|
+
|
|
952
|
+
// Consumer
|
|
953
|
+
await consumer.run({
|
|
954
|
+
eachMessage: async ({ topic, message }) => { /* ... */ },
|
|
955
|
+
});
|
|
956
|
+
// Span: "Kafka RECEIVE orders" with topic, partition, offset
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
### RabbitMQ (amqplib)
|
|
960
|
+
|
|
961
|
+
```ts
|
|
962
|
+
// Publish
|
|
963
|
+
channel.publish('exchange', 'routing.key', Buffer.from(JSON.stringify(data)));
|
|
964
|
+
// Span: "RabbitMQ PUBLISH exchange" with exchange, routing key
|
|
965
|
+
|
|
966
|
+
// Consume
|
|
967
|
+
channel.consume('queue-name', (msg) => { channel.ack(msg); });
|
|
968
|
+
// Span: "RabbitMQ RECEIVE queue-name" with queue name, delivery tag
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
### Socket.IO
|
|
972
|
+
|
|
973
|
+
```ts
|
|
974
|
+
io.on('connection', (socket) => {
|
|
975
|
+
socket.on('message', (data) => { /* ... */ });
|
|
976
|
+
socket.emit('response', { ok: true });
|
|
977
|
+
});
|
|
978
|
+
// Spans for emit and receive with event names, namespace, room
|
|
979
|
+
```
|
|
980
|
+
|
|
981
|
+
---
|
|
982
|
+
|
|
983
|
+
## 10. Cloud Provider Instrumentation
|
|
984
|
+
|
|
985
|
+
### AWS SDK v3
|
|
986
|
+
|
|
987
|
+
All AWS services are instrumented through the single `Client.send()` dispatch point:
|
|
988
|
+
|
|
989
|
+
```ts
|
|
990
|
+
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
|
|
991
|
+
|
|
992
|
+
const s3 = new S3Client({ region: 'us-east-1' });
|
|
993
|
+
await s3.send(new PutObjectCommand({ Bucket: 'my-bucket', Key: 'file.txt', Body: data }));
|
|
994
|
+
// Span: "AWS S3 PutObject" with rpc.service, rpc.method, aws.request_id, aws.region
|
|
995
|
+
```
|
|
996
|
+
|
|
997
|
+
Covers: S3, DynamoDB, SQS, SNS, Lambda, SES, CloudWatch, Kinesis, EventBridge, Secrets Manager, SSM, STS, IAM, EC2, ECS, EKS, RDS, ElastiCache, Redshift, Cognito, Route53, CloudFront, API Gateway, Step Functions, CodeBuild, KMS, Bedrock Runtime, and any other AWS service using SDK v3.
|
|
998
|
+
|
|
999
|
+
### Firebase Admin
|
|
1000
|
+
|
|
1001
|
+
**Firestore:**
|
|
1002
|
+
|
|
1003
|
+
```ts
|
|
1004
|
+
const doc = await db.collection('users').doc('user123').get();
|
|
1005
|
+
await db.collection('users').doc('user123').set({ name: 'Alice' });
|
|
1006
|
+
const snapshot = await db.collection('users').where('active', '==', true).get();
|
|
1007
|
+
// Spans: "Firestore GET users/user123", "Firestore SET users/user123", "Firestore QUERY users"
|
|
1008
|
+
```
|
|
1009
|
+
|
|
1010
|
+
Captured operations: GET, SET, ADD, UPDATE, DELETE, CREATE, LIST, QUERY, TX_GET, BATCH_COMMIT.
|
|
1011
|
+
|
|
1012
|
+
**Auth:**
|
|
1013
|
+
|
|
1014
|
+
```ts
|
|
1015
|
+
const user = await admin.auth().getUser(uid);
|
|
1016
|
+
const token = await admin.auth().verifyIdToken(idToken);
|
|
1017
|
+
// Spans: "Firebase Auth getUser", "Firebase Auth verifyIdToken"
|
|
1018
|
+
```
|
|
1019
|
+
|
|
1020
|
+
16 methods captured: createUser, getUser, getUserByEmail, getUserByPhoneNumber, listUsers, deleteUser, deleteUsers, updateUser, verifyIdToken, verifySessionCookie, createSessionCookie, revokeRefreshTokens, setCustomUserClaims, generateEmailVerificationLink, generatePasswordResetLink, generateSignInWithEmailLink.
|
|
1021
|
+
|
|
1022
|
+
**FCM Messaging:**
|
|
1023
|
+
|
|
1024
|
+
```ts
|
|
1025
|
+
await admin.messaging().send({ notification: { title: 'Hi' }, token: deviceToken });
|
|
1026
|
+
// Span: "Firebase FCM send" with success/failure counts
|
|
1027
|
+
```
|
|
1028
|
+
|
|
1029
|
+
9 methods captured: send, sendEach, sendEachForMulticast, sendMulticast, sendToDevice, sendToTopic, sendToCondition, subscribeToTopic, unsubscribeFromTopic.
|
|
1030
|
+
|
|
1031
|
+
---
|
|
1032
|
+
|
|
1033
|
+
## 11. gRPC, GraphQL & Network
|
|
1034
|
+
|
|
1035
|
+
### gRPC
|
|
1036
|
+
|
|
1037
|
+
```ts
|
|
1038
|
+
// Server and client calls automatically instrumented
|
|
1039
|
+
// Span: "grpc /package.Service/Method" with rpc.system, rpc.service, rpc.method, status code
|
|
1040
|
+
```
|
|
1041
|
+
|
|
1042
|
+
Supports unary, client streaming, server streaming, and bidirectional streaming.
|
|
1043
|
+
|
|
1044
|
+
### GraphQL
|
|
1045
|
+
|
|
1046
|
+
```ts
|
|
1047
|
+
// Resolver-level instrumentation
|
|
1048
|
+
// Span: "GraphQL query GetUser" with operation name, operation type, field path
|
|
1049
|
+
```
|
|
1050
|
+
|
|
1051
|
+
Captures query/mutation/subscription operations and individual field resolver execution.
|
|
1052
|
+
|
|
1053
|
+
### DNS
|
|
1054
|
+
|
|
1055
|
+
```ts
|
|
1056
|
+
import dns from 'dns';
|
|
1057
|
+
|
|
1058
|
+
dns.lookup('example.com', (err, address) => { /* ... */ });
|
|
1059
|
+
// Span: "DNS lookup example.com" with hostname, record type
|
|
1060
|
+
```
|
|
1061
|
+
|
|
1062
|
+
### Net (TCP Sockets)
|
|
1063
|
+
|
|
1064
|
+
```ts
|
|
1065
|
+
import net from 'net';
|
|
1066
|
+
|
|
1067
|
+
const socket = net.createConnection({ host: 'db.internal', port: 5432 });
|
|
1068
|
+
// Span: "TCP CONNECT db.internal:5432" with host, port, connection duration
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
---
|
|
1072
|
+
|
|
1073
|
+
## 12. Log Correlation
|
|
1074
|
+
|
|
1075
|
+
### Console Logs
|
|
1076
|
+
|
|
1077
|
+
All `console.log`, `console.info`, `console.warn`, `console.error`, and `console.debug` calls are automatically captured and correlated with the active trace or task context.
|
|
1078
|
+
|
|
1079
|
+
```ts
|
|
1080
|
+
app.get('/users/:id', async (req, res) => {
|
|
1081
|
+
console.info('fetching user', { userId: req.params.id });
|
|
1082
|
+
// Log is captured with traceId, level "info", and attributes { userId: "123" }
|
|
1083
|
+
const user = await getUser(req.params.id);
|
|
1084
|
+
res.json(user);
|
|
1085
|
+
});
|
|
1086
|
+
```
|
|
1087
|
+
|
|
1088
|
+
### Structured Logging Libraries
|
|
1089
|
+
|
|
1090
|
+
Pino, Winston, and Bunyan get trace/span IDs automatically injected into log records:
|
|
1091
|
+
|
|
1092
|
+
```ts
|
|
1093
|
+
import pino from 'pino';
|
|
1094
|
+
const logger = pino();
|
|
1095
|
+
|
|
1096
|
+
app.get('/users/:id', (req, res) => {
|
|
1097
|
+
logger.info({ userId: req.params.id }, 'fetching user');
|
|
1098
|
+
// Output includes: traceId, spanId alongside your log fields
|
|
1099
|
+
});
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
Disable console log capture:
|
|
1103
|
+
|
|
1104
|
+
```ts
|
|
1105
|
+
Senzor.init({ apiKey: '...', autoLogs: false });
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
---
|
|
1109
|
+
|
|
1110
|
+
## 13. Background Task Monitoring
|
|
1111
|
+
|
|
1112
|
+
### Auto-Instrumented
|
|
1113
|
+
|
|
1114
|
+
**BullMQ** workers are captured as task runs with queue delay, retry counts, dead-letter detection, and CPU/memory resource metrics.
|
|
1115
|
+
|
|
1116
|
+
**node-cron** scheduled jobs are captured as task runs with the cron schedule expression.
|
|
1117
|
+
|
|
1118
|
+
### Manual Task Wrapping
|
|
1119
|
+
|
|
1120
|
+
```ts
|
|
1121
|
+
const processPayment = Senzor.wrapTask(
|
|
1122
|
+
'process_payment', // task name
|
|
1123
|
+
'custom', // type: 'cron' | 'queue' | 'pipeline' | 'custom'
|
|
1124
|
+
{ metadata: { owner: 'billing' } },
|
|
1125
|
+
async (invoiceId: string) => {
|
|
1126
|
+
await chargeCustomer(invoiceId);
|
|
1127
|
+
}
|
|
1128
|
+
);
|
|
1129
|
+
|
|
1130
|
+
await processPayment('inv_123');
|
|
1131
|
+
```
|
|
1132
|
+
|
|
1133
|
+
### Low-Level Task API
|
|
1134
|
+
|
|
1135
|
+
```ts
|
|
1136
|
+
Senzor.startTask('rebuild_index', 'custom', { metadata: { trigger: 'manual' } }, async () => {
|
|
1137
|
+
try {
|
|
1138
|
+
await rebuildSearchIndex();
|
|
1139
|
+
// endTask is called automatically on success
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
Senzor.captureException(error);
|
|
1142
|
+
throw error; // endTask('failed') on throw
|
|
1143
|
+
}
|
|
1144
|
+
});
|
|
1145
|
+
```
|
|
1146
|
+
|
|
1147
|
+
Task payloads include:
|
|
1148
|
+
- `taskName`, `taskType`, `status`, `duration`
|
|
1149
|
+
- `queueDelay`, `attempts`, `isDeadLetter` (for BullMQ)
|
|
1150
|
+
- `resourceMetrics`: heap memory delta, CPU user/system time
|
|
1151
|
+
- `triggerTraceId`: links tasks triggered by HTTP requests
|
|
1152
|
+
|
|
1153
|
+
---
|
|
1154
|
+
|
|
1155
|
+
## 14. Manual Traces & Spans
|
|
1156
|
+
|
|
1157
|
+
### Manual Span
|
|
1158
|
+
|
|
1159
|
+
```ts
|
|
1160
|
+
const span = Senzor.startSpan('calculate_invoice', 'function');
|
|
1161
|
+
try {
|
|
1162
|
+
const total = await calculateInvoice(invoiceId);
|
|
1163
|
+
span.end({ invoiceId, total }, 200);
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
span.end({ invoiceId, error: String(error) }, 500);
|
|
1166
|
+
throw error;
|
|
1167
|
+
}
|
|
1168
|
+
```
|
|
1169
|
+
|
|
1170
|
+
Span types: `http`, `db`, `function`, `custom`, `rpc`, `messaging`, `dns`, `net`.
|
|
1171
|
+
|
|
1172
|
+
### Manual Trace
|
|
1173
|
+
|
|
1174
|
+
For environments where framework wrappers can't be used:
|
|
1175
|
+
|
|
1176
|
+
```ts
|
|
1177
|
+
Senzor.track({
|
|
1178
|
+
method: 'POST',
|
|
1179
|
+
route: '/webhooks/payment',
|
|
1180
|
+
path: '/webhooks/payment',
|
|
1181
|
+
status: 202,
|
|
1182
|
+
duration: 18.7,
|
|
1183
|
+
spans: [],
|
|
1184
|
+
});
|
|
1185
|
+
```
|
|
1186
|
+
|
|
1187
|
+
---
|
|
1188
|
+
|
|
1189
|
+
## 15. Error Tracking
|
|
1190
|
+
|
|
1191
|
+
### Automatic
|
|
1192
|
+
|
|
1193
|
+
The SDK captures these global events:
|
|
1194
|
+
|
|
1195
|
+
| Event | Severity |
|
|
1196
|
+
|-------|----------|
|
|
1197
|
+
| `uncaughtExceptionMonitor` | fatal |
|
|
1198
|
+
| `uncaughtException` | fatal |
|
|
1199
|
+
| `unhandledRejection` | error |
|
|
1200
|
+
| `warning` | warning |
|
|
1201
|
+
| `multipleResolves` | warning |
|
|
1202
|
+
| `SIGTERM` | info |
|
|
1203
|
+
| `SIGINT` | info |
|
|
1204
|
+
|
|
1205
|
+
Each captured error includes: stack trace, process context (pid, platform, uptime, NODE_ENV), memory snapshot (RSS, heap, external), and SDK metadata.
|
|
1206
|
+
|
|
1207
|
+
### Manual
|
|
1208
|
+
|
|
1209
|
+
```ts
|
|
1210
|
+
try {
|
|
1211
|
+
await chargeCustomer(customerId);
|
|
1212
|
+
} catch (error) {
|
|
1213
|
+
Senzor.captureException(error, { customerId, operation: 'charge' });
|
|
1214
|
+
throw error;
|
|
1215
|
+
}
|
|
1216
|
+
```
|
|
1217
|
+
|
|
1218
|
+
Errors are correlated with the active trace or task context.
|
|
1219
|
+
|
|
1220
|
+
---
|
|
1221
|
+
|
|
1222
|
+
## 16. Runtime Metrics
|
|
1223
|
+
|
|
1224
|
+
Collected every 15 seconds (configurable) and sent with trace batches.
|
|
1225
|
+
|
|
1226
|
+
| Metric | Description |
|
|
1227
|
+
|--------|-------------|
|
|
1228
|
+
| `eventLoop.lag.p50` | Event loop lag, 50th percentile (ms) |
|
|
1229
|
+
| `eventLoop.lag.p99` | Event loop lag, 99th percentile (ms) |
|
|
1230
|
+
| `eventLoop.lag.max` | Event loop lag, maximum observed (ms) |
|
|
1231
|
+
| `eventLoop.utilization` | Event loop utilization (0-1, from `perf_hooks` ELU) |
|
|
1232
|
+
| `gc.duration.minor` | GC pause time, minor collections (ms) |
|
|
1233
|
+
| `gc.duration.major` | GC pause time, major collections (ms) |
|
|
1234
|
+
| `gc.duration.incremental` | GC pause time, incremental marking (ms) |
|
|
1235
|
+
| `memory.heapUsed` | V8 heap used (bytes) |
|
|
1236
|
+
| `memory.heapTotal` | V8 heap total (bytes) |
|
|
1237
|
+
| `memory.rss` | Resident set size (bytes) |
|
|
1238
|
+
| `memory.external` | External memory (bytes) |
|
|
1239
|
+
| `memory.arrayBuffers` | ArrayBuffer memory (bytes) |
|
|
1240
|
+
| `activeHandles` | Open handles (file descriptors, sockets) |
|
|
1241
|
+
| `activeRequests` | Pending async requests |
|
|
1242
|
+
|
|
1243
|
+
Disable runtime metrics:
|
|
1244
|
+
|
|
1245
|
+
```ts
|
|
1246
|
+
Senzor.init({ apiKey: '...', runtimeMetrics: false });
|
|
1247
|
+
```
|
|
1248
|
+
|
|
1249
|
+
Adjust collection interval:
|
|
1250
|
+
|
|
1251
|
+
```ts
|
|
1252
|
+
Senzor.init({ apiKey: '...', runtimeMetricsInterval: 30000 }); // 30 seconds
|
|
1253
|
+
```
|
|
1254
|
+
|
|
1255
|
+
Runtime metrics are automatically disabled in AWS Lambda environments.
|
|
1256
|
+
|
|
1257
|
+
---
|
|
1258
|
+
|
|
1259
|
+
## 17. Distributed Tracing & Context Propagation
|
|
1260
|
+
|
|
1261
|
+
### Outgoing Headers
|
|
1262
|
+
|
|
1263
|
+
The SDK injects these headers on all outgoing HTTP calls:
|
|
1264
|
+
|
|
1265
|
+
```
|
|
1266
|
+
traceparent: 00-{traceId}-{spanId}-01
|
|
1267
|
+
x-senzor-trace-id: {traceId}
|
|
1268
|
+
x-senzor-parent-span-id: {spanId}
|
|
1269
|
+
```
|
|
1270
|
+
|
|
1271
|
+
### Incoming Headers
|
|
1272
|
+
|
|
1273
|
+
Incoming requests with `traceparent` or `x-senzor-trace-id` headers are linked as child traces, enabling cross-service distributed trace visualization.
|
|
1274
|
+
|
|
1275
|
+
### Context Propagation
|
|
1276
|
+
|
|
1277
|
+
The SDK uses `AsyncLocalStorage` to maintain trace context across async boundaries. All child spans, logs, and errors within a request are automatically correlated to the correct trace without any manual threading.
|
|
1278
|
+
|
|
1279
|
+
---
|
|
1280
|
+
|
|
1281
|
+
## 18. Configuration Reference
|
|
1282
|
+
|
|
1283
|
+
```ts
|
|
1284
|
+
Senzor.init({
|
|
1285
|
+
apiKey: 'sz_apm_xxx', // Required
|
|
1286
|
+
endpoint: 'https://api.senzor.dev', // Ingest endpoint
|
|
1287
|
+
batchSize: 100, // Flush threshold
|
|
1288
|
+
flushInterval: 10000, // Flush interval (ms)
|
|
1289
|
+
flushTimeoutMs: 5000, // Per-request timeout (ms)
|
|
1290
|
+
maxQueueSize: 10000, // Max queued items before drop
|
|
1291
|
+
maxSpansPerTrace: 500, // Max spans per trace
|
|
1292
|
+
maxAttributeLength: 2048, // Max string length
|
|
1293
|
+
maxAttributes: 64, // Max attributes per object
|
|
1294
|
+
captureHeaders: false, // Capture sanitized headers
|
|
1295
|
+
captureDbStatement: true, // Capture sanitized SQL
|
|
1296
|
+
instrumentations: true, // true | false | string[]
|
|
1297
|
+
frameworkSpans: true, // Framework middleware/handler spans
|
|
1298
|
+
captureMiddlewareSpans: true, // Middleware execution spans
|
|
1299
|
+
captureRouterSpans: true, // Router dispatch spans
|
|
1300
|
+
captureLifecycleHookSpans: true, // Lifecycle hook spans
|
|
1301
|
+
autoLogs: true, // Console log capture
|
|
1302
|
+
runtimeMetrics: true, // Runtime metrics collection
|
|
1303
|
+
runtimeMetricsInterval: 15000, // Collection interval (ms)
|
|
1304
|
+
debug: false, // SDK diagnostics
|
|
1305
|
+
});
|
|
1306
|
+
```
|
|
1307
|
+
|
|
1308
|
+
| Option | Production Guidance |
|
|
1309
|
+
|--------|-------------------|
|
|
1310
|
+
| `apiKey` | Always use environment variables or secret manager. Never hardcode. |
|
|
1311
|
+
| `batchSize` | Start at `100`. Increase for high-throughput services. |
|
|
1312
|
+
| `flushInterval` | `10000` ms for servers. `0` for Lambda (flush on demand). |
|
|
1313
|
+
| `maxQueueSize` | Keep bounded. Increase only after checking memory pressure. |
|
|
1314
|
+
| `maxSpansPerTrace` | Keep `500` unless traces fan out heavily (e.g., batch operations). |
|
|
1315
|
+
| `captureHeaders` | Keep `false` unless needed for debugging. |
|
|
1316
|
+
| `debug` | Never enable in production unless actively troubleshooting. |
|
|
1317
|
+
|
|
1318
|
+
### Selective Instrumentation
|
|
1319
|
+
|
|
1320
|
+
```ts
|
|
1321
|
+
// Enable only specific instrumentations
|
|
1322
|
+
Senzor.init({
|
|
1323
|
+
apiKey: '...',
|
|
1324
|
+
instrumentations: ['http', 'fetch', 'pg', 'redis', 'openai'],
|
|
1325
|
+
});
|
|
1326
|
+
|
|
1327
|
+
// Disable all auto-instrumentation (manual APIs only)
|
|
1328
|
+
Senzor.init({
|
|
1329
|
+
apiKey: '...',
|
|
1330
|
+
instrumentations: false,
|
|
1331
|
+
});
|
|
1332
|
+
```
|
|
1333
|
+
|
|
1334
|
+
---
|
|
1335
|
+
|
|
1336
|
+
## 19. Environment Variables
|
|
1337
|
+
|
|
1338
|
+
The preload entrypoint (`@senzops/apm-node/register`) reads:
|
|
1339
|
+
|
|
1340
|
+
| Variable | Maps To | Default |
|
|
1341
|
+
|----------|---------|---------|
|
|
1342
|
+
| `SENZOR_API_KEY` | `apiKey` | — |
|
|
1343
|
+
| `SENZOR_APM_API_KEY` | `apiKey` (alt) | — |
|
|
1344
|
+
| `SENZOR_SERVICE_API_KEY` | `apiKey` (alt) | — |
|
|
1345
|
+
| `SENZOR_ENDPOINT` | `endpoint` | `https://api.senzor.dev` |
|
|
1346
|
+
| `SENZOR_APM_ENDPOINT` | `endpoint` (alt) | — |
|
|
1347
|
+
| `SENZOR_DEBUG` | `debug` | `false` |
|
|
1348
|
+
| `SENZOR_AUTO_LOGS` | `autoLogs` | `true` |
|
|
1349
|
+
| `SENZOR_BATCH_SIZE` | `batchSize` | `100` (or `10` in Lambda) |
|
|
1350
|
+
| `SENZOR_FLUSH_INTERVAL` | `flushInterval` | `10000` (or `0` in Lambda) |
|
|
1351
|
+
| `SENZOR_FLUSH_TIMEOUT_MS` | `flushTimeoutMs` | `5000` |
|
|
1352
|
+
| `SENZOR_MAX_QUEUE_SIZE` | `maxQueueSize` | `10000` |
|
|
1353
|
+
| `SENZOR_MAX_SPANS_PER_TRACE` | `maxSpansPerTrace` | `500` |
|
|
1354
|
+
| `SENZOR_CAPTURE_HEADERS` | `captureHeaders` | `false` |
|
|
1355
|
+
| `SENZOR_CAPTURE_DB_STATEMENT` | `captureDbStatement` | `true` |
|
|
1356
|
+
| `SENZOR_FRAMEWORK_SPANS` | `frameworkSpans` | `true` |
|
|
1357
|
+
| `SENZOR_CAPTURE_MIDDLEWARE_SPANS` | `captureMiddlewareSpans` | `true` |
|
|
1358
|
+
| `SENZOR_CAPTURE_ROUTER_SPANS` | `captureRouterSpans` | `true` |
|
|
1359
|
+
| `SENZOR_CAPTURE_LIFECYCLE_HOOK_SPANS` | `captureLifecycleHookSpans` | `true` |
|
|
1360
|
+
| `SENZOR_RUNTIME_METRICS` | `runtimeMetrics` | `true` (auto `false` in Lambda) |
|
|
1361
|
+
| `SENZOR_RUNTIME_METRICS_INTERVAL` | `runtimeMetricsInterval` | `15000` |
|
|
1362
|
+
|
|
1363
|
+
Environment variable values always take precedence over programmatic configuration.
|
|
1364
|
+
|
|
1365
|
+
---
|
|
1366
|
+
|
|
1367
|
+
## 20. Security, Privacy & Cardinality
|
|
1368
|
+
|
|
1369
|
+
### Automatic Redaction
|
|
1370
|
+
|
|
1371
|
+
These keys are automatically redacted from attributes, headers, logs, and error context:
|
|
1372
|
+
|
|
1373
|
+
`authorization`, `cookie`, `set-cookie`, `password`, `passwd`, `pwd`, `secret`, `token`, `api-key`, `x-api-key`, `access-token`, `refresh-token`, `client-secret`, `private-key`
|
|
1374
|
+
|
|
1375
|
+
### Cardinality Controls
|
|
1376
|
+
|
|
1377
|
+
- URL routes are normalized: `/users/123` becomes `/users/:id`
|
|
1378
|
+
- UUIDs and MongoDB ObjectIDs are replaced with `:id` in route names
|
|
1379
|
+
- SQL statements are normalized to remove literal values
|
|
1380
|
+
- Attribute count and string length are bounded
|
|
1381
|
+
- Span count per trace is bounded
|
|
1382
|
+
- Queue size is bounded with oldest-first eviction
|
|
1383
|
+
|
|
1384
|
+
### Recommendations
|
|
1385
|
+
|
|
1386
|
+
- Keep `captureHeaders` disabled unless required for debugging
|
|
1387
|
+
- Never capture request/response bodies
|
|
1388
|
+
- Use route templates (e.g., `/users/:id`) rather than actual paths in span names
|
|
1389
|
+
- Store IDs in metadata, not in operation names
|
|
1390
|
+
- Use `instrumentations: [...]` to disable instrumentations you don't need
|
|
1391
|
+
|
|
1392
|
+
---
|
|
1393
|
+
|
|
1394
|
+
## 21. Transport Behavior
|
|
1395
|
+
|
|
1396
|
+
The SDK batches telemetry and sends it asynchronously using two independent queues:
|
|
1397
|
+
|
|
1398
|
+
- **APM Queue**: traces, errors, logs, runtime metrics -> `/api/ingest/apm`
|
|
1399
|
+
- **Task Queue**: task runs, task errors, task logs -> `/api/ingest/task`
|
|
1400
|
+
|
|
1401
|
+
Behavior:
|
|
1402
|
+
|
|
1403
|
+
- Queues are bounded by `maxQueueSize` (oldest items evicted first)
|
|
1404
|
+
- Flush triggers on `batchSize` threshold or `flushInterval` timer
|
|
1405
|
+
- Each ingest request has a `flushTimeoutMs` timeout
|
|
1406
|
+
- Failed batches are requeued within queue limits (retry on next flush)
|
|
1407
|
+
- SDK ingest requests are marked as internal (not traced recursively)
|
|
1408
|
+
- A best-effort flush runs on `process.beforeExit`
|
|
1409
|
+
- In Lambda, flush is forced before handler return + Extensions API SHUTDOWN hook
|
|
1410
|
+
|
|
1411
|
+
### Serverless Flush
|
|
1412
|
+
|
|
1413
|
+
Always call `await Senzor.flush()` before the function exits in serverless environments (unless using `wrapLambda` or `Senzor.worker()`, which handle this automatically).
|
|
1414
|
+
|
|
1415
|
+
---
|
|
1416
|
+
|
|
1417
|
+
## 22. Ingestion Payload Format
|
|
1418
|
+
|
|
1419
|
+
### APM Ingest (`POST /api/ingest/apm`)
|
|
1420
|
+
|
|
1421
|
+
```json
|
|
1422
|
+
{
|
|
1423
|
+
"traces": [{
|
|
1424
|
+
"traceId": "f3b2c2c9c70443f5a4b7f0ff6d5b9a17",
|
|
1425
|
+
"parentTraceId": "8cb1f2e2f43e4b2b8b3411c7df81e7e0",
|
|
1426
|
+
"parentSpanId": "6c62c908a21d4f49",
|
|
1427
|
+
"rootSpanId": "91f6c551d5a2403f",
|
|
1428
|
+
"method": "GET",
|
|
1429
|
+
"route": "/orders/:orderId",
|
|
1430
|
+
"path": "/orders/ord_123",
|
|
1431
|
+
"status": 200,
|
|
1432
|
+
"duration": 53.29,
|
|
1433
|
+
"ip": "203.0.113.10",
|
|
1434
|
+
"userAgent": "Mozilla/5.0",
|
|
1435
|
+
"timestamp": "2026-05-26T15:30:00.000Z",
|
|
1436
|
+
"spans": [{
|
|
1437
|
+
"spanId": "9d8a4d5f17e24d2a",
|
|
1438
|
+
"parentSpanId": "91f6c551d5a2403f",
|
|
1439
|
+
"name": "Postgres SELECT",
|
|
1440
|
+
"type": "db",
|
|
1441
|
+
"startTime": 5.41,
|
|
1442
|
+
"duration": 12.45,
|
|
1443
|
+
"status": 0,
|
|
1444
|
+
"meta": {
|
|
1445
|
+
"db.system.name": "postgresql",
|
|
1446
|
+
"db.operation.name": "SELECT",
|
|
1447
|
+
"db.query.text": "SELECT id FROM users WHERE id = $?"
|
|
1448
|
+
}
|
|
1449
|
+
}]
|
|
1450
|
+
}],
|
|
1451
|
+
"errors": [{
|
|
1452
|
+
"errorClass": "Error",
|
|
1453
|
+
"message": "Connection refused",
|
|
1454
|
+
"stackTrace": "Error: Connection refused\n at ...",
|
|
1455
|
+
"traceId": "f3b2c2c9c70443f5a4b7f0ff6d5b9a17",
|
|
1456
|
+
"context": { "operation": "db_connect" },
|
|
1457
|
+
"timestamp": "2026-05-26T15:30:00.000Z"
|
|
1458
|
+
}],
|
|
1459
|
+
"logs": [{
|
|
1460
|
+
"message": "order processed",
|
|
1461
|
+
"level": "info",
|
|
1462
|
+
"traceId": "f3b2c2c9c70443f5a4b7f0ff6d5b9a17",
|
|
1463
|
+
"attributes": { "orderId": "ord_123" },
|
|
1464
|
+
"timestamp": "2026-05-26T15:30:00.000Z"
|
|
1465
|
+
}],
|
|
1466
|
+
"runtimeMetrics": [{
|
|
1467
|
+
"timestamp": "2026-05-26T15:30:00.000Z",
|
|
1468
|
+
"eventLoop": { "lagP50": 1.2, "lagP99": 4.8, "lagMax": 12.1, "utilization": 0.34 },
|
|
1469
|
+
"gc": { "minor": 2.1, "major": 0, "incremental": 0.8 },
|
|
1470
|
+
"memory": { "heapUsed": 45000000, "heapTotal": 67000000, "rss": 89000000 }
|
|
1471
|
+
}]
|
|
1472
|
+
}
|
|
1473
|
+
```
|
|
1474
|
+
|
|
1475
|
+
### Task Ingest (`POST /api/ingest/task`)
|
|
1476
|
+
|
|
1477
|
+
```json
|
|
1478
|
+
{
|
|
1479
|
+
"runs": [{
|
|
1480
|
+
"runId": "3917bd35-b1d6-4e23-a1d2-d969e1a7d6a1",
|
|
1481
|
+
"taskName": "billing:send_invoice_email",
|
|
1482
|
+
"taskType": "queue",
|
|
1483
|
+
"status": "success",
|
|
1484
|
+
"duration": 188.3,
|
|
1485
|
+
"queueDelay": 92,
|
|
1486
|
+
"attempts": 1,
|
|
1487
|
+
"triggerTraceId": "f3b2c2c9c70443f5a4b7f0ff6d5b9a17",
|
|
1488
|
+
"metadata": { "jobId": "142", "queueName": "billing" },
|
|
1489
|
+
"resourceMetrics": {
|
|
1490
|
+
"memoryDeltaBytes": 1048576,
|
|
1491
|
+
"cpuUserUs": 12000,
|
|
1492
|
+
"cpuSystemUs": 3000
|
|
1493
|
+
},
|
|
1494
|
+
"isDeadLetter": false,
|
|
1495
|
+
"spans": [],
|
|
1496
|
+
"timestamp": "2026-05-26T15:30:00.000Z"
|
|
1497
|
+
}],
|
|
1498
|
+
"errors": [],
|
|
1499
|
+
"logs": []
|
|
1500
|
+
}
|
|
1501
|
+
```
|
|
1502
|
+
|
|
1503
|
+
---
|
|
1504
|
+
|
|
1505
|
+
## 23. Deployment Patterns
|
|
1506
|
+
|
|
1507
|
+
### Docker
|
|
1508
|
+
|
|
1509
|
+
```dockerfile
|
|
1510
|
+
FROM node:20-alpine
|
|
1511
|
+
WORKDIR /app
|
|
1512
|
+
COPY package*.json ./
|
|
1513
|
+
RUN npm ci --production
|
|
1514
|
+
COPY dist/ dist/
|
|
1515
|
+
|
|
1516
|
+
ENV SENZOR_API_KEY=sz_apm_xxx
|
|
1517
|
+
CMD ["node", "-r", "@senzops/apm-node/register", "dist/server.js"]
|
|
1518
|
+
```
|
|
1519
|
+
|
|
1520
|
+
### Kubernetes
|
|
1521
|
+
|
|
1522
|
+
```yaml
|
|
1523
|
+
apiVersion: apps/v1
|
|
1524
|
+
kind: Deployment
|
|
1525
|
+
spec:
|
|
1526
|
+
template:
|
|
1527
|
+
spec:
|
|
1528
|
+
containers:
|
|
1529
|
+
- name: api
|
|
1530
|
+
image: my-api:latest
|
|
1531
|
+
env:
|
|
1532
|
+
- name: SENZOR_API_KEY
|
|
1533
|
+
valueFrom:
|
|
1534
|
+
secretKeyRef:
|
|
1535
|
+
name: senzor-secrets
|
|
1536
|
+
key: api-key
|
|
1537
|
+
- name: NODE_OPTIONS
|
|
1538
|
+
value: "-r @senzops/apm-node/register"
|
|
1539
|
+
```
|
|
1540
|
+
|
|
1541
|
+
### PM2
|
|
1542
|
+
|
|
1543
|
+
```json
|
|
1544
|
+
{
|
|
1545
|
+
"apps": [{
|
|
1546
|
+
"name": "orders-api",
|
|
1547
|
+
"script": "dist/server.js",
|
|
1548
|
+
"node_args": "-r @senzops/apm-node/register",
|
|
1549
|
+
"env": {
|
|
1550
|
+
"SENZOR_API_KEY": "sz_apm_xxx",
|
|
1551
|
+
"NODE_ENV": "production"
|
|
1552
|
+
}
|
|
1553
|
+
}]
|
|
1554
|
+
}
|
|
1555
|
+
```
|
|
1556
|
+
|
|
1557
|
+
### AWS Lambda
|
|
1558
|
+
|
|
1559
|
+
See [Section 4: AWS Lambda Integration](#4-aws-lambda-integration).
|
|
1560
|
+
|
|
1561
|
+
### Cloudflare Workers
|
|
1562
|
+
|
|
1563
|
+
See [Section 5: Cloudflare Workers Integration](#5-cloudflare-workers-integration).
|
|
1564
|
+
|
|
1565
|
+
### Vercel (Serverless Functions)
|
|
1566
|
+
|
|
1567
|
+
```ts
|
|
1568
|
+
// api/route.ts
|
|
1569
|
+
import Senzor from '@senzops/apm-node';
|
|
1570
|
+
|
|
1571
|
+
Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });
|
|
1572
|
+
|
|
1573
|
+
export const GET = Senzor.wrapNextRoute(async (req) => {
|
|
1574
|
+
const result = await fetchData();
|
|
1575
|
+
return Response.json(result);
|
|
1576
|
+
});
|
|
1577
|
+
```
|
|
1578
|
+
|
|
1579
|
+
---
|
|
1580
|
+
|
|
1581
|
+
## 24. Troubleshooting
|
|
1582
|
+
|
|
1583
|
+
### Missing Spans
|
|
1584
|
+
|
|
1585
|
+
1. **Check startup order.** Preload mode must load before application modules:
|
|
1586
|
+
```sh
|
|
1587
|
+
node -r @senzops/apm-node/register server.js
|
|
1588
|
+
```
|
|
1589
|
+
2. **Programmatic mode:** Initialize `Senzor.init()` before importing `pg`, `mongodb`, `redis`, etc.
|
|
1590
|
+
3. **Check if the library is supported** in the [instrumentation table](#6-auto-instrumentation-reference).
|
|
1591
|
+
|
|
1592
|
+
### No Data in Senzor
|
|
1593
|
+
|
|
1594
|
+
1. Verify `SENZOR_API_KEY` is set and valid.
|
|
1595
|
+
2. Verify `endpoint` points to the correct Senzor environment.
|
|
1596
|
+
3. Verify the runtime can reach the ingest endpoint (check firewall, VPC, DNS).
|
|
1597
|
+
4. Enable `debug: true` to see flush attempts.
|
|
1598
|
+
5. In serverless environments, ensure `Senzor.flush()` is called (or use `wrapLambda`/`worker()`).
|
|
1599
|
+
|
|
1600
|
+
### Duplicate Traces
|
|
1601
|
+
|
|
1602
|
+
The SDK deduplicates active traces. If duplicates still appear:
|
|
1603
|
+
- Check whether both preload mode AND manual `track()` calls are active for the same request.
|
|
1604
|
+
- Ensure `Senzor.requestHandler()` is only attached once.
|
|
1605
|
+
|
|
1606
|
+
### High Memory Usage
|
|
1607
|
+
|
|
1608
|
+
```ts
|
|
1609
|
+
Senzor.init({
|
|
1610
|
+
apiKey: '...',
|
|
1611
|
+
maxQueueSize: 5000, // Reduce queue bounds
|
|
1612
|
+
maxSpansPerTrace: 250, // Reduce span retention
|
|
1613
|
+
maxAttributes: 32, // Reduce attribute count
|
|
1614
|
+
maxAttributeLength: 1024, // Reduce string lengths
|
|
1615
|
+
});
|
|
1616
|
+
```
|
|
1617
|
+
|
|
1618
|
+
### High-Cardinality Routes
|
|
1619
|
+
|
|
1620
|
+
Use framework wrappers that provide route templates:
|
|
1621
|
+
|
|
1622
|
+
```ts
|
|
1623
|
+
app.use(Senzor.requestHandler()); // Captures /users/:id instead of /users/123
|
|
1624
|
+
```
|
|
1625
|
+
|
|
1626
|
+
For manual traces, use normalized routes:
|
|
1627
|
+
|
|
1628
|
+
```ts
|
|
1629
|
+
Senzor.track({ method: 'GET', route: '/users/:id', path: '/users/123', ... });
|
|
1630
|
+
```
|
|
1631
|
+
|
|
1632
|
+
### Lambda Cold Start Issues
|
|
1633
|
+
|
|
1634
|
+
- Ensure `Senzor.init()` or preload runs outside the handler (module scope).
|
|
1635
|
+
- Check that `NODE_OPTIONS` is set correctly for Lambda Layer deployments.
|
|
1636
|
+
- Use `Senzor.wrapLambda()` for proper cold start tagging.
|
|
1637
|
+
|
|
1638
|
+
---
|
|
1639
|
+
|
|
1640
|
+
## 25. Public API Reference
|
|
1641
|
+
|
|
1642
|
+
```ts
|
|
1643
|
+
// Initialization
|
|
1644
|
+
Senzor.init(options: SenzorOptions) // Initialize SDK with API key
|
|
1645
|
+
Senzor.preload(options?: Partial<SenzorOptions>) // Pre-install hooks (used by register.ts)
|
|
1646
|
+
|
|
1647
|
+
// Telemetry
|
|
1648
|
+
Senzor.flush(): Promise<void> // Force flush all queued telemetry
|
|
1649
|
+
Senzor.track(data: object) // Send a manual trace
|
|
1650
|
+
Senzor.startSpan(name: string, type?: SpanType) // Start a manual child span
|
|
1651
|
+
Senzor.captureException(error: unknown, ctx?: object) // Capture an error
|
|
1652
|
+
|
|
1653
|
+
// Task Monitoring
|
|
1654
|
+
Senzor.wrapTask(name, type, options, fn) // Wrap function as monitored task
|
|
1655
|
+
Senzor.startTask(name, type, options, fn) // Start task monitoring context
|
|
1656
|
+
|
|
1657
|
+
// Framework Wrappers
|
|
1658
|
+
Senzor.requestHandler() // Express request middleware
|
|
1659
|
+
Senzor.errorHandler() // Express error middleware
|
|
1660
|
+
Senzor.fastifyPlugin // Fastify plugin
|
|
1661
|
+
Senzor.wrapNextRoute(handler) // Next.js App Router wrapper
|
|
1662
|
+
Senzor.wrapNextPages(handler) // Next.js Pages Router wrapper
|
|
1663
|
+
Senzor.wrapH3(handler) // H3/Nuxt/Nitro event handler wrapper
|
|
1664
|
+
Senzor.nitroPlugin // Nitro plugin for Cloudflare Workers
|
|
1665
|
+
Senzor.worker(handler) // Cloudflare Workers fetch handler wrapper
|
|
1666
|
+
Senzor.wrapLambda(handler) // AWS Lambda handler wrapper
|
|
1667
|
+
```
|
|
1668
|
+
|
|
1669
|
+
---
|
|
1670
|
+
|
|
1671
|
+
## 26. Build & Publish
|
|
1672
|
+
|
|
1673
|
+
```sh
|
|
1674
|
+
npm run build
|
|
1675
|
+
```
|
|
1676
|
+
|
|
1677
|
+
Output:
|
|
1678
|
+
|
|
1679
|
+
| File | Format | Description |
|
|
1680
|
+
|------|--------|-------------|
|
|
1681
|
+
| `dist/index.js` | CommonJS | Main entry |
|
|
1682
|
+
| `dist/index.mjs` | ESM | Main entry |
|
|
1683
|
+
| `dist/index.global.js` | IIFE | Browser/CDN bundle |
|
|
1684
|
+
| `dist/register.js` | CommonJS | Preload entry |
|
|
1685
|
+
| `dist/register.mjs` | ESM | Preload entry |
|
|
1686
|
+
| `dist/index.d.ts` | TypeScript | Type declarations |
|
|
1687
|
+
| `dist/register.d.ts` | TypeScript | Type declarations |
|
|
1688
|
+
|
|
1689
|
+
Verify before publishing:
|
|
1690
|
+
|
|
1691
|
+
```sh
|
|
1692
|
+
npx tsc --noEmit && npm run build
|
|
1693
|
+
```
|