@senzops/apm-node 1.2.5 → 1.2.6

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/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@senzops/apm-node",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
4
4
  "description": "Universal APM SDK for Senzor",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "exports": {
8
8
  ".": {
9
9
  "types": "./dist/index.d.ts",
10
+ "worker": "./dist/index.mjs",
10
11
  "require": "./dist/index.js",
11
12
  "import": "./dist/index.mjs"
12
13
  },
@@ -26,7 +27,8 @@
26
27
  "typescript": "^5.0.0"
27
28
  },
28
29
  "engines": {
29
- "node": ">=18.0.0"
30
+ "node": ">=18.0.0",
31
+ "bun": ">=1.0.0"
30
32
  },
31
33
  "keywords": [
32
34
  "apm",
@@ -35,7 +37,10 @@
35
37
  "node",
36
38
  "javascript",
37
39
  "api",
38
- "observability"
40
+ "observability",
41
+ "cloudflare-workers",
42
+ "edge",
43
+ "universal"
39
44
  ],
40
45
  "author": "Senzops",
41
46
  "license": "MIT"
@@ -1,19 +1,7 @@
1
1
  import { Transport } from './transport';
2
2
  import { Context } from './context';
3
3
  import { SenzorOptions, ActiveTrace, TaskRun, SenzorLog } from './types';
4
- import { randomUUID } from 'crypto';
5
- import { instrumentHttp, instrumentFetch } from '../instrumentation/http';
6
- import { instrumentExpress } from '../instrumentation/express';
7
- import { instrumentFastify } from '../instrumentation/fastify';
8
- import { instrumentKoa } from '../instrumentation/koa';
9
- import { instrumentMongo } from '../instrumentation/mongo';
10
- import { instrumentPg } from '../instrumentation/pg';
11
- import { instrumentUndici } from '../instrumentation/undici';
12
- import { instrumentRedis } from '../instrumentation/redis';
13
- import { instrumentMysql } from '../instrumentation/mysql';
14
- import { instrumentMongoose } from '../instrumentation/mongoose';
15
- import { instrumentBullMQ } from '../instrumentation/bullmq';
16
- import { instrumentNodeCron } from '../instrumentation/cron';
4
+ import { isNode } from './runtime';
17
5
  import { SDK_META } from '../utils/sdkMeta';
18
6
  import { parseTraceparent } from '../utils/traceContext';
19
7
  import { generateSpanId, generateTraceId } from '../utils/ids';
@@ -72,29 +60,37 @@ export class SenzorClient {
72
60
  }
73
61
 
