@himanshu-panchal/nodescope-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/auto-detect.js ADDED
@@ -0,0 +1,51 @@
1
+ const { patchPg } = require('./patches/pg');
2
+ const { patchMysql } = require('./patches/mysql');
3
+ const { patchMongoose } = require('./patches/mongoose');
4
+ const { patchRedis } = require('./patches/redis');
5
+ const { patchAxios } = require('./patches/axios');
6
+
7
+ function autoDetectAndPatch() {
8
+ const patched = [];
9
+
10
+ // PostgreSQL detect
11
+ try {
12
+ const pg = require('pg');
13
+ patchPg(pg);
14
+ patched.push('postgresql');
15
+ } catch (e) { /* not installed */ }
16
+
17
+ // MySQL detect
18
+ try {
19
+ const mysql = require('mysql2');
20
+ patchMysql(mysql);
21
+ patched.push('mysql');
22
+ } catch (e) {
23
+ try {
24
+ const mysql = require('mysql');
25
+ patchMysql(mysql);
26
+ patched.push('mysql');
27
+ } catch (e) { /* not installed */ }
28
+ }
29
+
30
+ // MongoDB/Mongoose detect
31
+ try {
32
+ const mongoose = require('mongoose');
33
+ patchMongoose(mongoose);
34
+ patched.push('mongodb');
35
+ } catch (e) { /* not installed */ }
36
+
37
+ // Axios detect
38
+ try {
39
+ const axios = require('axios');
40
+ patchAxios(axios);
41
+ patched.push('axios');
42
+ } catch (e) { /* not installed */ }
43
+
44
+ if (patched.length > 0) {
45
+ console.log(`✅ NodeScope: Auto-detected [${patched.join(', ')}]`);
46
+ }
47
+
48
+ return patched;
49
+ }
50
+
51
+ module.exports = { autoDetectAndPatch };
package/context.js ADDED
@@ -0,0 +1,51 @@
1
+ const { AsyncLocalStorage } = require('async_hooks');
2
+
3
+ const storage = new AsyncLocalStorage();
4
+
5
+ class Context {
6
+ static run(traceId, requestId, route, method, callback) {
7
+ const store = {
8
+ traceId,
9
+ requestId,
10
+ route,
11
+ method,
12
+ startTime: Date.now(),
13
+ spans: new Map(),
14
+ currentSpanId: null,
15
+ };
16
+ return storage.run(store, callback);
17
+ }
18
+
19
+ static getStore() {
20
+ return storage.getStore();
21
+ }
22
+
23
+ static getTraceId() {
24
+ return storage.getStore()?.traceId;
25
+ }
26
+
27
+ static addSpan(span) {
28
+ const store = storage.getStore();
29
+ if (store) store.spans.set(span.id, span);
30
+ }
31
+
32
+ static getSpan(spanId) {
33
+ return storage.getStore()?.spans.get(spanId);
34
+ }
35
+
36
+ static getAllSpans() {
37
+ const store = storage.getStore();
38
+ return store ? Array.from(store.spans.values()) : [];
39
+ }
40
+
41
+ static setCurrentSpanId(spanId) {
42
+ const store = storage.getStore();
43
+ if (store) store.currentSpanId = spanId;
44
+ }
45
+
46
+ static getCurrentSpanId() {
47
+ return storage.getStore()?.currentSpanId;
48
+ }
49
+ }
50
+
51
+ module.exports = { Context };
package/index.js ADDED
@@ -0,0 +1,75 @@
1
+ const { createMiddleware } = require('./middleware');
2
+ const { getInstance } = require('./tracer');
3
+ const { Context } = require('./context');
4
+ const { autoDetectAndPatch } = require('./auto-detect');
5
+ const { patchPg } = require('./patches/pg');
6
+ const { patchMysql } = require('./patches/mysql');
7
+ const { patchMongoose } = require('./patches/mongoose');
8
+ const { patchRedis } = require('./patches/redis');
9
+ const { patchAxios } = require('./patches/axios');
10
+ const { v4: uuidv4 } = require('uuid');
11
+
12
+ let autoDetectDone = false;
13
+
14
+ // Main function
15
+ function nodescope(config) {
16
+ // Auto detect libraries (ek baar)
17
+ if (!autoDetectDone) {
18
+ autoDetectDone = true;
19
+ // Tracer pehle init karo
20
+ getInstance(config);
21
+ // Phir auto detect
22
+ setTimeout(() => autoDetectAndPatch(), 100);
23
+ }
24
+
25
+ return createMiddleware(config);
26
+ }
27
+
28
+ // Manual deep trace wrapper
29
+ function trace(fn) {
30
+ return function (...args) {
31
+ const tracer = getInstance();
32
+ const traceId = Context.getTraceId();
33
+
34
+ if (!tracer || !traceId) {
35
+ return fn.apply(this, args);
36
+ }
37
+
38
+ const spanId = uuidv4();
39
+ const name = fn.name || 'anonymous';
40
+
41
+ tracer.startSpan(spanId, traceId, name, 'internal');
42
+
43
+ let result;
44
+ try {
45
+ result = fn.apply(this, args);
46
+ } catch (err) {
47
+ tracer.endSpan(spanId, err);
48
+ throw err;
49
+ }
50
+
51
+ if (result?.then) {
52
+ return result
53
+ .then((res) => { tracer.endSpan(spanId); return res; })
54
+ .catch((err) => { tracer.endSpan(spanId, err); throw err; });
55
+ }
56
+
57
+ tracer.endSpan(spanId);
58
+ return result;
59
+ };
60
+ }
61
+
62
+ // Exports
63
+ module.exports = nodescope;
64
+
65
+ // Named exports for manual use
66
+ module.exports.nodescope = nodescope;
67
+ module.exports.trace = trace;
68
+ module.exports.Context = Context;
69
+
70
+ // Manual patches (agar auto-detect kaam na kare)
71
+ module.exports.patchPg = patchPg;
72
+ module.exports.patchMysql = patchMysql;
73
+ module.exports.patchMongoose = patchMongoose;
74
+ module.exports.patchRedis = patchRedis;
75
+ module.exports.patchAxios = patchAxios;
package/middleware.js ADDED
@@ -0,0 +1,68 @@
1
+ const { v4: uuidv4 } = require('uuid');
2
+ const { Context } = require('./context');
3
+ const { getInstance } = require('./tracer');
4
+
5
+ function createMiddleware(config) {
6
+ const tracer = getInstance(config);
7
+
8
+ return function nodeScopeMiddleware(req, res, next) {
9
+ // Health check skip karo
10
+ if (req.path === '/health' || req.path === '/ping') {
11
+ return next();
12
+ }
13
+
14
+ const traceId = uuidv4();
15
+ const requestId = uuidv4();
16
+
17
+ tracer.startTrace(traceId, req.method, req.path);
18
+
19
+ Context.run(traceId, requestId, req.path, req.method, () => {
20
+ req.nodescope = {
21
+ traceId,
22
+ startSpan: (name) => {
23
+ const spanId = uuidv4();
24
+ tracer.startSpan(spanId, traceId, name, 'internal');
25
+ return {
26
+ end: (error) => tracer.endSpan(spanId, error),
27
+ setAttribute: (key, value) => tracer.addAttribute(spanId, key, value),
28
+ };
29
+ },
30
+ };
31
+
32
+ const metadata = {
33
+ ip: req.ip || req.connection?.remoteAddress,
34
+ userAgent: req.get('user-agent'),
35
+ query: Object.keys(req.query).length ? req.query : undefined,
36
+ route: req.route?.path,
37
+ };
38
+
39
+ let ended = false;
40
+
41
+ function endTrace() {
42
+ if (!ended) {
43
+ ended = true;
44
+ tracer.endTrace(traceId, res.statusCode, metadata);
45
+ }
46
+ }
47
+
48
+ const originalJson = res.json.bind(res);
49
+ const originalSend = res.send.bind(res);
50
+
51
+ res.json = function (data) {
52
+ endTrace();
53
+ return originalJson(data);
54
+ };
55
+
56
+ res.send = function (data) {
57
+ endTrace();
58
+ return originalSend(data);
59
+ };
60
+
61
+ res.on('finish', endTrace);
62
+
63
+ next();
64
+ });
65
+ };
66
+ }
67
+
68
+ module.exports = { createMiddleware };
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@himanshu-panchal/nodescope-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Universal Node.js observability SDK - works with any project",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "start": "node index.js"
8
+ },
9
+ "keywords": [
10
+ "observability",
11
+ "tracing",
12
+ "monitoring",
13
+ "nodejs",
14
+ "express",
15
+ "postgresql",
16
+ "mongodb",
17
+ "mysql",
18
+ "redis",
19
+ "apm"
20
+ ],
21
+ "author": "Himanshu Panchal",
22
+ "license": "MIT",
23
+ "dependencies": {
24
+ "socket.io-client": "^4.6.1",
25
+ "uuid": "^9.0.1"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/YOUR_USERNAME/nodescope-sdk"
30
+ },
31
+ "peerDependencies": {
32
+ "express": ">=4.0.0"
33
+ },
34
+ "peerDependenciesMeta": {
35
+ "express": { "optional": true },
36
+ "pg": { "optional": true },
37
+ "mysql2": { "optional": true },
38
+ "mongoose": { "optional": true },
39
+ "redis": { "optional": true },
40
+ "axios": { "optional": true }
41
+ }
42
+ }
@@ -0,0 +1,60 @@
1
+ const { v4: uuidv4 } = require('uuid');
2
+ const { Context } = require('../context');
3
+ const { getInstance } = require('../tracer');
4
+
5
+ function patchAxios(axios) {
6
+ if (!axios || axios.__nodescope_patched__) return axios;
7
+
8
+ // Request interceptor
9
+ axios.interceptors.request.use((config) => {
10
+ const tracer = getInstance();
11
+ const traceId = Context.getTraceId();
12
+
13
+ if (!tracer || !traceId) return config;
14
+
15
+ const spanId = uuidv4();
16
+ const method = (config.method || 'GET').toUpperCase();
17
+ const url = config.url || '';
18
+
19
+ tracer.startSpan(spanId, traceId, `http.${method} ${url}`, 'client');
20
+ tracer.addAttribute(spanId, 'http.method', method);
21
+ tracer.addAttribute(spanId, 'http.url', url);
22
+ tracer.addAttribute(spanId, 'http.base_url', config.baseURL || '');
23
+
24
+ config._nodescope_spanId = spanId;
25
+ return config;
26
+ });
27
+
28
+ // Response interceptor
29
+ axios.interceptors.response.use(
30
+ (response) => {
31
+ const tracer = getInstance();
32
+ const spanId = response.config?._nodescope_spanId;
33
+
34
+ if (tracer && spanId) {
35
+ tracer.addAttribute(spanId, 'http.status_code', response.status);
36
+ tracer.addAttribute(spanId, 'http.response_size', JSON.stringify(response.data || '').length);
37
+ tracer.endSpan(spanId);
38
+ }
39
+
40
+ return response;
41
+ },
42
+ (error) => {
43
+ const tracer = getInstance();
44
+ const spanId = error.config?._nodescope_spanId;
45
+
46
+ if (tracer && spanId) {
47
+ tracer.addAttribute(spanId, 'http.status_code', error.response?.status || 0);
48
+ tracer.endSpan(spanId, error);
49
+ }
50
+
51
+ throw error;
52
+ }
53
+ );
54
+
55
+ axios.__nodescope_patched__ = true;
56
+ console.log('✅ NodeScope: Axios instrumented');
57
+ return axios;
58
+ }
59
+
60
+ module.exports = { patchAxios };
File without changes
@@ -0,0 +1,57 @@
1
+ const { v4: uuidv4 } = require('uuid');
2
+ const { Context } = require('../context');
3
+ const { getInstance } = require('../tracer');
4
+
5
+ function patchMongoose(mongoose) {
6
+ if (!mongoose || mongoose.__nodescope_patched__) return mongoose;
7
+
8
+ // Mongoose middleware plugin use karo
9
+ mongoose.plugin(function nodeScopePlugin(schema) {
10
+ const ops = [
11
+ 'find', 'findOne', 'findOneAndUpdate', 'findOneAndDelete',
12
+ 'save', 'updateOne', 'updateMany', 'deleteOne', 'deleteMany',
13
+ 'countDocuments', 'aggregate',
14
+ ];
15
+
16
+ ops.forEach((op) => {
17
+ schema.pre(op, function () {
18
+ const tracer = getInstance();
19
+ const traceId = Context.getTraceId();
20
+ if (!tracer || !traceId) return;
21
+
22
+ const spanId = uuidv4();
23
+ const modelName = this?.model?.modelName || this?.modelName || 'Model';
24
+
25
+ this._nodescope_spanId = spanId;
26
+
27
+ tracer.startSpan(spanId, traceId, `mongo.${op} ${modelName}`, 'client');
28
+ tracer.addAttribute(spanId, 'db.system', 'mongodb');
29
+ tracer.addAttribute(spanId, 'db.operation', op);
30
+ tracer.addAttribute(spanId, 'db.collection', modelName);
31
+ });
32
+
33
+ schema.post(op, function (result) {
34
+ const tracer = getInstance();
35
+ const spanId = this._nodescope_spanId;
36
+ if (!tracer || !spanId) return;
37
+
38
+ const count = Array.isArray(result) ? result.length : result ? 1 : 0;
39
+ tracer.addAttribute(spanId, 'db.rows_returned', count);
40
+ tracer.endSpan(spanId);
41
+ });
42
+
43
+ schema.post(op, function (err, result, next) {
44
+ const tracer = getInstance();
45
+ const spanId = this._nodescope_spanId;
46
+ if (tracer && spanId) tracer.endSpan(spanId, err);
47
+ if (next) next(err);
48
+ });
49
+ });
50
+ });
51
+
52
+ mongoose.__nodescope_patched__ = true;
53
+ console.log('✅ NodeScope: MongoDB/Mongoose instrumented');
54
+ return mongoose;
55
+ }
56
+
57
+ module.exports = { patchMongoose };
@@ -0,0 +1,62 @@
1
+ const { v4: uuidv4 } = require('uuid');
2
+ const { Context } = require('../context');
3
+ const { getInstance } = require('../tracer');
4
+
5
+ function patchMysql(mysql) {
6
+ if (!mysql || mysql.__nodescope_patched__) return mysql;
7
+
8
+ const originalCreateConnection = mysql.createConnection;
9
+ const originalCreatePool = mysql.createPool;
10
+
11
+ function patchConnection(conn) {
12
+ const originalQuery = conn.query.bind(conn);
13
+
14
+ conn.query = function (sql, params, callback) {
15
+ const tracer = getInstance();
16
+ const traceId = Context.getTraceId();
17
+
18
+ if (!tracer || !traceId) {
19
+ return originalQuery(sql, params, callback);
20
+ }
21
+
22
+ const spanId = uuidv4();
23
+ const queryText = typeof sql === 'string' ? sql : sql?.sql || '';
24
+ const operation = queryText.trim().split(/\s+/)[0].toUpperCase();
25
+ const tableMatch = queryText.match(/(?:FROM|INTO|UPDATE|TABLE)\s+`?(\w+)`?/i);
26
+ const table = tableMatch ? tableMatch[1] : 'unknown';
27
+
28
+ tracer.startSpan(spanId, traceId, `mysql.${operation} ${table}`, 'client');
29
+ tracer.addAttribute(spanId, 'db.system', 'mysql');
30
+ tracer.addAttribute(spanId, 'db.operation', operation);
31
+ tracer.addAttribute(spanId, 'db.table', table);
32
+ tracer.addAttribute(spanId, 'db.query', queryText.substring(0, 300));
33
+
34
+ const startTime = Date.now();
35
+
36
+ return originalQuery(sql, params, (err, results) => {
37
+ if (err) {
38
+ tracer.endSpan(spanId, err);
39
+ } else {
40
+ tracer.addAttribute(spanId, 'db.rows_returned', Array.isArray(results) ? results.length : 0);
41
+ tracer.endSpan(spanId);
42
+ }
43
+ if (callback) callback(err, results);
44
+ });
45
+ };
46
+
47
+ return conn;
48
+ }
49
+
50
+ if (originalCreateConnection) {
51
+ mysql.createConnection = function (...args) {
52
+ const conn = originalCreateConnection.apply(this, args);
53
+ return patchConnection(conn);
54
+ };
55
+ }
56
+
57
+ mysql.__nodescope_patched__ = true;
58
+ console.log('✅ NodeScope: MySQL instrumented');
59
+ return mysql;
60
+ }
61
+
62
+ module.exports = { patchMysql };
package/patches/pg.js ADDED
@@ -0,0 +1,73 @@
1
+ const { v4: uuidv4 } = require('uuid');
2
+ const { Context } = require('../context');
3
+ const { getInstance } = require('../tracer');
4
+
5
+ function patchPg(pg) {
6
+ if (!pg || pg.__nodescope_patched__) return pg;
7
+
8
+ function patch(ClientClass) {
9
+ if (!ClientClass?.prototype?.query) return;
10
+
11
+ const original = ClientClass.prototype.query;
12
+
13
+ ClientClass.prototype.query = function (...args) {
14
+ const tracer = getInstance();
15
+ const traceId = Context.getTraceId();
16
+
17
+ if (!tracer || !traceId) {
18
+ return original.apply(this, args);
19
+ }
20
+
21
+ const spanId = uuidv4();
22
+ let queryText = '';
23
+ let queryParams = [];
24
+
25
+ if (typeof args[0] === 'string') {
26
+ queryText = args[0];
27
+ queryParams = args[1] || [];
28
+ } else if (args[0]?.text) {
29
+ queryText = args[0].text;
30
+ queryParams = args[0].values || [];
31
+ }
32
+
33
+ // Operation aur table detect karo
34
+ const operation = queryText.trim().split(/\s+/)[0].toUpperCase() || 'QUERY';
35
+ const tableMatch = queryText.match(/(?:FROM|INTO|UPDATE|TABLE|JOIN)\s+["']?(\w+)["']?/i);
36
+ const table = tableMatch ? tableMatch[1] : 'unknown';
37
+
38
+ tracer.startSpan(spanId, traceId, `pg.${operation} ${table}`, 'client');
39
+ tracer.addAttribute(spanId, 'db.system', 'postgresql');
40
+ tracer.addAttribute(spanId, 'db.operation', operation);
41
+ tracer.addAttribute(spanId, 'db.table', table);
42
+ tracer.addAttribute(spanId, 'db.query', queryText.substring(0, 300));
43
+ tracer.addAttribute(spanId, 'db.params_count', queryParams.length);
44
+
45
+ const result = original.apply(this, args);
46
+
47
+ if (result?.then) {
48
+ return result
49
+ .then((res) => {
50
+ tracer.addAttribute(spanId, 'db.rows_returned', res?.rows?.length ?? 0);
51
+ tracer.endSpan(spanId);
52
+ return res;
53
+ })
54
+ .catch((err) => {
55
+ tracer.endSpan(spanId, err);
56
+ throw err;
57
+ });
58
+ }
59
+
60
+ tracer.endSpan(spanId);
61
+ return result;
62
+ };
63
+ }
64
+
65
+ if (pg.Pool) patch(pg.Pool);
66
+ if (pg.Client) patch(pg.Client);
67
+
68
+ pg.__nodescope_patched__ = true;
69
+ console.log('✅ NodeScope: PostgreSQL instrumented');
70
+ return pg;
71
+ }
72
+
73
+ module.exports = { patchPg };
@@ -0,0 +1,44 @@
1
+ const { v4: uuidv4 } = require('uuid');
2
+ const { Context } = require('../context');
3
+ const { getInstance } = require('../tracer');
4
+
5
+ function patchRedis(redisClient) {
6
+ if (!redisClient || redisClient.__nodescope_patched__) return redisClient;
7
+
8
+ // Redis v4 (modern)
9
+ if (redisClient.sendCommand) {
10
+ const original = redisClient.sendCommand.bind(redisClient);
11
+
12
+ redisClient.sendCommand = async function (args) {
13
+ const tracer = getInstance();
14
+ const traceId = Context.getTraceId();
15
+
16
+ if (!tracer || !traceId) return original(args);
17
+
18
+ const spanId = uuidv4();
19
+ const command = Array.isArray(args) ? args[0] : 'CMD';
20
+ const key = Array.isArray(args) && args[1] ? String(args[1]).substring(0, 50) : '';
21
+
22
+ tracer.startSpan(spanId, traceId, `redis.${command} ${key}`.trim(), 'client');
23
+ tracer.addAttribute(spanId, 'db.system', 'redis');
24
+ tracer.addAttribute(spanId, 'db.operation', command);
25
+ if (key) tracer.addAttribute(spanId, 'db.key', key);
26
+
27
+ try {
28
+ const result = await original(args);
29
+ tracer.addAttribute(spanId, 'cache.hit', result !== null && result !== undefined);
30
+ tracer.endSpan(spanId);
31
+ return result;
32
+ } catch (err) {
33
+ tracer.endSpan(spanId, err);
34
+ throw err;
35
+ }
36
+ };
37
+ }
38
+
39
+ redisClient.__nodescope_patched__ = true;
40
+ console.log('✅ NodeScope: Redis instrumented');
41
+ return redisClient;
42
+ }
43
+
44
+ module.exports = { patchRedis };
package/tracer.js ADDED
@@ -0,0 +1,160 @@
1
+ const { io } = require('socket.io-client');
2
+ const { Context } = require('./context');
3
+
4
+ class Tracer {
5
+ constructor(config) {
6
+ this.config = {
7
+ serverUrl: config.serverUrl || 'http://localhost:3001',
8
+ appName: config.appName || 'my-app',
9
+ environment: config.environment || 'development',
10
+ enabled: config.enabled !== false,
11
+ };
12
+
13
+ this.socket = null;
14
+ this.connected = false;
15
+ this.buffer = [];
16
+ this.traces = new Map();
17
+
18
+ if (this.config.enabled) {
19
+ this._connect();
20
+ }
21
+ }
22
+
23
+ _connect() {
24
+ try {
25
+ this.socket = io(this.config.serverUrl, {
26
+ auth: {
27
+ appName: this.config.appName,
28
+ environment: this.config.environment,
29
+ },
30
+ reconnection: true,
31
+ reconnectionDelay: 2000,
32
+ timeout: 5000,
33
+ });
34
+
35
+ this.socket.on('connect', () => {
36
+ console.log(`✅ NodeScope connected → ${this.config.serverUrl}`);
37
+ this.connected = true;
38
+ this._flushBuffer();
39
+ });
40
+
41
+ this.socket.on('disconnect', () => {
42
+ console.log('⚠️ NodeScope disconnected');
43
+ this.connected = false;
44
+ });
45
+
46
+ this.socket.on('connect_error', (err) => {
47
+ // Silent fail - app ko affect na kare
48
+ });
49
+
50
+ } catch (err) {
51
+ // Silent fail
52
+ }
53
+ }
54
+
55
+ _send(trace) {
56
+ if (this.connected && this.socket) {
57
+ this.socket.emit('trace', trace);
58
+ } else {
59
+ this.buffer.push(trace);
60
+ if (this.buffer.length > 500) this.buffer.shift();
61
+ }
62
+ }
63
+
64
+ _flushBuffer() {
65
+ if (this.buffer.length > 0) {
66
+ this.buffer.forEach(trace => this.socket.emit('trace', trace));
67
+ this.buffer = [];
68
+ }
69
+ }
70
+
71
+ startTrace(traceId, method, path) {
72
+ this.traces.set(traceId, {
73
+ traceId,
74
+ method,
75
+ path,
76
+ startTime: Date.now(),
77
+ });
78
+ }
79
+
80
+ endTrace(traceId, statusCode, metadata) {
81
+ const trace = this.traces.get(traceId);
82
+ if (!trace) return;
83
+
84
+ const now = Date.now();
85
+ const fullTrace = {
86
+ traceId: trace.traceId,
87
+ method: trace.method,
88
+ path: trace.path,
89
+ statusCode,
90
+ startTime: trace.startTime,
91
+ endTime: now,
92
+ duration: now - trace.startTime,
93
+ spans: Context.getAllSpans(),
94
+ metadata: metadata || {},
95
+ };
96
+
97
+ this._send(fullTrace);
98
+ this.traces.delete(traceId);
99
+ }
100
+
101
+ startSpan(spanId, traceId, name, kind, parentId) {
102
+ const span = {
103
+ id: spanId,
104
+ traceId,
105
+ parentId: parentId || Context.getCurrentSpanId() || undefined,
106
+ name,
107
+ kind: kind || 'internal',
108
+ timestamp: Date.now(),
109
+ duration: 0,
110
+ attributes: {},
111
+ status: 'ok',
112
+ events: [],
113
+ };
114
+
115
+ Context.addSpan(span);
116
+ Context.setCurrentSpanId(spanId);
117
+ return span;
118
+ }
119
+
120
+ endSpan(spanId, error) {
121
+ const span = Context.getSpan(spanId);
122
+ if (!span) return;
123
+
124
+ span.duration = Date.now() - span.timestamp;
125
+
126
+ if (error) {
127
+ span.status = 'error';
128
+ span.error = {
129
+ message: error.message,
130
+ stack: error.stack,
131
+ type: error.name,
132
+ };
133
+ }
134
+
135
+ if (span.parentId) {
136
+ Context.setCurrentSpanId(span.parentId);
137
+ }
138
+ }
139
+
140
+ addAttribute(spanId, key, value) {
141
+ const span = Context.getSpan(spanId);
142
+ if (span) span.attributes[key] = value;
143
+ }
144
+ }
145
+
146
+ // Singleton instance
147
+ let instance = null;
148
+
149
+ function getInstance(config) {
150
+ if (!instance && config) {
151
+ instance = new Tracer(config);
152
+ }
153
+ return instance;
154
+ }
155
+
156
+ function resetInstance() {
157
+ instance = null;
158
+ }
159
+
160
+ module.exports = { Tracer, getInstance, resetInstance };