@senzops/apm-node 1.1.11 → 1.1.12
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/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/package.json +1 -1
- package/src/core/client.ts +23 -3
- package/src/core/types.ts +10 -0
- package/src/instrumentation/bullmq.ts +29 -4
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/transport.ts","../src/core/context.ts","../src/core/client.ts","../src/instrumentation/http.ts","../src/instrumentation/mongo.ts","../src/instrumentation/pg.ts","../src/instrumentation/hook.ts","../src/instrumentation/bullmq.ts","../src/instrumentation/cron.ts","../src/middleware/express.ts","../src/core/normalizer.ts","../src/wrappers/h3.ts","../src/wrappers/next.ts","../src/wrappers/fastify.ts","../src/index.ts"],"sourcesContent":["import { SenzorOptions, Trace, TaskRun, SenzorError } from './types';\r\n\r\nexport class Transport {\r\n private traceQueue: Trace[] = [];\r\n private apmErrorQueue: SenzorError[] = [];\r\n\r\n private taskQueue: TaskRun[] = [];\r\n private taskErrorQueue: SenzorError[] = [];\r\n\r\n private timer: NodeJS.Timeout | null = null;\r\n private apmEndpoint: string;\r\n private taskEndpoint: string;\r\n\r\n constructor(private config: SenzorOptions) {\r\n const baseEndpoint = config.endpoint || 'https://api.senzor.dev';\r\n // Support legacy full URLs or base URLs\r\n this.apmEndpoint = baseEndpoint.includes('/api/ingest') ? baseEndpoint : `${baseEndpoint}/api/ingest/apm`;\r\n this.taskEndpoint = baseEndpoint.includes('/api/ingest') ? baseEndpoint.replace('/apm', '/task') : `${baseEndpoint}/api/ingest/task`;\r\n\r\n if (typeof setInterval !== 'undefined') {\r\n this.timer = setInterval(() => this.flush(), config.flushInterval || 10000);\r\n if (this.timer && typeof this.timer.unref === 'function') {\r\n this.timer.unref();\r\n }\r\n }\r\n }\r\n\r\n public addTrace(trace: any) {\r\n this.traceQueue.push(trace);\r\n this.checkFlush();\r\n }\r\n\r\n public addTask(task: TaskRun) {\r\n this.taskQueue.push(task);\r\n this.checkFlush();\r\n }\r\n\r\n public addError(error: SenzorError, type: 'apm' | 'task' = 'apm') {\r\n if (type === 'task') this.taskErrorQueue.push(error);\r\n else this.apmErrorQueue.push(error);\r\n this.checkFlush();\r\n }\r\n\r\n private checkFlush() {\r\n const totalApm = this.traceQueue.length + this.apmErrorQueue.length;\r\n const totalTask = this.taskQueue.length + this.taskErrorQueue.length;\r\n if (totalApm >= (this.config.batchSize || 100) || totalTask >= (this.config.batchSize || 100)) {\r\n this.flush();\r\n }\r\n }\r\n\r\n public async flush() {\r\n const apmPayload = { traces: [...this.traceQueue], errors: [...this.apmErrorQueue] };\r\n const taskPayload = { runs: [...this.taskQueue], errors: [...this.taskErrorQueue] };\r\n\r\n this.traceQueue = [];\r\n this.apmErrorQueue = [];\r\n this.taskQueue = [];\r\n this.taskErrorQueue = [];\r\n\r\n const headers = { 'Content-Type': 'application/json', 'x-service-api-key': this.config.apiKey };\r\n\r\n try {\r\n const promises = [];\r\n\r\n if (apmPayload.traces.length > 0 || apmPayload.errors.length > 0) {\r\n promises.push(fetch(this.apmEndpoint, { method: 'POST', headers, body: JSON.stringify(apmPayload), keepalive: true }));\r\n }\r\n\r\n if (taskPayload.runs.length > 0 || taskPayload.errors.length > 0) {\r\n promises.push(fetch(this.taskEndpoint, { method: 'POST', headers, body: JSON.stringify(taskPayload), keepalive: true }));\r\n }\r\n\r\n await Promise.allSettled(promises);\r\n\r\n if (this.config.debug) {\r\n console.log(`[Senzor] Flushed: ${apmPayload.traces.length} traces, ${taskPayload.runs.length} tasks`);\r\n }\r\n } catch (err) {\r\n if (this.config.debug) console.error('[Senzor] Transport Flush Error:', err);\r\n }\r\n }\r\n}","import { AsyncLocalStorage } from 'async_hooks';\r\nimport { ActiveTrace } from './types';\r\n\r\nexport const storage = new AsyncLocalStorage<ActiveTrace>();\r\n\r\nexport const Context = {\r\n run: <T>(trace: ActiveTrace, fn: () => T): T => {\r\n return storage.run(trace, fn);\r\n },\r\n\r\n current: (): ActiveTrace | undefined => {\r\n return storage.getStore();\r\n },\r\n\r\n addSpan: (span: any) => {\r\n const store = storage.getStore();\r\n if (store) {\r\n store.spans.push(span);\r\n }\r\n }\r\n};","import { Transport } from './transport';\r\nimport { Context } from './context';\r\nimport { SenzorOptions, ActiveTrace, TaskRun } from './types';\r\nimport { randomUUID } from 'crypto';\r\nimport { instrumentHttp, instrumentFetch } from '../instrumentation/http';\r\nimport { instrumentMongo } from '../instrumentation/mongo';\r\nimport { instrumentPg } from '../instrumentation/pg';\r\nimport { instrumentBullMQ } from '../instrumentation/bullmq';\r\nimport { instrumentNodeCron } from '../instrumentation/cron';\r\n\r\nexport class SenzorClient {\r\n private transport: Transport | null = null;\r\n private options: SenzorOptions | null = null;\r\n private isInstrumented = false;\r\n\r\n public init(options: SenzorOptions) {\r\n if (!options.apiKey) {\r\n console.warn('[Senzor] API Key missing. SDK disabled.');\r\n return;\r\n }\r\n this.options = options;\r\n const endpoint = options.endpoint || 'https://api.senzor.dev/api/ingest/apm';\r\n const debug = options.debug || false;\r\n\r\n this.transport = new Transport({ ...options, endpoint });\r\n\r\n if (!this.isInstrumented) {\r\n this.setupGlobalErrorHandlers();\r\n\r\n try { instrumentHttp(endpoint, debug); } catch (e) { }\r\n try { instrumentFetch(endpoint, debug); } catch (e) { }\r\n try { instrumentMongo(debug); } catch (e) { }\r\n try { instrumentPg(); } catch (e) { }\r\n\r\n // Task Integrations (NEW)\r\n try { instrumentBullMQ(this, debug); } catch (e) { }\r\n try { instrumentNodeCron(this, debug); } catch (e) { }\r\n\r\n this.isInstrumented = true;\r\n if (debug) console.log('[Senzor] Auto-instrumentation & Error Tracking enabled');\r\n }\r\n }\r\n\r\n private setupGlobalErrorHandlers() {\r\n process.on('uncaughtException', (error) => {\r\n this.captureError(error, { type: 'uncaughtException' });\r\n });\r\n\r\n process.on('unhandledRejection', (reason) => {\r\n this.captureError(reason, { type: 'unhandledRejection' });\r\n });\r\n }\r\n\r\n public startTrace<T>(data: Partial<ActiveTrace['data']> & { headers?: any }, next: () => T): T {\r\n if (!this.transport) return next();\r\n\r\n let parentTraceId = undefined;\r\n let parentSpanId = undefined;\r\n\r\n if (data.headers) {\r\n const getHeader = (key: string) => {\r\n if (data.headers[key]) return data.headers[key];\r\n if (data.headers[key.toLowerCase()]) return data.headers[key.toLowerCase()];\r\n return undefined;\r\n };\r\n\r\n parentTraceId = getHeader('x-senzor-trace-id');\r\n parentSpanId = getHeader('x-senzor-parent-span-id');\r\n\r\n if (Array.isArray(parentTraceId)) parentTraceId = parentTraceId[0];\r\n if (Array.isArray(parentSpanId)) parentSpanId = parentSpanId[0];\r\n }\r\n\r\n const trace: ActiveTrace = {\r\n id: randomUUID(),\r\n contextType: 'apm', // Ensure we distinguish APM traces from Background Tasks\r\n startTime: performance.now(),\r\n data: {\r\n ...data,\r\n parentTraceId,\r\n parentSpanId\r\n },\r\n spans: []\r\n };\r\n\r\n return Context.run(trace, next);\r\n }\r\n\r\n public endTrace(status: number, extraData: any = {}) {\r\n const trace = Context.current();\r\n if (!trace || trace.contextType !== 'apm' || !this.transport) return;\r\n const duration = performance.now() - trace.startTime;\r\n\r\n const payload = {\r\n traceId: trace.id,\r\n parentTraceId: trace.data.parentTraceId,\r\n parentSpanId: trace.data.parentSpanId,\r\n ...trace.data,\r\n ...extraData,\r\n status, duration, spans: trace.spans, timestamp: new Date().toISOString()\r\n };\r\n this.transport.addTrace(payload);\r\n }\r\n\r\n // --- NEW: TASK MONITORING METHODS ---\r\n public startTask<T>(name: string, type: 'cron' | 'queue' | 'pipeline' | 'custom', options: any, next: () => T): T {\r\n if (!this.transport) return next();\r\n\r\n // Distributed Tracing: If an APM trace spawns this task (e.g. queueing a job inside an API)\r\n const currentContext = Context.current();\r\n const triggerTraceId = currentContext?.contextType === 'apm' ? currentContext.id : undefined;\r\n\r\n const task: ActiveTrace = {\r\n id: randomUUID(),\r\n contextType: 'task',\r\n startTime: performance.now(),\r\n data: { taskName: name, taskType: type, triggerTraceId, ...options },\r\n spans: []\r\n };\r\n return Context.run(task, next);\r\n }\r\n\r\n public endTask(status: 'success' | 'failed', extraMetadata: any = {}) {\r\n const task = Context.current();\r\n if (!task || task.contextType !== 'task' || !this.transport) return;\r\n\r\n const payload: TaskRun = {\r\n runId: task.id,\r\n taskName: task.data.taskName,\r\n taskType: task.data.taskType,\r\n triggerTraceId: task.data.triggerTraceId,\r\n queueDelay: task.data.queueDelay,\r\n attempts: task.data.attempts,\r\n metadata: { ...task.data.metadata, ...extraMetadata },\r\n status,\r\n duration: performance.now() - task.startTime,\r\n spans: task.spans,\r\n timestamp: new Date().toISOString()\r\n };\r\n // addTask relies on the new task Queue array in your transport.ts update\r\n this.transport.addTask(payload);\r\n }\r\n\r\n public wrapTask<T extends (...args: any[]) => any>(name: string, type: 'cron' | 'queue' | 'pipeline' | 'custom', options: any = {}, fn: T): T {\r\n return (async (...args: any[]) => {\r\n return this.startTask(name, type, options, async () => {\r\n try {\r\n const result = await fn(...args);\r\n this.endTask('success');\r\n return result;\r\n } catch (error) {\r\n this.captureError(error, { taskName: name });\r\n this.endTask('failed');\r\n throw error;\r\n }\r\n });\r\n }) as unknown as T;\r\n }\r\n\r\n // --- MODIFIED: Context-Aware Error Capture ---\r\n public captureError(error: unknown, context: any = {}) {\r\n if (!this.transport) return;\r\n\r\n let parsedError: Error;\r\n if (error instanceof Error) {\r\n parsedError = error;\r\n } else {\r\n parsedError = new Error(String(error));\r\n }\r\n\r\n const currentTrace = Context.current();\r\n\r\n const errPayload = {\r\n errorClass: parsedError.name || 'Error',\r\n message: parsedError.message,\r\n stackTrace: parsedError.stack,\r\n context,\r\n timestamp: new Date().toISOString()\r\n };\r\n\r\n if (currentTrace?.contextType === 'task') {\r\n this.transport.addError({ ...errPayload, runId: currentTrace.id }, 'task');\r\n } else {\r\n this.transport.addError({ ...errPayload, traceId: currentTrace?.id }, 'apm');\r\n }\r\n }\r\n\r\n public track(data: any) {\r\n this.transport?.addTrace({ traceId: randomUUID(), ...data, spans: [], timestamp: new Date().toISOString() });\r\n }\r\n\r\n public startSpan(name: string, type: 'db' | 'http' | 'function' | 'custom' = 'custom') {\r\n const trace = Context.current();\r\n if (!trace) return { end: () => { } };\r\n const startTime = performance.now() - trace.startTime;\r\n const spanStartAbs = performance.now();\r\n const spanId = randomUUID();\r\n return { end: (meta?: any, status?: number) => { Context.addSpan({ spanId, name, type, startTime, duration: performance.now() - spanStartAbs, status, meta }); } };\r\n }\r\n\r\n public async flush() { if (this.transport) await this.transport.flush(); }\r\n}\r\n\r\nexport const client = new SenzorClient();","import http from 'http';\r\nimport https from 'https';\r\nimport { URL } from 'url';\r\nimport { Context } from '../core/context';\r\nimport { randomUUID } from 'crypto';\r\n\r\nconst shimmer = (module: any, methodName: string, wrapper: (original: Function) => Function) => {\r\n if (!module[methodName]) return;\r\n const original = module[methodName];\r\n module[methodName] = wrapper(original);\r\n};\r\n\r\n// --- FETCH INSTRUMENTATION ---\r\nexport const instrumentFetch = (ingestUrl: string, debug = false) => {\r\n if (!globalThis.fetch) return;\r\n\r\n let ingestHost = '';\r\n try { ingestHost = new URL(ingestUrl).hostname; } catch (e) { }\r\n\r\n const originalFetch = globalThis.fetch;\r\n\r\n // @ts-ignore\r\n globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {\r\n let urlStr = '';\r\n if (typeof input === 'string') urlStr = input;\r\n else if (input instanceof URL) urlStr = input.toString();\r\n else if (input && (input as any).url) urlStr = (input as any).url;\r\n\r\n if (ingestHost && urlStr.includes(ingestHost)) {\r\n return originalFetch(input, init);\r\n }\r\n\r\n const trace = Context.current();\r\n if (!trace) {\r\n return originalFetch(input, init);\r\n }\r\n\r\n const method = (init?.method || 'GET').toUpperCase();\r\n const startTime = performance.now() - trace.startTime;\r\n const spanStartAbs = performance.now();\r\n const spanId = randomUUID();\r\n\r\n let hostname = 'unknown';\r\n try { hostname = new URL(urlStr).hostname; } catch (e) { }\r\n\r\n // Inject Headers\r\n const newInit = { ...init } as RequestInit;\r\n if (!newInit.headers) newInit.headers = {};\r\n\r\n // Helper to set header on various types\r\n const setHeader = (key: string, value: string) => {\r\n if (newInit.headers instanceof Headers) {\r\n newInit.headers.set(key, value);\r\n } else if (Array.isArray(newInit.headers)) {\r\n newInit.headers.push([key, value]);\r\n } else {\r\n (newInit.headers as any)[key] = value;\r\n }\r\n };\r\n\r\n setHeader('x-senzor-trace-id', trace.id);\r\n setHeader('x-senzor-parent-span-id', spanId);\r\n\r\n try {\r\n const response = await originalFetch(input, newInit);\r\n\r\n const duration = performance.now() - spanStartAbs;\r\n Context.addSpan({\r\n spanId,\r\n name: `${method} ${hostname}`,\r\n type: 'http',\r\n startTime,\r\n duration,\r\n status: response.status,\r\n meta: { url: urlStr, method, library: 'fetch' }\r\n });\r\n\r\n return response;\r\n } catch (err: any) {\r\n const duration = performance.now() - spanStartAbs;\r\n Context.addSpan({\r\n spanId,\r\n name: `${method} ${hostname}`,\r\n type: 'http',\r\n startTime,\r\n duration,\r\n status: 500,\r\n meta: { error: err.message, url: urlStr, library: 'fetch' }\r\n });\r\n throw err;\r\n }\r\n };\r\n};\r\n\r\n// --- HTTP/HTTPS INSTRUMENTATION ---\r\nexport const instrumentHttp = (ingestUrl: string, debug = false) => {\r\n let ingestHost = '';\r\n try { ingestHost = new URL(ingestUrl).hostname; } catch (e) { }\r\n\r\n const requestWrapper = (original: Function) => {\r\n return function (this: any, ...args: any[]) {\r\n let options: any = {};\r\n let urlStr = '';\r\n let optionsIndex = 0;\r\n\r\n // Parsing Logic: http.request(url, options, cb) OR http.request(options, cb)\r\n if (typeof args[0] === 'string' || args[0] instanceof URL) {\r\n urlStr = args[0].toString();\r\n optionsIndex = 1;\r\n } else {\r\n optionsIndex = 0;\r\n }\r\n\r\n // Ensure options object exists at correct index\r\n if (!args[optionsIndex] || typeof args[optionsIndex] !== 'object') {\r\n args[optionsIndex] = {};\r\n }\r\n options = args[optionsIndex];\r\n\r\n // Construct URL if missing\r\n if (!urlStr) {\r\n const protocol = options.protocol || (options.port === 443 ? 'https:' : 'http:');\r\n const host = options.hostname || options.host || 'localhost';\r\n const path = options.path || '/';\r\n urlStr = `${protocol}//${host}${path}`;\r\n }\r\n\r\n // Guard\r\n if (ingestHost && (urlStr.includes(ingestHost) || (options.hostname && options.hostname.includes(ingestHost)))) {\r\n return original.apply(this, args);\r\n }\r\n\r\n const trace = Context.current();\r\n if (!trace) return original.apply(this, args);\r\n\r\n const method = (options.method || 'GET').toUpperCase();\r\n const startTime = performance.now() - trace.startTime;\r\n const spanStartAbs = performance.now();\r\n const spanId = randomUUID();\r\n\r\n let hostname = 'unknown';\r\n try { hostname = new URL(urlStr).hostname; } catch (e) { hostname = options.hostname || 'unknown'; }\r\n\r\n // Inject Headers (Mutate the options object reference directly)\r\n if (!options.headers) options.headers = {};\r\n options.headers['x-senzor-trace-id'] = trace.id;\r\n options.headers['x-senzor-parent-span-id'] = spanId;\r\n\r\n // Debug\r\n if (debug) console.log(`[Senzor] Injecting headers to ${urlStr}`);\r\n\r\n // Call Original\r\n const req = original.apply(this, args);\r\n\r\n const captureSpan = (res: any, error?: Error) => {\r\n const duration = performance.now() - spanStartAbs;\r\n Context.addSpan({\r\n spanId,\r\n name: `${method} ${hostname}`,\r\n type: 'http',\r\n startTime,\r\n duration,\r\n status: error ? 500 : res?.statusCode || 0,\r\n meta: { url: urlStr, method, library: 'http' }\r\n });\r\n };\r\n\r\n req.on('response', (res: any) => {\r\n res.once('end', () => captureSpan(res));\r\n res.once('close', () => captureSpan(res));\r\n res.once('error', (err: Error) => captureSpan(res, err));\r\n });\r\n\r\n req.on('error', (err: Error) => captureSpan(null, err));\r\n\r\n return req;\r\n };\r\n };\r\n\r\n shimmer(http, 'request', requestWrapper);\r\n shimmer(http, 'get', requestWrapper);\r\n shimmer(https, 'request', requestWrapper);\r\n shimmer(https, 'get', requestWrapper);\r\n};","import { Context } from '../core/context';\r\n\r\nexport const instrumentMongo = (debug = false) => {\r\n try {\r\n const mongodb = require('mongodb');\r\n const Collection = mongodb.Collection;\r\n\r\n // Attempt to get Cursor classes\r\n // Note: The location of these classes varies by driver version, \r\n // checking common locations\r\n const FindCursor = mongodb.FindCursor || require('mongodb/lib/cursor/find_cursor').FindCursor;\r\n const AggregationCursor = mongodb.AggregationCursor || require('mongodb/lib/cursor/aggregation_cursor').AggregationCursor;\r\n\r\n if (debug) console.log('[Senzor] Instrumenting MongoDB (Collection + Cursors)...');\r\n\r\n // --- Helper to Record Span ---\r\n const recordSpan = (name: string, operation: string, collection: string, startAbs: number, traceStart: number, err?: Error) => {\r\n const duration = performance.now() - startAbs;\r\n Context.addSpan({\r\n name: `MongoDB ${name}`,\r\n type: 'db',\r\n startTime: performance.now() - traceStart - duration, // Adjust start time to when op actually started\r\n duration,\r\n status: err ? 500 : 0,\r\n meta: { collection, operation, error: err ? err.message : undefined }\r\n });\r\n if (debug) console.log(`[Senzor] Captured Mongo: ${name} (${duration.toFixed(2)}ms)`);\r\n };\r\n\r\n // --- 1. Instrument Immediate Operations (Insert/Update/Delete) ---\r\n const immediateMethods = ['insertOne', 'insertMany', 'updateOne', 'updateMany', 'deleteOne', 'deleteMany', 'countDocuments'];\r\n\r\n immediateMethods.forEach((method) => {\r\n if (!Collection.prototype[method]) return;\r\n const original = Collection.prototype[method];\r\n\r\n Collection.prototype[method] = function (...args: any[]) {\r\n const trace = Context.current();\r\n if (!trace) return original.apply(this, args);\r\n\r\n const spanStartAbs = performance.now();\r\n const traceStart = trace.startTime;\r\n const collName = this.collectionName;\r\n\r\n try {\r\n const result = original.apply(this, args);\r\n if (result && typeof result.then === 'function') {\r\n return result.then(\r\n (res: any) => { recordSpan(method, method, collName, spanStartAbs, traceStart); return res; },\r\n (err: any) => { recordSpan(method, method, collName, spanStartAbs, traceStart, err); throw err; }\r\n );\r\n }\r\n return result;\r\n } catch (err: any) {\r\n recordSpan(method, method, collName, spanStartAbs, traceStart, err);\r\n throw err;\r\n }\r\n };\r\n });\r\n\r\n // --- 2. Instrument Cursor Execution (find -> toArray) ---\r\n const patchCursor = (CursorClass: any, label: string) => {\r\n if (!CursorClass || !CursorClass.prototype.toArray) return;\r\n\r\n const originalToArray = CursorClass.prototype.toArray;\r\n\r\n CursorClass.prototype.toArray = function (...args: any[]) {\r\n const trace = Context.current();\r\n // Cursors are often created in context but executed later. \r\n // We check context at execution time.\r\n if (!trace) return originalToArray.apply(this, args);\r\n\r\n const spanStartAbs = performance.now();\r\n const traceStart = trace.startTime;\r\n // Attempt to get collection name from cursor internal state\r\n const collName = this.namespace?.collection || 'unknown';\r\n\r\n const onSuccess = (res: any) => {\r\n recordSpan(label, label, collName, spanStartAbs, traceStart);\r\n return res;\r\n };\r\n const onError = (err: any) => {\r\n recordSpan(label, label, collName, spanStartAbs, traceStart, err);\r\n throw err;\r\n };\r\n\r\n try {\r\n const result = originalToArray.apply(this, args);\r\n if (result && typeof result.then === 'function') {\r\n return result.then(onSuccess, onError);\r\n }\r\n return onSuccess(result);\r\n } catch (e) {\r\n onError(e);\r\n }\r\n };\r\n };\r\n\r\n patchCursor(FindCursor, 'find');\r\n patchCursor(AggregationCursor, 'aggregate');\r\n\r\n } catch (e: any) {\r\n if (debug) console.warn('[Senzor] MongoDB instrumentation warning:', e.message);\r\n }\r\n};","import { Context } from '../core/context';\r\n\r\n// Simple shim for 'pg' library\r\nexport const instrumentPg = () => {\r\n try {\r\n // Try to require pg (it might not be installed by user)\r\n const pg = require('pg');\r\n const originalQuery = pg.Client.prototype.query;\r\n\r\n pg.Client.prototype.query = function (...args: any[]) {\r\n const trace = Context.current();\r\n if (!trace) return originalQuery.apply(this, args);\r\n\r\n const startTime = performance.now() - trace.startTime;\r\n const spanStartAbs = performance.now();\r\n\r\n // Extract SQL (first arg usually string or config object)\r\n const sql = typeof args[0] === 'string' ? args[0] : args[0].text;\r\n\r\n // Wrap callback if present, or handle Promise\r\n const result = originalQuery.apply(this, args);\r\n\r\n if (result && typeof result.then === 'function') {\r\n return result.then((res: any) => {\r\n const duration = performance.now() - spanStartAbs;\r\n Context.addSpan({\r\n name: 'Postgres Query',\r\n type: 'db',\r\n startTime,\r\n duration,\r\n meta: { query: sql }\r\n });\r\n return res;\r\n });\r\n }\r\n return result;\r\n };\r\n } catch (e) {\r\n // User doesn't use pg, ignore\r\n }\r\n};","import Module from 'module';\r\n\r\nexport const hookRequire = (moduleName: string, onRequire: (exports: any) => void) => {\r\n // 1. If it was already loaded (e.g., imported at the top of the file before init)\r\n try {\r\n const resolvedPath = require.resolve(moduleName);\r\n const cached = require.cache[resolvedPath];\r\n if (cached && cached.exports) {\r\n onRequire(cached.exports);\r\n }\r\n } catch (e) {\r\n // Silently ignore if module is not installed\r\n }\r\n\r\n // 2. Intercept future requires\r\n const originalLoad = (Module as any)._load;\r\n (Module as any)._load = function (request: string, parent: any, isMain: boolean) {\r\n const exports = originalLoad.apply(this, arguments);\r\n if (request === moduleName && exports) {\r\n onRequire(exports);\r\n }\r\n return exports;\r\n };\r\n};","import type { SenzorClient } from '../core/client';\r\nimport { hookRequire } from './hook';\r\n\r\nexport const instrumentBullMQ = (client: SenzorClient, debug: boolean) => {\r\n hookRequire('bullmq', (bullExports) => {\r\n\r\n const patchWorker = (target: any) => {\r\n if (!target || !target.Worker || !target.Worker.prototype.processJob || target.Worker.prototype.processJob.__senzorPatched) return;\r\n\r\n const originalProcessJob = target.Worker.prototype.processJob;\r\n\r\n target.Worker.prototype.processJob = async function (job: any) {\r\n const queueDelay = job.timestamp ? Date.now() - job.timestamp : 0;\r\n const attempts = (job.attemptsMade || 0) + 1;\r\n const taskName = job.name === '__default__' ? job.queueName : `${job.queueName}:${job.name}`;\r\n\r\n return client.startTask(\r\n taskName,\r\n 'queue',\r\n { queueDelay, attempts, metadata: { jobId: job.id, queueName: job.queueName } },\r\n async () => {\r\n try {\r\n const result = await originalProcessJob.apply(this, arguments);\r\n client.endTask('success');\r\n return result;\r\n } catch (error) {\r\n client.captureError(error, { queueName: job.queueName, jobId: job.id });\r\n client.endTask('failed');\r\n throw error;\r\n }\r\n }\r\n );\r\n };\r\n\r\n Object.defineProperty(target.Worker.prototype.processJob, '__senzorPatched', { value: true, enumerable: false, writable: true });\r\n if (debug) console.log('[Senzor] BullMQ Worker successfully instrumented');\r\n };\r\n\r\n patchWorker(bullExports);\r\n\r\n if (bullExports.default) {\r\n patchWorker(bullExports.default);\r\n }\r\n });\r\n};","import type { SenzorClient } from '../core/client';\r\nimport { hookRequire } from './hook';\r\n\r\nexport const instrumentNodeCron = (client: SenzorClient, debug: boolean) => {\r\n hookRequire('node-cron', (cronExports) => {\r\n\r\n // Abstracted patcher so we can apply it to both the root and the .default export\r\n const patchSchedule = (target: any) => {\r\n if (!target || typeof target.schedule !== 'function' || target.__senzorPatched) return;\r\n\r\n const originalSchedule = target.schedule;\r\n\r\n target.schedule = function (expression: string, func: (...args: any[]) => any, options: any) {\r\n // Handle node-cron's dynamic options argument (can be string or object)\r\n const optsObj = typeof options === 'object' ? options : { timezone: options };\r\n const taskName = optsObj?.name || `cron: ${expression}`;\r\n\r\n const wrappedFunc = client.wrapTask(\r\n taskName,\r\n 'cron',\r\n { metadata: optsObj },\r\n func\r\n );\r\n\r\n return originalSchedule.call(this, expression, wrappedFunc, options);\r\n };\r\n\r\n // Safely mark as patched to prevent infinite loops\r\n Object.defineProperty(target, '__senzorPatched', { value: true, enumerable: false, writable: true });\r\n if (debug) console.log('[Senzor] Node-Cron successfully instrumented');\r\n };\r\n\r\n // Apply patch to root (for const cron = require('node-cron'))\r\n patchSchedule(cronExports);\r\n\r\n // Apply patch to default (for import cron from 'node-cron')\r\n if (cronExports.default) {\r\n patchSchedule(cronExports.default);\r\n }\r\n });\r\n};","import { client } from '../core/client';\r\n\r\n// 1. Request Handler (Place before routes)\r\nexport const expressMiddleware = () => {\r\n return (req: any, res: any, next: () => void) => {\r\n client.startTrace({\r\n method: req.method,\r\n path: req.originalUrl || req.url,\r\n ip: req.ip || req.socket?.remoteAddress,\r\n userAgent: req.headers['user-agent'],\r\n headers: req.headers\r\n }, () => {\r\n\r\n // Auto-detect status code on finish\r\n res.once('finish', () => {\r\n try {\r\n let route = 'UNKNOWN';\r\n // Express populates req.route only if a route matched\r\n if (req.route && req.route.path) {\r\n route = (req.baseUrl || '') + req.route.path;\r\n } else if (res.statusCode === 404) {\r\n route = 'Not Found';\r\n } else {\r\n route = req.path || 'Wildcard';\r\n }\r\n\r\n client.endTrace(res.statusCode, { route });\r\n } catch (e) { /* Fail open */ }\r\n });\r\n\r\n next();\r\n });\r\n };\r\n};\r\n\r\n// 2. Error Handler (Place after routes)\r\n// This is required in Express to capture the actual Error Object (Stack Trace)\r\nexport const expressErrorHandler = () => {\r\n return (err: any, req: any, res: any, next: (err?: any) => void) => {\r\n\r\n // 1. Capture the exception context\r\n client.captureError(err);\r\n\r\n // 2. Pass it to the next error handler (don't swallow it)\r\n next(err);\r\n };\r\n};","/**\r\n * Heuristic URL Normalizer\r\n * Converts raw paths with IDs into generic patterns to prevent high cardinality.\r\n * Example: /users/123/orders/abc-def -> /users/:id/orders/:uuid\r\n */\r\nexport const normalizePath = (path: string): string => {\r\n if (!path || path === '/') return '/';\r\n\r\n return path\r\n // Replace UUIDs (long alphanumeric strings)\r\n .replace(\r\n /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g,\r\n ':uuid'\r\n )\r\n // Replace MongoDB ObjectIds (24 hex chars)\r\n .replace(/[0-9a-fA-F]{24}/g, ':objectId')\r\n // Replace pure numeric IDs (e.g., /123)\r\n .replace(/\\/(\\d+)(?=\\/|$)/g, '/:id')\r\n // Remove query strings\r\n .split('?')[0];\r\n};\r\n\r\n/**\r\n * Tries to extract route from Framework internals, falls back to heuristic\r\n */\r\nexport const getRoute = (req: any, fallbackPath: string): string => {\r\n // Express / Connect\r\n if (req.route && req.route.path) {\r\n return (req.baseUrl || '') + req.route.path;\r\n }\r\n\r\n // H3 / Nitro (Nuxt)\r\n if (req.context && req.context.matchedRoute) {\r\n return req.context.matchedRoute.path;\r\n }\r\n\r\n // Fastify\r\n if (req.routerPath) {\r\n return req.routerPath;\r\n }\r\n\r\n // Fallback: Heuristic Normalization\r\n return normalizePath(fallbackPath);\r\n};","import { client } from '../core/client';\r\nimport { getRoute } from '../core/normalizer';\r\n\r\ntype EventHandler = (event: any) => any;\r\n\r\nexport const wrapH3 = (handler: EventHandler) => {\r\n return (event: any) => {\r\n const req = event.node.req;\r\n const path = req.originalUrl || req.url || '/';\r\n\r\n return client.startTrace({\r\n method: req.method || 'GET',\r\n path: path,\r\n ip: req.headers['x-forwarded-for'] || req.socket?.remoteAddress,\r\n userAgent: req.headers['user-agent'],\r\n headers: req.headers // Pass headers\r\n }, async () => {\r\n try {\r\n const response = await handler(event);\r\n let status = 200;\r\n if (event.node.res.statusCode) status = event.node.res.statusCode;\r\n if (response && response.statusCode) status = response.statusCode;\r\n\r\n client.endTrace(status, { route: getRoute(event, path) });\r\n return response;\r\n } catch (err: any) {\r\n client.captureError(err);\r\n const status = err.statusCode || err.status || 500;\r\n client.endTrace(status, { route: getRoute(event, path) });\r\n throw err;\r\n }\r\n });\r\n };\r\n};","import { client } from '../core/client';\r\nimport { normalizePath } from '../core/normalizer';\r\n\r\n// --- App Router Wrapper ---\r\nexport const wrapNextRoute = (handler: Function) => {\r\n return async (req: Request | any, context?: any) => {\r\n\r\n // Extract info from Web Standard Request\r\n const url = req.url ? new URL(req.url) : { pathname: '/' };\r\n const method = req.method || 'GET';\r\n\r\n // Header Extraction\r\n let headers: Record<string, string> = {};\r\n let ua: string | undefined;\r\n let ip: string | undefined;\r\n\r\n if (typeof req.headers.get === 'function') {\r\n // It's a Web Request Object\r\n ua = req.headers.get('user-agent');\r\n ip = req.headers.get('x-forwarded-for');\r\n\r\n // Convert to plain object for trace context extraction\r\n req.headers.forEach((value: string, key: string) => {\r\n headers[key] = value;\r\n });\r\n } else {\r\n // It's a Node Request Object (rare in App router but possible)\r\n headers = req.headers;\r\n ua = headers['user-agent'];\r\n ip = headers['x-forwarded-for'] as string;\r\n }\r\n\r\n return client.startTrace({\r\n method,\r\n path: url.pathname,\r\n userAgent: ua,\r\n ip: ip,\r\n headers: headers // Pass extracted headers\r\n }, async () => {\r\n try {\r\n const response = await handler(req, context);\r\n const status = response?.status || 200;\r\n\r\n client.endTrace(status, { route: normalizePath(url.pathname) });\r\n return response;\r\n } catch (err: any) {\r\n client.captureError(err);\r\n client.endTrace(500, { route: normalizePath(url.pathname) });\r\n throw err;\r\n }\r\n });\r\n };\r\n};\r\n\r\n// --- Pages Router Wrapper ---\r\nexport const wrapNextPages = (handler: Function) => {\r\n return async (req: any, res: any) => {\r\n const path = req.url ? req.url.split('?')[0] : '/';\r\n\r\n return client.startTrace({\r\n method: req.method || 'GET',\r\n path: path,\r\n userAgent: req.headers['user-agent'],\r\n ip: req.headers['x-forwarded-for'] || req.socket?.remoteAddress,\r\n headers: req.headers // Standard Node headers work fine\r\n }, async () => {\r\n\r\n const done = () => {\r\n client.endTrace(res.statusCode || 200, { route: normalizePath(path) });\r\n };\r\n\r\n res.once('finish', done);\r\n res.once('close', done);\r\n\r\n try {\r\n return await handler(req, res);\r\n } catch (e: any) {\r\n client.captureError(e);\r\n throw e;\r\n }\r\n });\r\n };\r\n};","import { client } from '../core/client';\r\nimport { SenzorOptions } from '../core/types';\r\n\r\nexport const senzorPlugin = (fastify: any, options: SenzorOptions, done: Function) => {\r\n if (options && options.apiKey) {\r\n client.init(options);\r\n }\r\n\r\n fastify.addHook('onRequest', (request: any, reply: any, next: Function) => {\r\n client.startTrace({\r\n method: request.method,\r\n path: request.raw.url || request.url,\r\n ip: request.ip,\r\n userAgent: request.headers['user-agent'],\r\n headers: request.headers // Pass headers\r\n }, () => next());\r\n });\r\n\r\n fastify.addHook('onError', (request: any, reply: any, error: any, next: Function) => {\r\n client.captureError(error);\r\n next();\r\n });\r\n\r\n fastify.addHook('onResponse', (request: any, reply: any, next: Function) => {\r\n const route = request.routeOptions?.url || request.routerPath || 'UNKNOWN';\r\n client.endTrace(reply.statusCode, { route });\r\n next();\r\n });\r\n\r\n done();\r\n};","import { client } from './core/client';\r\nimport { expressMiddleware, expressErrorHandler } from './middleware/express';\r\nimport { wrapH3 } from './wrappers/h3';\r\nimport { wrapNextRoute, wrapNextPages } from './wrappers/next';\r\nimport { senzorPlugin } from './wrappers/fastify';\r\nimport { SenzorOptions } from './core/types';\r\n\r\nconst Senzor = {\r\n init: (options: SenzorOptions) => client.init(options),\r\n flush: () => client.flush(),\r\n track: client.track.bind(client),\r\n startSpan: client.startSpan.bind(client),\r\n captureException: client.captureError.bind(client),\r\n\r\n // Task Monitoring (NEW)\r\n wrapTask: client.wrapTask.bind(client),\r\n startTask: client.startTask.bind(client),\r\n\r\n // Express\r\n requestHandler: expressMiddleware,\r\n errorHandler: expressErrorHandler,\r\n\r\n // Next\r\n wrapNextRoute,\r\n wrapNextPages,\r\n\r\n // H3\r\n wrapH3,\r\n\r\n // Fastify\r\n fastifyPlugin: senzorPlugin\r\n};\r\n\r\nexport default Senzor;\r\nexport { Senzor };"],"mappings":"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","names":["Transport","config","baseEndpoint","trace","task","error","type","totalApm","totalTask","apmPayload","taskPayload","headers","promises","err","AsyncLocalStorage","storage","Context","trace","fn","span","store","randomUUID","http","https","URL","randomUUID","shimmer","module","methodName","wrapper","original","instrumentFetch","ingestUrl","debug","ingestHost","URL","originalFetch","input","init","urlStr","trace","Context","method","startTime","spanStartAbs","spanId","hostname","newInit","setHeader","key","value","response","duration","err","instrumentHttp","requestWrapper","args","options","optionsIndex","protocol","host","path","req","captureSpan","res","error","http","https","instrumentMongo","debug","mongodb","Collection","FindCursor","AggregationCursor","recordSpan","name","operation","collection","startAbs","traceStart","err","duration","Context","method","original","args","trace","spanStartAbs","collName","result","res","patchCursor","CursorClass","label","originalToArray","onSuccess","onError","e","instrumentPg","pg","originalQuery","args","trace","Context","startTime","spanStartAbs","sql","result","res","duration","Module","hookRequire","moduleName","onRequire","resolvedPath","__require","cached","originalLoad","Module","request","parent","isMain","exports","instrumentBullMQ","client","debug","hookRequire","bullExports","patchWorker","target","originalProcessJob","job","queueDelay","attempts","taskName","result","error","instrumentNodeCron","client","debug","hookRequire","cronExports","patchSchedule","target","originalSchedule","expression","func","options","optsObj","taskName","wrappedFunc","SenzorClient","options","endpoint","debug","Transport","instrumentHttp","instrumentFetch","instrumentMongo","instrumentPg","instrumentBullMQ","instrumentNodeCron","error","reason","data","next","parentTraceId","parentSpanId","getHeader","key","trace","randomUUID","Context","status","extraData","duration","payload","name","type","currentContext","triggerTraceId","task","extraMetadata","fn","args","result","context","parsedError","currentTrace","errPayload","startTime","spanStartAbs","spanId","meta","client","expressMiddleware","req","res","next","client","route","expressErrorHandler","err","normalizePath","path","getRoute","req","fallbackPath","wrapH3","handler","event","req","path","client","response","status","getRoute","err","wrapNextRoute","handler","req","context","url","method","headers","ua","ip","value","key","client","response","status","normalizePath","err","wrapNextPages","res","path","done","e","senzorPlugin","fastify","options","done","client","request","reply","next","error","route","Senzor","options","client","expressMiddleware","expressErrorHandler","wrapNextRoute","wrapNextPages","wrapH3","senzorPlugin","index_default"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/context.ts","../src/core/transport.ts","../src/core/client.ts","../src/instrumentation/http.ts","../src/instrumentation/mongo.ts","../src/instrumentation/pg.ts","../src/instrumentation/hook.ts","../src/instrumentation/bullmq.ts","../src/instrumentation/cron.ts","../src/middleware/express.ts","../src/core/normalizer.ts","../src/wrappers/h3.ts","../src/wrappers/next.ts","../src/wrappers/fastify.ts","../src/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'async_hooks';\r\nimport { ActiveTrace } from './types';\r\n\r\nexport const storage = new AsyncLocalStorage<ActiveTrace>();\r\n\r\nexport const Context = {\r\n run: <T>(trace: ActiveTrace, fn: () => T): T => {\r\n return storage.run(trace, fn);\r\n },\r\n\r\n current: (): ActiveTrace | undefined => {\r\n return storage.getStore();\r\n },\r\n\r\n addSpan: (span: any) => {\r\n const store = storage.getStore();\r\n if (store) {\r\n store.spans.push(span);\r\n }\r\n }\r\n};","import { SenzorOptions, Trace, TaskRun, SenzorError } from './types';\r\n\r\nexport class Transport {\r\n private traceQueue: Trace[] = [];\r\n private apmErrorQueue: SenzorError[] = [];\r\n\r\n private taskQueue: TaskRun[] = [];\r\n private taskErrorQueue: SenzorError[] = [];\r\n\r\n private timer: NodeJS.Timeout | null = null;\r\n private apmEndpoint: string;\r\n private taskEndpoint: string;\r\n\r\n constructor(private config: SenzorOptions) {\r\n const baseEndpoint = config.endpoint || 'https://api.senzor.dev';\r\n // Support legacy full URLs or base URLs\r\n this.apmEndpoint = baseEndpoint.includes('/api/ingest') ? baseEndpoint : `${baseEndpoint}/api/ingest/apm`;\r\n this.taskEndpoint = baseEndpoint.includes('/api/ingest') ? baseEndpoint.replace('/apm', '/task') : `${baseEndpoint}/api/ingest/task`;\r\n\r\n if (typeof setInterval !== 'undefined') {\r\n this.timer = setInterval(() => this.flush(), config.flushInterval || 10000);\r\n if (this.timer && typeof this.timer.unref === 'function') {\r\n this.timer.unref();\r\n }\r\n }\r\n }\r\n\r\n public addTrace(trace: any) {\r\n this.traceQueue.push(trace);\r\n this.checkFlush();\r\n }\r\n\r\n public addTask(task: TaskRun) {\r\n this.taskQueue.push(task);\r\n this.checkFlush();\r\n }\r\n\r\n public addError(error: SenzorError, type: 'apm' | 'task' = 'apm') {\r\n if (type === 'task') this.taskErrorQueue.push(error);\r\n else this.apmErrorQueue.push(error);\r\n this.checkFlush();\r\n }\r\n\r\n private checkFlush() {\r\n const totalApm = this.traceQueue.length + this.apmErrorQueue.length;\r\n const totalTask = this.taskQueue.length + this.taskErrorQueue.length;\r\n if (totalApm >= (this.config.batchSize || 100) || totalTask >= (this.config.batchSize || 100)) {\r\n this.flush();\r\n }\r\n }\r\n\r\n public async flush() {\r\n const apmPayload = { traces: [...this.traceQueue], errors: [...this.apmErrorQueue] };\r\n const taskPayload = { runs: [...this.taskQueue], errors: [...this.taskErrorQueue] };\r\n\r\n this.traceQueue = [];\r\n this.apmErrorQueue = [];\r\n this.taskQueue = [];\r\n this.taskErrorQueue = [];\r\n\r\n const headers = { 'Content-Type': 'application/json', 'x-service-api-key': this.config.apiKey };\r\n\r\n try {\r\n const promises = [];\r\n\r\n if (apmPayload.traces.length > 0 || apmPayload.errors.length > 0) {\r\n promises.push(fetch(this.apmEndpoint, { method: 'POST', headers, body: JSON.stringify(apmPayload), keepalive: true }));\r\n }\r\n\r\n if (taskPayload.runs.length > 0 || taskPayload.errors.length > 0) {\r\n promises.push(fetch(this.taskEndpoint, { method: 'POST', headers, body: JSON.stringify(taskPayload), keepalive: true }));\r\n }\r\n\r\n await Promise.allSettled(promises);\r\n\r\n if (this.config.debug) {\r\n console.log(`[Senzor] Flushed: ${apmPayload.traces.length} traces, ${taskPayload.runs.length} tasks`);\r\n }\r\n } catch (err) {\r\n if (this.config.debug) console.error('[Senzor] Transport Flush Error:', err);\r\n }\r\n }\r\n}","import { Transport } from './transport';\r\nimport { Context } from './context';\r\nimport { SenzorOptions, ActiveTrace, TaskRun } from './types';\r\nimport { randomUUID } from 'crypto';\r\nimport { instrumentHttp, instrumentFetch } from '../instrumentation/http';\r\nimport { instrumentMongo } from '../instrumentation/mongo';\r\nimport { instrumentPg } from '../instrumentation/pg';\r\nimport { instrumentBullMQ } from '../instrumentation/bullmq';\r\nimport { instrumentNodeCron } from '../instrumentation/cron';\r\n\r\nexport class SenzorClient {\r\n private transport: Transport | null = null;\r\n private options: SenzorOptions | null = null;\r\n private isInstrumented = false;\r\n\r\n public init(options: SenzorOptions) {\r\n if (!options.apiKey) {\r\n console.warn('[Senzor] API Key missing. SDK disabled.');\r\n return;\r\n }\r\n this.options = options;\r\n const endpoint = options.endpoint || 'https://api.senzor.dev/api/ingest/apm';\r\n const debug = options.debug || false;\r\n\r\n this.transport = new Transport({ ...options, endpoint });\r\n\r\n if (!this.isInstrumented) {\r\n this.setupGlobalErrorHandlers();\r\n\r\n try { instrumentHttp(endpoint, debug); } catch (e) { }\r\n try { instrumentFetch(endpoint, debug); } catch (e) { }\r\n try { instrumentMongo(debug); } catch (e) { }\r\n try { instrumentPg(); } catch (e) { }\r\n\r\n // Task Integrations (NEW)\r\n try { instrumentBullMQ(this, debug); } catch (e) { }\r\n try { instrumentNodeCron(this, debug); } catch (e) { }\r\n\r\n this.isInstrumented = true;\r\n if (debug) console.log('[Senzor] Auto-instrumentation & Error Tracking enabled');\r\n }\r\n }\r\n\r\n private setupGlobalErrorHandlers() {\r\n process.on('uncaughtException', (error) => {\r\n this.captureError(error, { type: 'uncaughtException' });\r\n });\r\n\r\n process.on('unhandledRejection', (reason) => {\r\n this.captureError(reason, { type: 'unhandledRejection' });\r\n });\r\n }\r\n\r\n public startTrace<T>(data: Partial<ActiveTrace['data']> & { headers?: any }, next: () => T): T {\r\n if (!this.transport) return next();\r\n\r\n let parentTraceId = undefined;\r\n let parentSpanId = undefined;\r\n\r\n if (data.headers) {\r\n const getHeader = (key: string) => {\r\n if (data.headers[key]) return data.headers[key];\r\n if (data.headers[key.toLowerCase()]) return data.headers[key.toLowerCase()];\r\n return undefined;\r\n };\r\n\r\n parentTraceId = getHeader('x-senzor-trace-id');\r\n parentSpanId = getHeader('x-senzor-parent-span-id');\r\n\r\n if (Array.isArray(parentTraceId)) parentTraceId = parentTraceId[0];\r\n if (Array.isArray(parentSpanId)) parentSpanId = parentSpanId[0];\r\n }\r\n\r\n const trace: ActiveTrace = {\r\n id: randomUUID(),\r\n contextType: 'apm', // Ensure we distinguish APM traces from Background Tasks\r\n startTime: performance.now(),\r\n data: {\r\n ...data,\r\n parentTraceId,\r\n parentSpanId\r\n },\r\n spans: []\r\n };\r\n\r\n return Context.run(trace, next);\r\n }\r\n\r\n public endTrace(status: number, extraData: any = {}) {\r\n const trace = Context.current();\r\n if (!trace || trace.contextType !== 'apm' || !this.transport) return;\r\n const duration = performance.now() - trace.startTime;\r\n\r\n const payload = {\r\n traceId: trace.id,\r\n parentTraceId: trace.data.parentTraceId,\r\n parentSpanId: trace.data.parentSpanId,\r\n ...trace.data,\r\n ...extraData,\r\n status, duration, spans: trace.spans, timestamp: new Date().toISOString()\r\n };\r\n this.transport.addTrace(payload);\r\n }\r\n\r\n // --- TASK MONITORING METHODS ---\r\n public startTask<T>(name: string, type: 'cron' | 'queue' | 'pipeline' | 'custom', options: any, next: () => T): T {\r\n if (!this.transport) return next();\r\n\r\n const currentContext = Context.current();\r\n const triggerTraceId = currentContext?.contextType === 'apm' ? currentContext.id : undefined;\r\n\r\n // Snapshot system resources before execution\r\n const startMemory = process.memoryUsage ? process.memoryUsage().heapUsed : 0;\r\n const startCpu = process.cpuUsage ? process.cpuUsage() : undefined;\r\n\r\n const task: ActiveTrace = {\r\n id: randomUUID(),\r\n contextType: 'task',\r\n startTime: performance.now(),\r\n startMemory,\r\n startCpu,\r\n data: { taskName: name, taskType: type, triggerTraceId, ...options },\r\n spans: []\r\n };\r\n return Context.run(task, next);\r\n }\r\n\r\n public endTask(status: 'success' | 'failed', extraMetadata: any = {}) {\r\n const task = Context.current();\r\n if (!task || task.contextType !== 'task' || !this.transport) return;\r\n\r\n // Calculate resource deltas\r\n let resourceMetrics;\r\n if (process.memoryUsage && task.startMemory !== undefined && process.cpuUsage && task.startCpu) {\r\n const endMemory = process.memoryUsage().heapUsed;\r\n const cpuDelta = process.cpuUsage(task.startCpu);\r\n\r\n resourceMetrics = {\r\n memoryDeltaBytes: endMemory - task.startMemory, // Can be negative if GC ran!\r\n cpuUserUs: cpuDelta.user,\r\n cpuSystemUs: cpuDelta.system\r\n };\r\n }\r\n\r\n const payload: TaskRun = {\r\n runId: task.id,\r\n taskName: task.data.taskName,\r\n taskType: task.data.taskType,\r\n triggerTraceId: task.data.triggerTraceId,\r\n queueDelay: task.data.queueDelay,\r\n attempts: task.data.attempts,\r\n isDeadLetter: task.data.isDeadLetter, // Extracted from options/metadata if provided\r\n metadata: { ...task.data.metadata, ...extraMetadata },\r\n resourceMetrics,\r\n status,\r\n duration: performance.now() - task.startTime,\r\n spans: task.spans,\r\n timestamp: new Date().toISOString()\r\n };\r\n\r\n this.transport.addTask(payload);\r\n }\r\n\r\n public wrapTask<T extends (...args: any[]) => any>(name: string, type: 'cron' | 'queue' | 'pipeline' | 'custom', options: any = {}, fn: T): T {\r\n return (async (...args: any[]) => {\r\n return this.startTask(name, type, options, async () => {\r\n try {\r\n const result = await fn(...args);\r\n this.endTask('success');\r\n return result;\r\n } catch (error) {\r\n this.captureError(error, { taskName: name });\r\n this.endTask('failed');\r\n throw error;\r\n }\r\n });\r\n }) as unknown as T;\r\n }\r\n\r\n // --- MODIFIED: Context-Aware Error Capture ---\r\n public captureError(error: unknown, context: any = {}) {\r\n if (!this.transport) return;\r\n\r\n let parsedError: Error;\r\n if (error instanceof Error) {\r\n parsedError = error;\r\n } else {\r\n parsedError = new Error(String(error));\r\n }\r\n\r\n const currentTrace = Context.current();\r\n\r\n const errPayload = {\r\n errorClass: parsedError.name || 'Error',\r\n message: parsedError.message,\r\n stackTrace: parsedError.stack,\r\n context,\r\n timestamp: new Date().toISOString()\r\n };\r\n\r\n if (currentTrace?.contextType === 'task') {\r\n this.transport.addError({ ...errPayload, runId: currentTrace.id }, 'task');\r\n } else {\r\n this.transport.addError({ ...errPayload, traceId: currentTrace?.id }, 'apm');\r\n }\r\n }\r\n\r\n public track(data: any) {\r\n this.transport?.addTrace({ traceId: randomUUID(), ...data, spans: [], timestamp: new Date().toISOString() });\r\n }\r\n\r\n public startSpan(name: string, type: 'db' | 'http' | 'function' | 'custom' = 'custom') {\r\n const trace = Context.current();\r\n if (!trace) return { end: () => { } };\r\n const startTime = performance.now() - trace.startTime;\r\n const spanStartAbs = performance.now();\r\n const spanId = randomUUID();\r\n return { end: (meta?: any, status?: number) => { Context.addSpan({ spanId, name, type, startTime, duration: performance.now() - spanStartAbs, status, meta }); } };\r\n }\r\n\r\n public async flush() { if (this.transport) await this.transport.flush(); }\r\n}\r\n\r\nexport const client = new SenzorClient();","import http from 'http';\r\nimport https from 'https';\r\nimport { URL } from 'url';\r\nimport { Context } from '../core/context';\r\nimport { randomUUID } from 'crypto';\r\n\r\nconst shimmer = (module: any, methodName: string, wrapper: (original: Function) => Function) => {\r\n if (!module[methodName]) return;\r\n const original = module[methodName];\r\n module[methodName] = wrapper(original);\r\n};\r\n\r\n// --- FETCH INSTRUMENTATION ---\r\nexport const instrumentFetch = (ingestUrl: string, debug = false) => {\r\n if (!globalThis.fetch) return;\r\n\r\n let ingestHost = '';\r\n try { ingestHost = new URL(ingestUrl).hostname; } catch (e) { }\r\n\r\n const originalFetch = globalThis.fetch;\r\n\r\n // @ts-ignore\r\n globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {\r\n let urlStr = '';\r\n if (typeof input === 'string') urlStr = input;\r\n else if (input instanceof URL) urlStr = input.toString();\r\n else if (input && (input as any).url) urlStr = (input as any).url;\r\n\r\n if (ingestHost && urlStr.includes(ingestHost)) {\r\n return originalFetch(input, init);\r\n }\r\n\r\n const trace = Context.current();\r\n if (!trace) {\r\n return originalFetch(input, init);\r\n }\r\n\r\n const method = (init?.method || 'GET').toUpperCase();\r\n const startTime = performance.now() - trace.startTime;\r\n const spanStartAbs = performance.now();\r\n const spanId = randomUUID();\r\n\r\n let hostname = 'unknown';\r\n try { hostname = new URL(urlStr).hostname; } catch (e) { }\r\n\r\n // Inject Headers\r\n const newInit = { ...init } as RequestInit;\r\n if (!newInit.headers) newInit.headers = {};\r\n\r\n // Helper to set header on various types\r\n const setHeader = (key: string, value: string) => {\r\n if (newInit.headers instanceof Headers) {\r\n newInit.headers.set(key, value);\r\n } else if (Array.isArray(newInit.headers)) {\r\n newInit.headers.push([key, value]);\r\n } else {\r\n (newInit.headers as any)[key] = value;\r\n }\r\n };\r\n\r\n setHeader('x-senzor-trace-id', trace.id);\r\n setHeader('x-senzor-parent-span-id', spanId);\r\n\r\n try {\r\n const response = await originalFetch(input, newInit);\r\n\r\n const duration = performance.now() - spanStartAbs;\r\n Context.addSpan({\r\n spanId,\r\n name: `${method} ${hostname}`,\r\n type: 'http',\r\n startTime,\r\n duration,\r\n status: response.status,\r\n meta: { url: urlStr, method, library: 'fetch' }\r\n });\r\n\r\n return response;\r\n } catch (err: any) {\r\n const duration = performance.now() - spanStartAbs;\r\n Context.addSpan({\r\n spanId,\r\n name: `${method} ${hostname}`,\r\n type: 'http',\r\n startTime,\r\n duration,\r\n status: 500,\r\n meta: { error: err.message, url: urlStr, library: 'fetch' }\r\n });\r\n throw err;\r\n }\r\n };\r\n};\r\n\r\n// --- HTTP/HTTPS INSTRUMENTATION ---\r\nexport const instrumentHttp = (ingestUrl: string, debug = false) => {\r\n let ingestHost = '';\r\n try { ingestHost = new URL(ingestUrl).hostname; } catch (e) { }\r\n\r\n const requestWrapper = (original: Function) => {\r\n return function (this: any, ...args: any[]) {\r\n let options: any = {};\r\n let urlStr = '';\r\n let optionsIndex = 0;\r\n\r\n // Parsing Logic: http.request(url, options, cb) OR http.request(options, cb)\r\n if (typeof args[0] === 'string' || args[0] instanceof URL) {\r\n urlStr = args[0].toString();\r\n optionsIndex = 1;\r\n } else {\r\n optionsIndex = 0;\r\n }\r\n\r\n // Ensure options object exists at correct index\r\n if (!args[optionsIndex] || typeof args[optionsIndex] !== 'object') {\r\n args[optionsIndex] = {};\r\n }\r\n options = args[optionsIndex];\r\n\r\n // Construct URL if missing\r\n if (!urlStr) {\r\n const protocol = options.protocol || (options.port === 443 ? 'https:' : 'http:');\r\n const host = options.hostname || options.host || 'localhost';\r\n const path = options.path || '/';\r\n urlStr = `${protocol}//${host}${path}`;\r\n }\r\n\r\n // Guard\r\n if (ingestHost && (urlStr.includes(ingestHost) || (options.hostname && options.hostname.includes(ingestHost)))) {\r\n return original.apply(this, args);\r\n }\r\n\r\n const trace = Context.current();\r\n if (!trace) return original.apply(this, args);\r\n\r\n const method = (options.method || 'GET').toUpperCase();\r\n const startTime = performance.now() - trace.startTime;\r\n const spanStartAbs = performance.now();\r\n const spanId = randomUUID();\r\n\r\n let hostname = 'unknown';\r\n try { hostname = new URL(urlStr).hostname; } catch (e) { hostname = options.hostname || 'unknown'; }\r\n\r\n // Inject Headers (Mutate the options object reference directly)\r\n if (!options.headers) options.headers = {};\r\n options.headers['x-senzor-trace-id'] = trace.id;\r\n options.headers['x-senzor-parent-span-id'] = spanId;\r\n\r\n // Debug\r\n if (debug) console.log(`[Senzor] Injecting headers to ${urlStr}`);\r\n\r\n // Call Original\r\n const req = original.apply(this, args);\r\n\r\n const captureSpan = (res: any, error?: Error) => {\r\n const duration = performance.now() - spanStartAbs;\r\n Context.addSpan({\r\n spanId,\r\n name: `${method} ${hostname}`,\r\n type: 'http',\r\n startTime,\r\n duration,\r\n status: error ? 500 : res?.statusCode || 0,\r\n meta: { url: urlStr, method, library: 'http' }\r\n });\r\n };\r\n\r\n req.on('response', (res: any) => {\r\n res.once('end', () => captureSpan(res));\r\n res.once('close', () => captureSpan(res));\r\n res.once('error', (err: Error) => captureSpan(res, err));\r\n });\r\n\r\n req.on('error', (err: Error) => captureSpan(null, err));\r\n\r\n return req;\r\n };\r\n };\r\n\r\n shimmer(http, 'request', requestWrapper);\r\n shimmer(http, 'get', requestWrapper);\r\n shimmer(https, 'request', requestWrapper);\r\n shimmer(https, 'get', requestWrapper);\r\n};","import { Context } from '../core/context';\r\n\r\nexport const instrumentMongo = (debug = false) => {\r\n try {\r\n const mongodb = require('mongodb');\r\n const Collection = mongodb.Collection;\r\n\r\n // Attempt to get Cursor classes\r\n // Note: The location of these classes varies by driver version, \r\n // checking common locations\r\n const FindCursor = mongodb.FindCursor || require('mongodb/lib/cursor/find_cursor').FindCursor;\r\n const AggregationCursor = mongodb.AggregationCursor || require('mongodb/lib/cursor/aggregation_cursor').AggregationCursor;\r\n\r\n if (debug) console.log('[Senzor] Instrumenting MongoDB (Collection + Cursors)...');\r\n\r\n // --- Helper to Record Span ---\r\n const recordSpan = (name: string, operation: string, collection: string, startAbs: number, traceStart: number, err?: Error) => {\r\n const duration = performance.now() - startAbs;\r\n Context.addSpan({\r\n name: `MongoDB ${name}`,\r\n type: 'db',\r\n startTime: performance.now() - traceStart - duration, // Adjust start time to when op actually started\r\n duration,\r\n status: err ? 500 : 0,\r\n meta: { collection, operation, error: err ? err.message : undefined }\r\n });\r\n if (debug) console.log(`[Senzor] Captured Mongo: ${name} (${duration.toFixed(2)}ms)`);\r\n };\r\n\r\n // --- 1. Instrument Immediate Operations (Insert/Update/Delete) ---\r\n const immediateMethods = ['insertOne', 'insertMany', 'updateOne', 'updateMany', 'deleteOne', 'deleteMany', 'countDocuments'];\r\n\r\n immediateMethods.forEach((method) => {\r\n if (!Collection.prototype[method]) return;\r\n const original = Collection.prototype[method];\r\n\r\n Collection.prototype[method] = function (...args: any[]) {\r\n const trace = Context.current();\r\n if (!trace) return original.apply(this, args);\r\n\r\n const spanStartAbs = performance.now();\r\n const traceStart = trace.startTime;\r\n const collName = this.collectionName;\r\n\r\n try {\r\n const result = original.apply(this, args);\r\n if (result && typeof result.then === 'function') {\r\n return result.then(\r\n (res: any) => { recordSpan(method, method, collName, spanStartAbs, traceStart); return res; },\r\n (err: any) => { recordSpan(method, method, collName, spanStartAbs, traceStart, err); throw err; }\r\n );\r\n }\r\n return result;\r\n } catch (err: any) {\r\n recordSpan(method, method, collName, spanStartAbs, traceStart, err);\r\n throw err;\r\n }\r\n };\r\n });\r\n\r\n // --- 2. Instrument Cursor Execution (find -> toArray) ---\r\n const patchCursor = (CursorClass: any, label: string) => {\r\n if (!CursorClass || !CursorClass.prototype.toArray) return;\r\n\r\n const originalToArray = CursorClass.prototype.toArray;\r\n\r\n CursorClass.prototype.toArray = function (...args: any[]) {\r\n const trace = Context.current();\r\n // Cursors are often created in context but executed later. \r\n // We check context at execution time.\r\n if (!trace) return originalToArray.apply(this, args);\r\n\r\n const spanStartAbs = performance.now();\r\n const traceStart = trace.startTime;\r\n // Attempt to get collection name from cursor internal state\r\n const collName = this.namespace?.collection || 'unknown';\r\n\r\n const onSuccess = (res: any) => {\r\n recordSpan(label, label, collName, spanStartAbs, traceStart);\r\n return res;\r\n };\r\n const onError = (err: any) => {\r\n recordSpan(label, label, collName, spanStartAbs, traceStart, err);\r\n throw err;\r\n };\r\n\r\n try {\r\n const result = originalToArray.apply(this, args);\r\n if (result && typeof result.then === 'function') {\r\n return result.then(onSuccess, onError);\r\n }\r\n return onSuccess(result);\r\n } catch (e) {\r\n onError(e);\r\n }\r\n };\r\n };\r\n\r\n patchCursor(FindCursor, 'find');\r\n patchCursor(AggregationCursor, 'aggregate');\r\n\r\n } catch (e: any) {\r\n if (debug) console.warn('[Senzor] MongoDB instrumentation warning:', e.message);\r\n }\r\n};","import { Context } from '../core/context';\r\n\r\n// Simple shim for 'pg' library\r\nexport const instrumentPg = () => {\r\n try {\r\n // Try to require pg (it might not be installed by user)\r\n const pg = require('pg');\r\n const originalQuery = pg.Client.prototype.query;\r\n\r\n pg.Client.prototype.query = function (...args: any[]) {\r\n const trace = Context.current();\r\n if (!trace) return originalQuery.apply(this, args);\r\n\r\n const startTime = performance.now() - trace.startTime;\r\n const spanStartAbs = performance.now();\r\n\r\n // Extract SQL (first arg usually string or config object)\r\n const sql = typeof args[0] === 'string' ? args[0] : args[0].text;\r\n\r\n // Wrap callback if present, or handle Promise\r\n const result = originalQuery.apply(this, args);\r\n\r\n if (result && typeof result.then === 'function') {\r\n return result.then((res: any) => {\r\n const duration = performance.now() - spanStartAbs;\r\n Context.addSpan({\r\n name: 'Postgres Query',\r\n type: 'db',\r\n startTime,\r\n duration,\r\n meta: { query: sql }\r\n });\r\n return res;\r\n });\r\n }\r\n return result;\r\n };\r\n } catch (e) {\r\n // User doesn't use pg, ignore\r\n }\r\n};","import Module from 'module';\r\n\r\nexport const hookRequire = (moduleName: string, onRequire: (exports: any) => void) => {\r\n // 1. If it was already loaded (e.g., imported at the top of the file before init)\r\n try {\r\n const resolvedPath = require.resolve(moduleName);\r\n const cached = require.cache[resolvedPath];\r\n if (cached && cached.exports) {\r\n onRequire(cached.exports);\r\n }\r\n } catch (e) {\r\n // Silently ignore if module is not installed\r\n }\r\n\r\n // 2. Intercept future requires\r\n const originalLoad = (Module as any)._load;\r\n (Module as any)._load = function (request: string, parent: any, isMain: boolean) {\r\n const exports = originalLoad.apply(this, arguments);\r\n if (request === moduleName && exports) {\r\n onRequire(exports);\r\n }\r\n return exports;\r\n };\r\n};","import type { SenzorClient } from '../core/client';\r\nimport { hookRequire } from './hook';\r\n\r\nexport const instrumentBullMQ = (client: SenzorClient, debug: boolean) => {\r\n hookRequire('bullmq', (bullExports) => {\r\n\r\n const patchWorker = (target: any) => {\r\n if (!target || !target.Worker || !target.Worker.prototype.processJob || target.Worker.prototype.processJob.__senzorPatched) return;\r\n\r\n const originalProcessJob = target.Worker.prototype.processJob;\r\n\r\n target.Worker.prototype.processJob = async function (job: any) {\r\n const queueDelay = job.timestamp ? Date.now() - job.timestamp : 0;\r\n\r\n // BullMQ increments attemptsMade *after* a failure. \r\n // So the current run attempt is attemptsMade + 1.\r\n const currentAttempt = (job.attemptsMade || 0) + 1;\r\n const maxAttempts = job.opts?.attempts || 1;\r\n\r\n // If it fails on this run, and it's >= the max allowed attempts, it's entering the DLQ.\r\n const isFinalAttempt = currentAttempt >= maxAttempts;\r\n\r\n const taskName = job.name === '__default__' ? job.queueName : `${job.queueName}:${job.name}`;\r\n\r\n return client.startTask(\r\n taskName,\r\n 'queue',\r\n {\r\n queueDelay,\r\n attempts: currentAttempt,\r\n // We preset isDeadLetter to false, but if it throws an error and isFinalAttempt is true, \r\n // we will mutate this in the catch block.\r\n isDeadLetter: false,\r\n metadata: { jobId: job.id, queueName: job.queueName, maxAttempts }\r\n },\r\n async () => {\r\n try {\r\n const result = await originalProcessJob.apply(this, arguments);\r\n client.endTask('success');\r\n return result;\r\n } catch (error) {\r\n const context = require('../core/context').Context.current();\r\n if (context && context.contextType === 'task' && isFinalAttempt) {\r\n context.data.isDeadLetter = true; // Flag it as a permanent failure\r\n }\r\n\r\n client.captureError(error, {\r\n queueName: job.queueName,\r\n jobId: job.id,\r\n isDeadLetter: isFinalAttempt\r\n });\r\n\r\n client.endTask('failed');\r\n throw error;\r\n }\r\n }\r\n );\r\n };\r\n\r\n Object.defineProperty(target.Worker.prototype.processJob, '__senzorPatched', { value: true, enumerable: false, writable: true });\r\n if (debug) console.log('[Senzor] BullMQ Worker successfully instrumented with DLQ tracking');\r\n };\r\n\r\n patchWorker(bullExports);\r\n\r\n if (bullExports.default) {\r\n patchWorker(bullExports.default);\r\n }\r\n });\r\n};","import type { SenzorClient } from '../core/client';\r\nimport { hookRequire } from './hook';\r\n\r\nexport const instrumentNodeCron = (client: SenzorClient, debug: boolean) => {\r\n hookRequire('node-cron', (cronExports) => {\r\n\r\n // Abstracted patcher so we can apply it to both the root and the .default export\r\n const patchSchedule = (target: any) => {\r\n if (!target || typeof target.schedule !== 'function' || target.__senzorPatched) return;\r\n\r\n const originalSchedule = target.schedule;\r\n\r\n target.schedule = function (expression: string, func: (...args: any[]) => any, options: any) {\r\n // Handle node-cron's dynamic options argument (can be string or object)\r\n const optsObj = typeof options === 'object' ? options : { timezone: options };\r\n const taskName = optsObj?.name || `cron: ${expression}`;\r\n\r\n const wrappedFunc = client.wrapTask(\r\n taskName,\r\n 'cron',\r\n { metadata: optsObj },\r\n func\r\n );\r\n\r\n return originalSchedule.call(this, expression, wrappedFunc, options);\r\n };\r\n\r\n // Safely mark as patched to prevent infinite loops\r\n Object.defineProperty(target, '__senzorPatched', { value: true, enumerable: false, writable: true });\r\n if (debug) console.log('[Senzor] Node-Cron successfully instrumented');\r\n };\r\n\r\n // Apply patch to root (for const cron = require('node-cron'))\r\n patchSchedule(cronExports);\r\n\r\n // Apply patch to default (for import cron from 'node-cron')\r\n if (cronExports.default) {\r\n patchSchedule(cronExports.default);\r\n }\r\n });\r\n};","import { client } from '../core/client';\r\n\r\n// 1. Request Handler (Place before routes)\r\nexport const expressMiddleware = () => {\r\n return (req: any, res: any, next: () => void) => {\r\n client.startTrace({\r\n method: req.method,\r\n path: req.originalUrl || req.url,\r\n ip: req.ip || req.socket?.remoteAddress,\r\n userAgent: req.headers['user-agent'],\r\n headers: req.headers\r\n }, () => {\r\n\r\n // Auto-detect status code on finish\r\n res.once('finish', () => {\r\n try {\r\n let route = 'UNKNOWN';\r\n // Express populates req.route only if a route matched\r\n if (req.route && req.route.path) {\r\n route = (req.baseUrl || '') + req.route.path;\r\n } else if (res.statusCode === 404) {\r\n route = 'Not Found';\r\n } else {\r\n route = req.path || 'Wildcard';\r\n }\r\n\r\n client.endTrace(res.statusCode, { route });\r\n } catch (e) { /* Fail open */ }\r\n });\r\n\r\n next();\r\n });\r\n };\r\n};\r\n\r\n// 2. Error Handler (Place after routes)\r\n// This is required in Express to capture the actual Error Object (Stack Trace)\r\nexport const expressErrorHandler = () => {\r\n return (err: any, req: any, res: any, next: (err?: any) => void) => {\r\n\r\n // 1. Capture the exception context\r\n client.captureError(err);\r\n\r\n // 2. Pass it to the next error handler (don't swallow it)\r\n next(err);\r\n };\r\n};","/**\r\n * Heuristic URL Normalizer\r\n * Converts raw paths with IDs into generic patterns to prevent high cardinality.\r\n * Example: /users/123/orders/abc-def -> /users/:id/orders/:uuid\r\n */\r\nexport const normalizePath = (path: string): string => {\r\n if (!path || path === '/') return '/';\r\n\r\n return path\r\n // Replace UUIDs (long alphanumeric strings)\r\n .replace(\r\n /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g,\r\n ':uuid'\r\n )\r\n // Replace MongoDB ObjectIds (24 hex chars)\r\n .replace(/[0-9a-fA-F]{24}/g, ':objectId')\r\n // Replace pure numeric IDs (e.g., /123)\r\n .replace(/\\/(\\d+)(?=\\/|$)/g, '/:id')\r\n // Remove query strings\r\n .split('?')[0];\r\n};\r\n\r\n/**\r\n * Tries to extract route from Framework internals, falls back to heuristic\r\n */\r\nexport const getRoute = (req: any, fallbackPath: string): string => {\r\n // Express / Connect\r\n if (req.route && req.route.path) {\r\n return (req.baseUrl || '') + req.route.path;\r\n }\r\n\r\n // H3 / Nitro (Nuxt)\r\n if (req.context && req.context.matchedRoute) {\r\n return req.context.matchedRoute.path;\r\n }\r\n\r\n // Fastify\r\n if (req.routerPath) {\r\n return req.routerPath;\r\n }\r\n\r\n // Fallback: Heuristic Normalization\r\n return normalizePath(fallbackPath);\r\n};","import { client } from '../core/client';\r\nimport { getRoute } from '../core/normalizer';\r\n\r\ntype EventHandler = (event: any) => any;\r\n\r\nexport const wrapH3 = (handler: EventHandler) => {\r\n return (event: any) => {\r\n const req = event.node.req;\r\n const path = req.originalUrl || req.url || '/';\r\n\r\n return client.startTrace({\r\n method: req.method || 'GET',\r\n path: path,\r\n ip: req.headers['x-forwarded-for'] || req.socket?.remoteAddress,\r\n userAgent: req.headers['user-agent'],\r\n headers: req.headers // Pass headers\r\n }, async () => {\r\n try {\r\n const response = await handler(event);\r\n let status = 200;\r\n if (event.node.res.statusCode) status = event.node.res.statusCode;\r\n if (response && response.statusCode) status = response.statusCode;\r\n\r\n client.endTrace(status, { route: getRoute(event, path) });\r\n return response;\r\n } catch (err: any) {\r\n client.captureError(err);\r\n const status = err.statusCode || err.status || 500;\r\n client.endTrace(status, { route: getRoute(event, path) });\r\n throw err;\r\n }\r\n });\r\n };\r\n};","import { client } from '../core/client';\r\nimport { normalizePath } from '../core/normalizer';\r\n\r\n// --- App Router Wrapper ---\r\nexport const wrapNextRoute = (handler: Function) => {\r\n return async (req: Request | any, context?: any) => {\r\n\r\n // Extract info from Web Standard Request\r\n const url = req.url ? new URL(req.url) : { pathname: '/' };\r\n const method = req.method || 'GET';\r\n\r\n // Header Extraction\r\n let headers: Record<string, string> = {};\r\n let ua: string | undefined;\r\n let ip: string | undefined;\r\n\r\n if (typeof req.headers.get === 'function') {\r\n // It's a Web Request Object\r\n ua = req.headers.get('user-agent');\r\n ip = req.headers.get('x-forwarded-for');\r\n\r\n // Convert to plain object for trace context extraction\r\n req.headers.forEach((value: string, key: string) => {\r\n headers[key] = value;\r\n });\r\n } else {\r\n // It's a Node Request Object (rare in App router but possible)\r\n headers = req.headers;\r\n ua = headers['user-agent'];\r\n ip = headers['x-forwarded-for'] as string;\r\n }\r\n\r\n return client.startTrace({\r\n method,\r\n path: url.pathname,\r\n userAgent: ua,\r\n ip: ip,\r\n headers: headers // Pass extracted headers\r\n }, async () => {\r\n try {\r\n const response = await handler(req, context);\r\n const status = response?.status || 200;\r\n\r\n client.endTrace(status, { route: normalizePath(url.pathname) });\r\n return response;\r\n } catch (err: any) {\r\n client.captureError(err);\r\n client.endTrace(500, { route: normalizePath(url.pathname) });\r\n throw err;\r\n }\r\n });\r\n };\r\n};\r\n\r\n// --- Pages Router Wrapper ---\r\nexport const wrapNextPages = (handler: Function) => {\r\n return async (req: any, res: any) => {\r\n const path = req.url ? req.url.split('?')[0] : '/';\r\n\r\n return client.startTrace({\r\n method: req.method || 'GET',\r\n path: path,\r\n userAgent: req.headers['user-agent'],\r\n ip: req.headers['x-forwarded-for'] || req.socket?.remoteAddress,\r\n headers: req.headers // Standard Node headers work fine\r\n }, async () => {\r\n\r\n const done = () => {\r\n client.endTrace(res.statusCode || 200, { route: normalizePath(path) });\r\n };\r\n\r\n res.once('finish', done);\r\n res.once('close', done);\r\n\r\n try {\r\n return await handler(req, res);\r\n } catch (e: any) {\r\n client.captureError(e);\r\n throw e;\r\n }\r\n });\r\n };\r\n};","import { client } from '../core/client';\r\nimport { SenzorOptions } from '../core/types';\r\n\r\nexport const senzorPlugin = (fastify: any, options: SenzorOptions, done: Function) => {\r\n if (options && options.apiKey) {\r\n client.init(options);\r\n }\r\n\r\n fastify.addHook('onRequest', (request: any, reply: any, next: Function) => {\r\n client.startTrace({\r\n method: request.method,\r\n path: request.raw.url || request.url,\r\n ip: request.ip,\r\n userAgent: request.headers['user-agent'],\r\n headers: request.headers // Pass headers\r\n }, () => next());\r\n });\r\n\r\n fastify.addHook('onError', (request: any, reply: any, error: any, next: Function) => {\r\n client.captureError(error);\r\n next();\r\n });\r\n\r\n fastify.addHook('onResponse', (request: any, reply: any, next: Function) => {\r\n const route = request.routeOptions?.url || request.routerPath || 'UNKNOWN';\r\n client.endTrace(reply.statusCode, { route });\r\n next();\r\n });\r\n\r\n done();\r\n};","import { client } from './core/client';\r\nimport { expressMiddleware, expressErrorHandler } from './middleware/express';\r\nimport { wrapH3 } from './wrappers/h3';\r\nimport { wrapNextRoute, wrapNextPages } from './wrappers/next';\r\nimport { senzorPlugin } from './wrappers/fastify';\r\nimport { SenzorOptions } from './core/types';\r\n\r\nconst Senzor = {\r\n init: (options: SenzorOptions) => client.init(options),\r\n flush: () => client.flush(),\r\n track: client.track.bind(client),\r\n startSpan: client.startSpan.bind(client),\r\n captureException: client.captureError.bind(client),\r\n\r\n // Task Monitoring (NEW)\r\n wrapTask: client.wrapTask.bind(client),\r\n startTask: client.startTask.bind(client),\r\n\r\n // Express\r\n requestHandler: expressMiddleware,\r\n errorHandler: expressErrorHandler,\r\n\r\n // Next\r\n wrapNextRoute,\r\n wrapNextPages,\r\n\r\n // H3\r\n wrapH3,\r\n\r\n // Fastify\r\n fastifyPlugin: senzorPlugin\r\n};\r\n\r\nexport default Senzor;\r\nexport { Senzor };"],"mappings":"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","names":["context_exports","__export","Context","storage","AsyncLocalStorage","init_context","__esmMin","trace","fn","span","store","Transport","config","baseEndpoint","trace","task","error","type","totalApm","totalTask","apmPayload","taskPayload","headers","promises","err","init_context","randomUUID","init_context","http","https","URL","randomUUID","shimmer","module","methodName","wrapper","original","instrumentFetch","ingestUrl","debug","ingestHost","originalFetch","input","init","urlStr","trace","Context","method","startTime","spanStartAbs","spanId","hostname","newInit","setHeader","key","value","response","duration","err","instrumentHttp","requestWrapper","args","options","optionsIndex","protocol","host","path","req","captureSpan","res","error","init_context","instrumentMongo","debug","mongodb","Collection","FindCursor","AggregationCursor","recordSpan","name","operation","collection","startAbs","traceStart","err","duration","Context","method","original","args","trace","spanStartAbs","collName","result","res","patchCursor","CursorClass","label","originalToArray","onSuccess","onError","e","init_context","instrumentPg","pg","originalQuery","args","trace","Context","startTime","spanStartAbs","sql","result","res","duration","Module","hookRequire","moduleName","onRequire","resolvedPath","__require","cached","originalLoad","Module","request","parent","isMain","exports","instrumentBullMQ","client","debug","hookRequire","bullExports","patchWorker","target","originalProcessJob","job","queueDelay","currentAttempt","maxAttempts","isFinalAttempt","taskName","result","error","context","instrumentNodeCron","client","debug","hookRequire","cronExports","patchSchedule","target","originalSchedule","expression","func","options","optsObj","taskName","wrappedFunc","SenzorClient","options","endpoint","debug","Transport","instrumentHttp","instrumentFetch","instrumentMongo","instrumentPg","instrumentBullMQ","instrumentNodeCron","error","reason","data","next","parentTraceId","parentSpanId","getHeader","key","trace","randomUUID","Context","status","extraData","duration","payload","name","type","currentContext","triggerTraceId","startMemory","startCpu","task","extraMetadata","resourceMetrics","endMemory","cpuDelta","fn","args","result","context","parsedError","currentTrace","errPayload","startTime","spanStartAbs","spanId","meta","client","expressMiddleware","req","res","next","client","route","expressErrorHandler","err","normalizePath","path","getRoute","req","fallbackPath","wrapH3","handler","event","req","path","client","response","status","getRoute","err","wrapNextRoute","handler","req","context","url","method","headers","ua","ip","value","key","client","response","status","normalizePath","err","wrapNextPages","res","path","done","e","senzorPlugin","fastify","options","done","client","request","reply","next","error","route","Senzor","options","client","expressMiddleware","expressErrorHandler","wrapNextRoute","wrapNextPages","wrapH3","senzorPlugin","index_default"]}
|
package/package.json
CHANGED
package/src/core/client.ts
CHANGED
|
@@ -102,18 +102,23 @@ export class SenzorClient {
|
|
|
102
102
|
this.transport.addTrace(payload);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
// ---
|
|
105
|
+
// --- TASK MONITORING METHODS ---
|
|
106
106
|
public startTask<T>(name: string, type: 'cron' | 'queue' | 'pipeline' | 'custom', options: any, next: () => T): T {
|
|
107
107
|
if (!this.transport) return next();
|
|
108
108
|
|
|
109
|
-
// Distributed Tracing: If an APM trace spawns this task (e.g. queueing a job inside an API)
|
|
110
109
|
const currentContext = Context.current();
|
|
111
110
|
const triggerTraceId = currentContext?.contextType === 'apm' ? currentContext.id : undefined;
|
|
112
111
|
|
|
112
|
+
// Snapshot system resources before execution
|
|
113
|
+
const startMemory = process.memoryUsage ? process.memoryUsage().heapUsed : 0;
|
|
114
|
+
const startCpu = process.cpuUsage ? process.cpuUsage() : undefined;
|
|
115
|
+
|
|
113
116
|
const task: ActiveTrace = {
|
|
114
117
|
id: randomUUID(),
|
|
115
118
|
contextType: 'task',
|
|
116
119
|
startTime: performance.now(),
|
|
120
|
+
startMemory,
|
|
121
|
+
startCpu,
|
|
117
122
|
data: { taskName: name, taskType: type, triggerTraceId, ...options },
|
|
118
123
|
spans: []
|
|
119
124
|
};
|
|
@@ -124,6 +129,19 @@ export class SenzorClient {
|
|
|
124
129
|
const task = Context.current();
|
|
125
130
|
if (!task || task.contextType !== 'task' || !this.transport) return;
|
|
126
131
|
|
|
132
|
+
// Calculate resource deltas
|
|
133
|
+
let resourceMetrics;
|
|
134
|
+
if (process.memoryUsage && task.startMemory !== undefined && process.cpuUsage && task.startCpu) {
|
|
135
|
+
const endMemory = process.memoryUsage().heapUsed;
|
|
136
|
+
const cpuDelta = process.cpuUsage(task.startCpu);
|
|
137
|
+
|
|
138
|
+
resourceMetrics = {
|
|
139
|
+
memoryDeltaBytes: endMemory - task.startMemory, // Can be negative if GC ran!
|
|
140
|
+
cpuUserUs: cpuDelta.user,
|
|
141
|
+
cpuSystemUs: cpuDelta.system
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
127
145
|
const payload: TaskRun = {
|
|
128
146
|
runId: task.id,
|
|
129
147
|
taskName: task.data.taskName,
|
|
@@ -131,13 +149,15 @@ export class SenzorClient {
|
|
|
131
149
|
triggerTraceId: task.data.triggerTraceId,
|
|
132
150
|
queueDelay: task.data.queueDelay,
|
|
133
151
|
attempts: task.data.attempts,
|
|
152
|
+
isDeadLetter: task.data.isDeadLetter, // Extracted from options/metadata if provided
|
|
134
153
|
metadata: { ...task.data.metadata, ...extraMetadata },
|
|
154
|
+
resourceMetrics,
|
|
135
155
|
status,
|
|
136
156
|
duration: performance.now() - task.startTime,
|
|
137
157
|
spans: task.spans,
|
|
138
158
|
timestamp: new Date().toISOString()
|
|
139
159
|
};
|
|
140
|
-
|
|
160
|
+
|
|
141
161
|
this.transport.addTask(payload);
|
|
142
162
|
}
|
|
143
163
|
|
package/src/core/types.ts
CHANGED
|
@@ -41,6 +41,12 @@ export interface Trace {
|
|
|
41
41
|
spans: Span[];
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
export interface ResourceMetrics {
|
|
45
|
+
memoryDeltaBytes: number; // Delta of process.memoryUsage().heapUsed
|
|
46
|
+
cpuUserUs: number; // CPU time spent in user space (microseconds)
|
|
47
|
+
cpuSystemUs: number; // CPU time spent in OS system calls (microseconds)
|
|
48
|
+
}
|
|
49
|
+
|
|
44
50
|
export interface TaskRun {
|
|
45
51
|
runId: string;
|
|
46
52
|
taskName: string;
|
|
@@ -51,6 +57,8 @@ export interface TaskRun {
|
|
|
51
57
|
attempts?: number;
|
|
52
58
|
triggerTraceId?: string;
|
|
53
59
|
metadata?: any;
|
|
60
|
+
resourceMetrics?: ResourceMetrics; // Hardware cost profiling
|
|
61
|
+
isDeadLetter?: boolean; // True if the job failed its final retry
|
|
54
62
|
spans: Span[];
|
|
55
63
|
timestamp: string;
|
|
56
64
|
}
|
|
@@ -60,6 +68,8 @@ export interface ActiveTrace {
|
|
|
60
68
|
id: string; // The APM traceId OR the Task runId
|
|
61
69
|
contextType: 'apm' | 'task';
|
|
62
70
|
startTime: number;
|
|
71
|
+
startMemory?: number; // Baseline heap
|
|
72
|
+
startCpu?: NodeJS.CpuUsage; // Baseline CPU tick
|
|
63
73
|
data: any; // Holds Partial<Trace> or Partial<TaskRun>
|
|
64
74
|
spans: Span[];
|
|
65
75
|
}
|
|
@@ -11,20 +11,45 @@ export const instrumentBullMQ = (client: SenzorClient, debug: boolean) => {
|
|
|
11
11
|
|
|
12
12
|
target.Worker.prototype.processJob = async function (job: any) {
|
|
13
13
|
const queueDelay = job.timestamp ? Date.now() - job.timestamp : 0;
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
// BullMQ increments attemptsMade *after* a failure.
|
|
16
|
+
// So the current run attempt is attemptsMade + 1.
|
|
17
|
+
const currentAttempt = (job.attemptsMade || 0) + 1;
|
|
18
|
+
const maxAttempts = job.opts?.attempts || 1;
|
|
19
|
+
|
|
20
|
+
// If it fails on this run, and it's >= the max allowed attempts, it's entering the DLQ.
|
|
21
|
+
const isFinalAttempt = currentAttempt >= maxAttempts;
|
|
22
|
+
|
|
15
23
|
const taskName = job.name === '__default__' ? job.queueName : `${job.queueName}:${job.name}`;
|
|
16
24
|
|
|
17
25
|
return client.startTask(
|
|
18
26
|
taskName,
|
|
19
27
|
'queue',
|
|
20
|
-
{
|
|
28
|
+
{
|
|
29
|
+
queueDelay,
|
|
30
|
+
attempts: currentAttempt,
|
|
31
|
+
// We preset isDeadLetter to false, but if it throws an error and isFinalAttempt is true,
|
|
32
|
+
// we will mutate this in the catch block.
|
|
33
|
+
isDeadLetter: false,
|
|
34
|
+
metadata: { jobId: job.id, queueName: job.queueName, maxAttempts }
|
|
35
|
+
},
|
|
21
36
|
async () => {
|
|
22
37
|
try {
|
|
23
38
|
const result = await originalProcessJob.apply(this, arguments);
|
|
24
39
|
client.endTask('success');
|
|
25
40
|
return result;
|
|
26
41
|
} catch (error) {
|
|
27
|
-
|
|
42
|
+
const context = require('../core/context').Context.current();
|
|
43
|
+
if (context && context.contextType === 'task' && isFinalAttempt) {
|
|
44
|
+
context.data.isDeadLetter = true; // Flag it as a permanent failure
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
client.captureError(error, {
|
|
48
|
+
queueName: job.queueName,
|
|
49
|
+
jobId: job.id,
|
|
50
|
+
isDeadLetter: isFinalAttempt
|
|
51
|
+
});
|
|
52
|
+
|
|
28
53
|
client.endTask('failed');
|
|
29
54
|
throw error;
|
|
30
55
|
}
|
|
@@ -33,7 +58,7 @@ export const instrumentBullMQ = (client: SenzorClient, debug: boolean) => {
|
|
|
33
58
|
};
|
|
34
59
|
|
|
35
60
|
Object.defineProperty(target.Worker.prototype.processJob, '__senzorPatched', { value: true, enumerable: false, writable: true });
|
|
36
|
-
if (debug) console.log('[Senzor] BullMQ Worker successfully instrumented');
|
|
61
|
+
if (debug) console.log('[Senzor] BullMQ Worker successfully instrumented with DLQ tracking');
|
|
37
62
|
};
|
|
38
63
|
|
|
39
64
|
patchWorker(bullExports);
|