74
62
  private installNativeInstrumentations(endpoint: string, debug: boolean) {
75
- if (!this.isInstrumented) {
76
- this.setupGlobalErrorHandlers();
77
- this.setupLogInterception(); // Fire up Auto Log Instrumentation
78
-
79
- try { if (this.isInstrumentationEnabled('http')) instrumentHttp(this, endpoint, this.options || undefined); } catch (e) { }
80
- try { if (this.isInstrumentationEnabled('express')) instrumentExpress(this.options || undefined); } catch (e) { }
81
- try { if (this.isInstrumentationEnabled('fastify')) instrumentFastify(this.options || undefined); } catch (e) { }
82
- try { if (this.isInstrumentationEnabled('koa')) instrumentKoa(this.options || undefined); } catch (e) { }
83
- try { if (this.isInstrumentationEnabled('fetch')) instrumentFetch(endpoint, this.options || undefined); } catch (e) { }
84
- try { if (this.isInstrumentationEnabled('undici')) instrumentUndici(this.options || undefined); } catch (e) { }
85
- try { if (this.isInstrumentationEnabled('mongo')) instrumentMongo(this.options || undefined); } catch (e) { }
86
- try { if (this.isInstrumentationEnabled('mongoose')) instrumentMongoose(this.options || undefined); } catch (e) { }
87
- try { if (this.isInstrumentationEnabled('pg')) instrumentPg(this.options || undefined); } catch (e) { }
88
- try { if (this.isInstrumentationEnabled('mysql')) instrumentMysql(this.options || undefined); } catch (e) { }
89
- try { if (this.isInstrumentationEnabled('redis')) instrumentRedis(this.options || undefined); } catch (e) { }
90
-
91
- // Task Integrations
92
- try { if (this.isInstrumentationEnabled('bullmq')) instrumentBullMQ(this, debug); } catch (e) { }
93
- try { if (this.isInstrumentationEnabled('cron')) instrumentNodeCron(this, debug); } catch (e) { }
94
-
95
- this.isInstrumented = true;
96
- if (debug) console.log('[Senzor] Auto-instrumentation enabled');
63
+ if (this.isInstrumented) return;
64
+
65
+ this.setupGlobalErrorHandlers();
66
+ this.setupLogInterception();
67
+
68
+ // Fetch instrumentation works on all runtimes (Workers, Node, Bun, Deno)
69
+ try {
70
+ if (this.isInstrumentationEnabled('fetch')) {
71
+ const { instrumentFetch } = require('../instrumentation/http');
72
+ instrumentFetch(endpoint, this.options || undefined);
73
+ }
74
+ } catch {}
75
+
76
+ // Node-only instrumentations: http, module hooking, db drivers, etc.
77
+ if (isNode()) {
78
+ try { if (this.isInstrumentationEnabled('http')) { const { instrumentHttp } = require('../instrumentation/http'); instrumentHttp(this, endpoint, this.options || undefined); } } catch {}
79
+ try { if (this.isInstrumentationEnabled('express')) { const { instrumentExpress } = require('../instrumentation/express'); instrumentExpress(this.options || undefined); } } catch {}
80
+ try { if (this.isInstrumentationEnabled('fastify')) { const { instrumentFastify } = require('../instrumentation/fastify'); instrumentFastify(this.options || undefined); } } catch {}
81
+ try { if (this.isInstrumentationEnabled('koa')) { const { instrumentKoa } = require('../instrumentation/koa'); instrumentKoa(this.options || undefined); } } catch {}
82
+ try { if (this.isInstrumentationEnabled('undici')) { const { instrumentUndici } = require('../instrumentation/undici'); instrumentUndici(this.options || undefined); } } catch {}
83
+ try { if (this.isInstrumentationEnabled('mongo')) { const { instrumentMongo } = require('../instrumentation/mongo'); instrumentMongo(this.options || undefined); } } catch {}
84
+ try { if (this.isInstrumentationEnabled('mongoose')) { const { instrumentMongoose } = require('../instrumentation/mongoose'); instrumentMongoose(this.options || undefined); } } catch {}
85
+ try { if (this.isInstrumentationEnabled('pg')) { const { instrumentPg } = require('../instrumentation/pg'); instrumentPg(this.options || undefined); } } catch {}
86
+ try { if (this.isInstrumentationEnabled('mysql')) { const { instrumentMysql } = require('../instrumentation/mysql'); instrumentMysql(this.options || undefined); } } catch {}
87
+ try { if (this.isInstrumentationEnabled('redis')) { const { instrumentRedis } = require('../instrumentation/redis'); instrumentRedis(this.options || undefined); } } catch {}
88
+ try { if (this.isInstrumentationEnabled('bullmq')) { const { instrumentBullMQ } = require('../instrumentation/bullmq'); instrumentBullMQ(this, debug); } } catch {}
89
+ try { if (this.isInstrumentationEnabled('cron')) { const { instrumentNodeCron } = require('../instrumentation/cron'); instrumentNodeCron(this, debug); } } catch {}
97
90
  }
91
+
92
+ this.isInstrumented = true;
93
+ if (debug) console.log('[Senzor] Auto-instrumentation enabled');
98
94
  }
99
95
 
100
96
  // --- Enterprise Auto-Log Interception ---
@@ -177,6 +173,8 @@ export class SenzorClient {
177
173
  }
178
174
 
179
175
  private setupGlobalErrorHandlers() {
176
+ if (!isNode()) return;
177
+
180
178
  if ((process as any).__senzorGlobalHandlersInstalled) {
181
179
  return;
182
180
  }
@@ -338,11 +336,11 @@ export class SenzorClient {
338
336
  const currentContext = Context.current();
339
337
  const triggerTraceId = currentContext?.contextType === 'apm' ? currentContext.id : undefined;
340
338
 
341
- const startMemory = process.memoryUsage ? process.memoryUsage().heapUsed : 0;
342
- const startCpu = process.cpuUsage ? process.cpuUsage() : undefined;
339
+ const startMemory = isNode() && process.memoryUsage ? process.memoryUsage().heapUsed : 0;
340
+ const startCpu = isNode() && process.cpuUsage ? process.cpuUsage() : undefined;
343
341
 
344
342
  const task: ActiveTrace = {
345
- id: randomUUID(),
343
+ id: generateTraceId(),
346
344
  contextType: 'task',
347
345
  startTime: performance.now(),
348
346
  rootSpanId: generateSpanId(),
@@ -367,7 +365,7 @@ export class SenzorClient {
367
365
  task.state.ended = true;
368
366
 
369
367
  let resourceMetrics;
370
- if (process.memoryUsage && task.startMemory !== undefined && process.cpuUsage && task.startCpu) {
368
+ if (isNode() && process.memoryUsage && task.startMemory !== undefined && process.cpuUsage && task.startCpu) {
371
369
  const endMemory = process.memoryUsage().heapUsed;
372
370
  const cpuDelta = process.cpuUsage(task.startCpu);
373
371
 
@@ -1,7 +1,51 @@
1
- import { AsyncLocalStorage } from 'async_hooks';
2
1
  import { ActiveTrace, Span } from './types';
3
2
 
4
- export const storage = new AsyncLocalStorage<ActiveTrace>();
3
+ interface IStorage<T> {
4
+ run<R>(store: T, callback: (...args: any[]) => R, ...args: any[]): R;
5
+ getStore(): T | undefined;
6
+ }
7
+
8
+ class NaiveStorage<T> implements IStorage<T> {
9
+ private store: T | undefined;
10
+
11
+ run<R>(store: T, callback: (...args: any[]) => R, ...args: any[]): R {
12
+ const prev = this.store;
13
+ this.store = store;
14
+ try {
15
+ return callback(...args);
16
+ } finally {
17
+ this.store = prev;
18
+ }
19
+ }
20
+
21
+ getStore(): T | undefined {
22
+ return this.store;
23
+ }
24
+ }
25
+
26
+ const resolveStorage = <T>(): IStorage<T> => {
27
+ if (typeof globalThis !== 'undefined' && (globalThis as any).AsyncLocalStorage) {
28
+ return new (globalThis as any).AsyncLocalStorage();
29
+ }
30
+
31
+ try {
32
+ if (typeof require !== 'undefined') {
33
+ const { AsyncLocalStorage } = require('node:async_hooks');
34
+ if (AsyncLocalStorage) return new AsyncLocalStorage();
35
+ }
36
+ } catch {}
37
+
38
+ try {
39
+ if (typeof require !== 'undefined') {
40
+ const { AsyncLocalStorage } = require('async_hooks');
41
+ if (AsyncLocalStorage) return new AsyncLocalStorage();
42
+ }
43
+ } catch {}
44
+
45
+ return new NaiveStorage<T>();
46
+ };
47
+
48
+ export const storage = resolveStorage<ActiveTrace>();
5
49
 
6
50
  export const Context = {
7
51
  run: <T>(trace: ActiveTrace, fn: () => T): T => {
@@ -0,0 +1,15 @@
1
+ const _isNode = typeof process !== 'undefined' &&
2
+ typeof process.versions !== 'undefined' &&
3
+ typeof process.versions.node !== 'undefined';
4
+
5
+ export const isNode = (): boolean => _isNode;
6
+
7
+ export const isEdgeRuntime = (): boolean =>
8
+ typeof navigator !== 'undefined' &&
9
+ typeof navigator.userAgent === 'string' &&
10
+ navigator.userAgent.includes('Cloudflare');
11
+
12
+ export const getEnv = (key: string): string | undefined => {
13
+ if (_isNode) return process.env[key];
14
+ return undefined;
15
+ };
@@ -255,6 +255,8 @@ export class Transport {
255
255
  }
256
256
 
257
257
  private installShutdownFlush() {
258
+ if (typeof process === 'undefined' || typeof process.once !== 'function') return;
259
+
258
260
  const key = Symbol.for('senzor.transport.shutdownFlushInstalled');
259
261
  const proc = process as unknown as Record<symbol, boolean>;
260
262
  if (proc[key]) return;
package/src/index.ts CHANGED
@@ -2,18 +2,20 @@ import { client } from './core/client';
2
2
  import { expressMiddleware, expressErrorHandler } from './middleware/express';
3
3
  import { wrapH3 } from './wrappers/h3';
4
4
  import { wrapNextRoute, wrapNextPages } from './wrappers/next';
5
+ import { wrapWorker } from './wrappers/worker';
6
+ import { nitroPlugin } from './wrappers/nitro';
5
7
  import { senzorPlugin } from './wrappers/fastify';
6
8
  import { SenzorOptions } from './core/types';
7
9
 
8
- const Senzor = {
9
- preload: (options: Partial<SenzorOptions> = {}) => client.preload(options),
10
- init: (options: SenzorOptions) => client.init(options),
11
- flush: () => client.flush(),
10
+ const Senzor = {
11
+ preload: (options: Partial<SenzorOptions> = {}) => client.preload(options),
12
+ init: (options: SenzorOptions) => client.init(options),
13
+ flush: () => client.flush(),
12
14
  track: client.track.bind(client),
13
15
  startSpan: client.startSpan.bind(client),
14
16
  captureException: client.captureError.bind(client),
15
17
 
16
- // Task Monitoring (NEW)
18
+ // Task Monitoring
17
19
  wrapTask: client.wrapTask.bind(client),
18
20
  startTask: client.startTask.bind(client),
19
21
 
@@ -29,8 +31,14 @@ const Senzor = {
29
31
  wrapH3,
30
32
 
31
33
  // Fastify
32
- fastifyPlugin: senzorPlugin
34
+ fastifyPlugin: senzorPlugin,
35
+
36
+ // Cloudflare Workers
37
+ worker: wrapWorker,
38
+
39
+ // Nitro / Nuxt
40
+ nitroPlugin
33
41
  };
34
42
 
35
43
  export default Senzor;
36
- export { Senzor };
44
+ export { Senzor };
@@ -1,18 +1,20 @@
1
- import Module from 'module';
2
-
3
- const SENZOR_PATCHED = Symbol.for('senzor.require.patched');
4
- const SENZOR_HOOKS = Symbol.for('senzor.require.hooks');
5
-
6
1
  type HookFn = (exports: unknown) => unknown | void;
7
2
  type HookMap = Map<string, HookFn[]>;
8
3
 
9
- // Module.createRequire works in both CJS and ESM contexts,
10
- // unlike bare `require` which is unavailable in ESM builds.
11
- const safeRequire: NodeRequire = Module.createRequire(
12
- typeof __filename !== 'undefined'
13
- ? __filename
14
- : process.cwd() + '/'
15
- );
4
+ let Module: any;
5
+ let safeRequire: NodeRequire;
6
+
7
+ try {
8
+ Module = require('module');
9
+ safeRequire = Module.createRequire(
10
+ typeof __filename !== 'undefined'
11
+ ? __filename
12
+ : process.cwd() + '/'
13
+ );
14
+ } catch {}
15
+
16
+ const SENZOR_PATCHED = Symbol.for('senzor.require.patched');
17
+ const SENZOR_HOOKS = Symbol.for('senzor.require.hooks');
16
18
 
17
19
  function getHookRegistry(): HookMap {
18
20
  const mod = Module as unknown as Record<symbol, HookMap>;
@@ -119,6 +121,8 @@ function retryPatch(moduleName: string, hook: HookFn) {
119
121
  }
120
122
 
121
123
  export const hookRequire = (moduleName: string, onRequire: HookFn) => {
124
+ if (!Module || !safeRequire) return;
125
+
122
126
  const registry = getHookRegistry();
123
127
 
124
128
  if (!registry.has(moduleName)) {
@@ -1,6 +1,3 @@
1
- import http from 'http';
2
- import https from 'https';
3
- import { URL } from 'url';
4
1
  import type { SenzorClient } from '../core/client';
5
2
  import { Context } from '../core/context';
6
3
  import { getRoute, normalizePath } from '../core/normalizer';
@@ -299,7 +296,7 @@ const patchIncomingServer = (
299
296
  };
300
297
 
301
298
  const patchOutgoing = (
302
- moduleRef: typeof http | typeof https,
299
+ moduleRef: any,
303
300
  protocol: 'http:' | 'https:',
304
301
  ingestUrl: string,
305
302
  options?: SenzorOptions
@@ -522,9 +519,19 @@ export const instrumentHttp = (
522
519
  ingestUrl: string,
523
520
  options?: SenzorOptions
524
521
  ) => {
525
- patchIncomingServer(http.Server?.prototype, 'http', client, options);
526
- patchIncomingServer(https.Server?.prototype, 'https', client, options);
522
+ let httpMod: any;
523
+ let httpsMod: any;
527
524
 
528
- patchOutgoing(http, 'http:', ingestUrl, options);
529
- patchOutgoing(https, 'https:', ingestUrl, options);
525
+ try { httpMod = require('http'); } catch { return; }
526
+ try { httpsMod = require('https'); } catch {}
527
+
528
+ if (httpMod?.Server?.prototype) {
529
+ patchIncomingServer(httpMod.Server.prototype, 'http', client, options);
530
+ }
531
+ if (httpsMod?.Server?.prototype) {
532
+ patchIncomingServer(httpsMod.Server.prototype, 'https', client, options);
533
+ }
534
+
535
+ patchOutgoing(httpMod, 'http:', ingestUrl, options);
536
+ if (httpsMod) patchOutgoing(httpsMod, 'https:', ingestUrl, options);
530
537
  };
package/src/register.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { client } from './core/client';
2
+ import { getEnv } from './core/runtime';
2
3
 
3
4
  const truthy = (value: string | undefined): boolean =>
4
5
  value === '1' || value === 'true' || value === 'yes';
@@ -10,43 +11,43 @@ const numberFromEnv = (value: string | undefined): number | undefined => {
10
11
  };
11
12
 
12
13
  const apiKey =
13
- process.env.SENZOR_API_KEY ||
14
- process.env.SENZOR_APM_API_KEY ||
15
- process.env.SENZOR_SERVICE_API_KEY;
14
+ getEnv('SENZOR_API_KEY') ||
15
+ getEnv('SENZOR_APM_API_KEY') ||
16
+ getEnv('SENZOR_SERVICE_API_KEY');
16
17
 
17
18
  const endpoint =
18
- process.env.SENZOR_ENDPOINT ||
19
- process.env.SENZOR_APM_ENDPOINT;
19
+ getEnv('SENZOR_ENDPOINT') ||
20
+ getEnv('SENZOR_APM_ENDPOINT');
20
21
 
21
22
  const options = {
22
23
  apiKey: apiKey || '',
23
24
  endpoint,
24
- debug: truthy(process.env.SENZOR_DEBUG),
25
- autoLogs: process.env.SENZOR_AUTO_LOGS === 'false' ? false : undefined,
26
- batchSize: numberFromEnv(process.env.SENZOR_BATCH_SIZE),
27
- flushInterval: numberFromEnv(process.env.SENZOR_FLUSH_INTERVAL),
28
- flushTimeoutMs: numberFromEnv(process.env.SENZOR_FLUSH_TIMEOUT_MS),
29
- maxQueueSize: numberFromEnv(process.env.SENZOR_MAX_QUEUE_SIZE),
30
- maxSpansPerTrace: numberFromEnv(process.env.SENZOR_MAX_SPANS_PER_TRACE),
31
- captureHeaders: truthy(process.env.SENZOR_CAPTURE_HEADERS),
25
+ debug: truthy(getEnv('SENZOR_DEBUG')),
26
+ autoLogs: getEnv('SENZOR_AUTO_LOGS') === 'false' ? false : undefined,
27
+ batchSize: numberFromEnv(getEnv('SENZOR_BATCH_SIZE')),
28
+ flushInterval: numberFromEnv(getEnv('SENZOR_FLUSH_INTERVAL')),
29
+ flushTimeoutMs: numberFromEnv(getEnv('SENZOR_FLUSH_TIMEOUT_MS')),
30
+ maxQueueSize: numberFromEnv(getEnv('SENZOR_MAX_QUEUE_SIZE')),
31
+ maxSpansPerTrace: numberFromEnv(getEnv('SENZOR_MAX_SPANS_PER_TRACE')),
32
+ captureHeaders: truthy(getEnv('SENZOR_CAPTURE_HEADERS')),
32
33
  captureDbStatement:
33
- process.env.SENZOR_CAPTURE_DB_STATEMENT === 'false'
34
+ getEnv('SENZOR_CAPTURE_DB_STATEMENT') === 'false'
34
35
  ? false
35
36
  : undefined,
36
37
  frameworkSpans:
37
- process.env.SENZOR_FRAMEWORK_SPANS === 'false'
38
+ getEnv('SENZOR_FRAMEWORK_SPANS') === 'false'
38
39
  ? false
39
40
  : undefined,
40
41
  captureMiddlewareSpans:
41
- process.env.SENZOR_CAPTURE_MIDDLEWARE_SPANS === 'false'
42
+ getEnv('SENZOR_CAPTURE_MIDDLEWARE_SPANS') === 'false'
42
43
  ? false
43
44
  : undefined,
44
45
  captureRouterSpans:
45
- process.env.SENZOR_CAPTURE_ROUTER_SPANS === 'false'
46
+ getEnv('SENZOR_CAPTURE_ROUTER_SPANS') === 'false'
46
47
  ? false
47
48
  : undefined,
48
49
  captureLifecycleHookSpans:
49
- process.env.SENZOR_CAPTURE_LIFECYCLE_HOOK_SPANS === 'false'
50
+ getEnv('SENZOR_CAPTURE_LIFECYCLE_HOOK_SPANS') === 'false'
50
51
  ? false
51
52
  : undefined
52
53
  };