@devskin/agent 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/README.md +156 -0
- package/dist/agent.d.ts +28 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +221 -0
- package/dist/agent.js.map +1 -0
- package/dist/api-client.d.ts +14 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +102 -0
- package/dist/api-client.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/instrumentation/express.d.ts +5 -0
- package/dist/instrumentation/express.d.ts.map +1 -0
- package/dist/instrumentation/express.js +100 -0
- package/dist/instrumentation/express.js.map +1 -0
- package/dist/instrumentation/http.d.ts +3 -0
- package/dist/instrumentation/http.d.ts.map +1 -0
- package/dist/instrumentation/http.js +144 -0
- package/dist/instrumentation/http.js.map +1 -0
- package/dist/span.d.ts +21 -0
- package/dist/span.d.ts.map +1 -0
- package/dist/span.js +105 -0
- package/dist/span.js.map +1 -0
- package/dist/types.d.ts +75 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/context.d.ts +19 -0
- package/dist/utils/context.d.ts.map +1 -0
- package/dist/utils/context.js +42 -0
- package/dist/utils/context.js.map +1 -0
- package/dist/utils/id-generator.d.ts +4 -0
- package/dist/utils/id-generator.d.ts.map +1 -0
- package/dist/utils/id-generator.js +16 -0
- package/dist/utils/id-generator.js.map +1 -0
- package/package.json +46 -0
- package/src/agent.ts +276 -0
- package/src/api-client.ts +125 -0
- package/src/index.ts +32 -0
- package/src/instrumentation/express.ts +143 -0
- package/src/instrumentation/http.ts +180 -0
- package/src/span.ts +178 -0
- package/src/types.ts +128 -0
- package/src/utils/context.ts +87 -0
- package/src/utils/id-generator.ts +22 -0
- package/tsconfig.json +28 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import * as http from 'http';
|
|
2
|
+
import * as https from 'https';
|
|
3
|
+
import { Agent } from '../agent';
|
|
4
|
+
import { SpanBuilder, TransactionBuilder } from '../span';
|
|
5
|
+
import { SpanKind } from '../types';
|
|
6
|
+
import { Context } from '../utils/context';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Instrument HTTP module
|
|
10
|
+
*/
|
|
11
|
+
export function instrumentHttp(agent: Agent): void {
|
|
12
|
+
const config = agent.getConfig();
|
|
13
|
+
|
|
14
|
+
// Instrument outgoing HTTP requests
|
|
15
|
+
instrumentHttpRequest(http, agent);
|
|
16
|
+
instrumentHttpRequest(https, agent);
|
|
17
|
+
|
|
18
|
+
// Instrument incoming HTTP requests (server)
|
|
19
|
+
instrumentHttpServer(http, agent);
|
|
20
|
+
instrumentHttpServer(https, agent);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Instrument outgoing HTTP requests
|
|
25
|
+
*/
|
|
26
|
+
function instrumentHttpRequest(module: typeof http | typeof https, agent: Agent): void {
|
|
27
|
+
const originalRequest = module.request;
|
|
28
|
+
|
|
29
|
+
(module as any).request = function (
|
|
30
|
+
this: any,
|
|
31
|
+
...args: any[]
|
|
32
|
+
) {
|
|
33
|
+
let options = args[0];
|
|
34
|
+
let callback = args[1];
|
|
35
|
+
|
|
36
|
+
// Handle overloaded signatures
|
|
37
|
+
if (typeof options === 'string') {
|
|
38
|
+
options = new URL(options);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Create a span for the outgoing request
|
|
42
|
+
const config = agent.getConfig();
|
|
43
|
+
const url = typeof args[0] === 'string' ? args[0] : options.href || `${options.protocol}//${options.host || options.hostname}${options.path}`;
|
|
44
|
+
|
|
45
|
+
const span = new SpanBuilder(
|
|
46
|
+
`HTTP ${options.method || 'GET'}`,
|
|
47
|
+
SpanKind.CLIENT,
|
|
48
|
+
config.serviceName,
|
|
49
|
+
config.serviceVersion,
|
|
50
|
+
config.environment,
|
|
51
|
+
agent
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
span.setAttributes({
|
|
55
|
+
'http.method': options.method || 'GET',
|
|
56
|
+
'http.url': url,
|
|
57
|
+
'http.target': options.path || '/',
|
|
58
|
+
'net.peer.name': options.hostname || options.host,
|
|
59
|
+
'net.peer.port': options.port,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Add trace context to outgoing request headers
|
|
63
|
+
const traceId = Context.getTraceId();
|
|
64
|
+
const spanId = span.getSpan().span_id;
|
|
65
|
+
|
|
66
|
+
if (!options.headers) {
|
|
67
|
+
options.headers = {};
|
|
68
|
+
}
|
|
69
|
+
options.headers['x-trace-id'] = traceId;
|
|
70
|
+
options.headers['x-span-id'] = spanId;
|
|
71
|
+
|
|
72
|
+
// Wrap callback
|
|
73
|
+
const wrappedCallback = (res: http.IncomingMessage) => {
|
|
74
|
+
span.setAttributes({
|
|
75
|
+
'http.status_code': res.statusCode,
|
|
76
|
+
'http.response.size': res.headers['content-length'],
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
80
|
+
span.setStatus('error' as any, `HTTP ${res.statusCode}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
res.on('end', () => {
|
|
84
|
+
span.end();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (callback) {
|
|
88
|
+
callback(res);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const req = originalRequest.call(this, options, wrappedCallback);
|
|
93
|
+
|
|
94
|
+
req.on('error', (error: Error) => {
|
|
95
|
+
span.recordError(error);
|
|
96
|
+
span.end();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return req;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Instrument incoming HTTP requests (server)
|
|
105
|
+
*/
|
|
106
|
+
function instrumentHttpServer(module: typeof http | typeof https, agent: Agent): void {
|
|
107
|
+
const originalCreateServer = module.createServer;
|
|
108
|
+
|
|
109
|
+
(module as any).createServer = function (
|
|
110
|
+
this: any,
|
|
111
|
+
...args: any[]
|
|
112
|
+
): http.Server | https.Server {
|
|
113
|
+
const server = originalCreateServer.call(this, ...args) as http.Server | https.Server;
|
|
114
|
+
const config = agent.getConfig();
|
|
115
|
+
|
|
116
|
+
// Wrap the request listener
|
|
117
|
+
server.on('request', (req: http.IncomingMessage, res: http.ServerResponse) => {
|
|
118
|
+
// Check if we should sample this request
|
|
119
|
+
if (!agent.shouldSample()) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Extract trace context from headers
|
|
124
|
+
const incomingTraceId = req.headers['x-trace-id'] as string;
|
|
125
|
+
const incomingSpanId = req.headers['x-span-id'] as string;
|
|
126
|
+
|
|
127
|
+
const transaction = new TransactionBuilder(
|
|
128
|
+
`${req.method} ${req.url}`,
|
|
129
|
+
'http.request',
|
|
130
|
+
config.serviceName,
|
|
131
|
+
config.serviceVersion,
|
|
132
|
+
config.environment,
|
|
133
|
+
true,
|
|
134
|
+
agent
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// If there's an incoming trace ID, use it
|
|
138
|
+
if (incomingTraceId) {
|
|
139
|
+
transaction.getTransaction().trace_id = incomingTraceId;
|
|
140
|
+
if (incomingSpanId) {
|
|
141
|
+
transaction.getTransaction().parent_span_id = incomingSpanId;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
transaction.setAttributes({
|
|
146
|
+
'http.method': req.method,
|
|
147
|
+
'http.url': req.url,
|
|
148
|
+
'http.target': req.url,
|
|
149
|
+
'http.host': req.headers.host,
|
|
150
|
+
'http.scheme': (req as any).protocol || 'http',
|
|
151
|
+
'http.user_agent': req.headers['user-agent'],
|
|
152
|
+
'net.peer.ip': req.socket.remoteAddress,
|
|
153
|
+
'net.peer.port': req.socket.remotePort,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Run the rest of the request handling in context
|
|
157
|
+
Context.run({ transaction: transaction.getTransaction() }, () => {
|
|
158
|
+
const originalEnd = res.end;
|
|
159
|
+
|
|
160
|
+
(res as any).end = function (this: http.ServerResponse, ...endArgs: any[]) {
|
|
161
|
+
transaction.setAttributes({
|
|
162
|
+
'http.status_code': res.statusCode,
|
|
163
|
+
'http.response.size': res.getHeader('content-length'),
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
if (res.statusCode >= 400) {
|
|
167
|
+
transaction.setStatus('error' as any, `HTTP ${res.statusCode}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
transaction.setResult(res.statusCode < 400 ? 'success' : 'error');
|
|
171
|
+
transaction.end();
|
|
172
|
+
|
|
173
|
+
return originalEnd.call(this, ...endArgs);
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return server;
|
|
179
|
+
};
|
|
180
|
+
}
|
package/src/span.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { Span, SpanKind, SpanStatus, SpanEvent, Transaction } from './types';
|
|
2
|
+
import { generateSpanId, generateTraceId } from './utils/id-generator';
|
|
3
|
+
import { Context } from './utils/context';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Span builder for creating and managing spans
|
|
7
|
+
*/
|
|
8
|
+
export class SpanBuilder {
|
|
9
|
+
private span: Span;
|
|
10
|
+
private agent: any; // Reference to agent for reporting
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
name: string,
|
|
14
|
+
kind: SpanKind,
|
|
15
|
+
serviceName: string,
|
|
16
|
+
serviceVersion?: string,
|
|
17
|
+
environment?: string,
|
|
18
|
+
agent?: any
|
|
19
|
+
) {
|
|
20
|
+
const parentSpan = Context.getCurrentSpan();
|
|
21
|
+
const traceId = Context.getTraceId() || generateTraceId();
|
|
22
|
+
|
|
23
|
+
this.span = {
|
|
24
|
+
span_id: generateSpanId(),
|
|
25
|
+
trace_id: traceId,
|
|
26
|
+
parent_span_id: parentSpan?.span_id,
|
|
27
|
+
name,
|
|
28
|
+
kind,
|
|
29
|
+
start_time: new Date(),
|
|
30
|
+
status: SpanStatus.OK,
|
|
31
|
+
attributes: {},
|
|
32
|
+
events: [],
|
|
33
|
+
service_name: serviceName,
|
|
34
|
+
service_version: serviceVersion,
|
|
35
|
+
environment,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
this.agent = agent;
|
|
39
|
+
|
|
40
|
+
// Set this span as current in context
|
|
41
|
+
Context.setSpan(this.span);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Set an attribute on the span
|
|
46
|
+
*/
|
|
47
|
+
setAttribute(key: string, value: any): this {
|
|
48
|
+
this.span.attributes[key] = value;
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Set multiple attributes
|
|
54
|
+
*/
|
|
55
|
+
setAttributes(attributes: Record<string, any>): this {
|
|
56
|
+
Object.assign(this.span.attributes, attributes);
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Add an event to the span
|
|
62
|
+
*/
|
|
63
|
+
addEvent(name: string, attributes?: Record<string, any>): this {
|
|
64
|
+
this.span.events.push({
|
|
65
|
+
timestamp: new Date(),
|
|
66
|
+
name,
|
|
67
|
+
attributes,
|
|
68
|
+
});
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Set the span status
|
|
74
|
+
*/
|
|
75
|
+
setStatus(status: SpanStatus, message?: string): this {
|
|
76
|
+
this.span.status = status;
|
|
77
|
+
if (message) {
|
|
78
|
+
this.span.status_message = message;
|
|
79
|
+
}
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Mark the span as having an error
|
|
85
|
+
*/
|
|
86
|
+
recordError(error: Error): this {
|
|
87
|
+
this.setStatus(SpanStatus.ERROR, error.message);
|
|
88
|
+
this.setAttributes({
|
|
89
|
+
'error.type': error.name,
|
|
90
|
+
'error.message': error.message,
|
|
91
|
+
'error.stack': error.stack,
|
|
92
|
+
});
|
|
93
|
+
this.addEvent('exception', {
|
|
94
|
+
'exception.type': error.name,
|
|
95
|
+
'exception.message': error.message,
|
|
96
|
+
});
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* End the span
|
|
102
|
+
*/
|
|
103
|
+
end(): void {
|
|
104
|
+
this.span.end_time = new Date();
|
|
105
|
+
this.span.duration_ms = this.span.end_time.getTime() - this.span.start_time.getTime();
|
|
106
|
+
|
|
107
|
+
// Report span to agent
|
|
108
|
+
if (this.agent && typeof this.agent.reportSpan === 'function') {
|
|
109
|
+
this.agent.reportSpan(this.span);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get the span data
|
|
115
|
+
*/
|
|
116
|
+
getSpan(): Span {
|
|
117
|
+
return this.span;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Transaction builder for creating root spans (transactions)
|
|
123
|
+
*/
|
|
124
|
+
export class TransactionBuilder extends SpanBuilder {
|
|
125
|
+
private transaction: Transaction;
|
|
126
|
+
|
|
127
|
+
constructor(
|
|
128
|
+
name: string,
|
|
129
|
+
type: string,
|
|
130
|
+
serviceName: string,
|
|
131
|
+
serviceVersion?: string,
|
|
132
|
+
environment?: string,
|
|
133
|
+
sampled = true,
|
|
134
|
+
agent?: any
|
|
135
|
+
) {
|
|
136
|
+
super(name, SpanKind.SERVER, serviceName, serviceVersion, environment, agent);
|
|
137
|
+
|
|
138
|
+
const span = this.getSpan();
|
|
139
|
+
this.transaction = {
|
|
140
|
+
...span,
|
|
141
|
+
transaction_type: type,
|
|
142
|
+
transaction_name: name,
|
|
143
|
+
sampled,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// Set transaction in context
|
|
147
|
+
Context.setTransaction(this.transaction);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Set the transaction result
|
|
152
|
+
*/
|
|
153
|
+
setResult(result: string): this {
|
|
154
|
+
this.transaction.result = result;
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* End the transaction
|
|
160
|
+
*/
|
|
161
|
+
end(): void {
|
|
162
|
+
this.transaction.end_time = new Date();
|
|
163
|
+
this.transaction.duration_ms =
|
|
164
|
+
this.transaction.end_time.getTime() - this.transaction.start_time.getTime();
|
|
165
|
+
|
|
166
|
+
// Report transaction to agent
|
|
167
|
+
if ((this as any).agent && typeof (this as any).agent.reportTransaction === 'function') {
|
|
168
|
+
(this as any).agent.reportTransaction(this.transaction);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get the transaction data
|
|
174
|
+
*/
|
|
175
|
+
getTransaction(): Transaction {
|
|
176
|
+
return this.transaction;
|
|
177
|
+
}
|
|
178
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for DevSkin APM Agent
|
|
3
|
+
*/
|
|
4
|
+
export interface AgentConfig {
|
|
5
|
+
/** DevSkin backend URL */
|
|
6
|
+
serverUrl: string;
|
|
7
|
+
|
|
8
|
+
/** API key for authentication */
|
|
9
|
+
apiKey: string;
|
|
10
|
+
|
|
11
|
+
/** Service name */
|
|
12
|
+
serviceName: string;
|
|
13
|
+
|
|
14
|
+
/** Service version */
|
|
15
|
+
serviceVersion?: string;
|
|
16
|
+
|
|
17
|
+
/** Environment (production, staging, development) */
|
|
18
|
+
environment?: string;
|
|
19
|
+
|
|
20
|
+
/** Enable/disable the agent */
|
|
21
|
+
enabled?: boolean;
|
|
22
|
+
|
|
23
|
+
/** Sample rate (0.0 to 1.0) */
|
|
24
|
+
sampleRate?: number;
|
|
25
|
+
|
|
26
|
+
/** Enable HTTP instrumentation */
|
|
27
|
+
instrumentHttp?: boolean;
|
|
28
|
+
|
|
29
|
+
/** Enable Express instrumentation */
|
|
30
|
+
instrumentExpress?: boolean;
|
|
31
|
+
|
|
32
|
+
/** Batch size for sending data */
|
|
33
|
+
batchSize?: number;
|
|
34
|
+
|
|
35
|
+
/** Flush interval in milliseconds */
|
|
36
|
+
flushInterval?: number;
|
|
37
|
+
|
|
38
|
+
/** Enable debug logging */
|
|
39
|
+
debug?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Span kind
|
|
44
|
+
*/
|
|
45
|
+
export enum SpanKind {
|
|
46
|
+
SERVER = 'server',
|
|
47
|
+
CLIENT = 'client',
|
|
48
|
+
INTERNAL = 'internal',
|
|
49
|
+
PRODUCER = 'producer',
|
|
50
|
+
CONSUMER = 'consumer',
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Span status
|
|
55
|
+
*/
|
|
56
|
+
export enum SpanStatus {
|
|
57
|
+
OK = 'ok',
|
|
58
|
+
ERROR = 'error',
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Span data structure
|
|
63
|
+
*/
|
|
64
|
+
export interface Span {
|
|
65
|
+
span_id: string;
|
|
66
|
+
trace_id: string;
|
|
67
|
+
parent_span_id?: string;
|
|
68
|
+
name: string;
|
|
69
|
+
kind: SpanKind;
|
|
70
|
+
start_time: Date;
|
|
71
|
+
end_time?: Date;
|
|
72
|
+
duration_ms?: number;
|
|
73
|
+
status: SpanStatus;
|
|
74
|
+
status_message?: string;
|
|
75
|
+
attributes: Record<string, any>;
|
|
76
|
+
events: SpanEvent[];
|
|
77
|
+
service_name: string;
|
|
78
|
+
service_version?: string;
|
|
79
|
+
environment?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Span event
|
|
84
|
+
*/
|
|
85
|
+
export interface SpanEvent {
|
|
86
|
+
timestamp: Date;
|
|
87
|
+
name: string;
|
|
88
|
+
attributes?: Record<string, any>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Transaction (root span)
|
|
93
|
+
*/
|
|
94
|
+
export interface Transaction extends Span {
|
|
95
|
+
transaction_type: string;
|
|
96
|
+
transaction_name: string;
|
|
97
|
+
result?: string;
|
|
98
|
+
sampled: boolean;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Log entry
|
|
103
|
+
*/
|
|
104
|
+
export interface LogEntry {
|
|
105
|
+
timestamp: Date;
|
|
106
|
+
level: string;
|
|
107
|
+
message: string;
|
|
108
|
+
trace_id?: string;
|
|
109
|
+
span_id?: string;
|
|
110
|
+
attributes?: Record<string, any>;
|
|
111
|
+
service_name: string;
|
|
112
|
+
environment?: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Error data
|
|
117
|
+
*/
|
|
118
|
+
export interface ErrorData {
|
|
119
|
+
timestamp: Date;
|
|
120
|
+
message: string;
|
|
121
|
+
type: string;
|
|
122
|
+
stack_trace?: string;
|
|
123
|
+
trace_id?: string;
|
|
124
|
+
span_id?: string;
|
|
125
|
+
attributes?: Record<string, any>;
|
|
126
|
+
service_name: string;
|
|
127
|
+
environment?: string;
|
|
128
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
2
|
+
import { Span, Transaction } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Context data stored in AsyncLocalStorage
|
|
6
|
+
*/
|
|
7
|
+
interface ContextData {
|
|
8
|
+
transaction?: Transaction;
|
|
9
|
+
currentSpan?: Span;
|
|
10
|
+
traceId?: string;
|
|
11
|
+
spanId?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* AsyncLocalStorage instance for context propagation
|
|
16
|
+
*/
|
|
17
|
+
const asyncLocalStorage = new AsyncLocalStorage<ContextData>();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Context manager for maintaining trace context across async operations
|
|
21
|
+
*/
|
|
22
|
+
export class Context {
|
|
23
|
+
/**
|
|
24
|
+
* Run a function with a new context
|
|
25
|
+
*/
|
|
26
|
+
static run<T>(context: ContextData, fn: () => T): T {
|
|
27
|
+
return asyncLocalStorage.run(context, fn);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get the current context
|
|
32
|
+
*/
|
|
33
|
+
static get(): ContextData | undefined {
|
|
34
|
+
return asyncLocalStorage.getStore();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get the current transaction
|
|
39
|
+
*/
|
|
40
|
+
static getCurrentTransaction(): Transaction | undefined {
|
|
41
|
+
return asyncLocalStorage.getStore()?.transaction;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get the current span
|
|
46
|
+
*/
|
|
47
|
+
static getCurrentSpan(): Span | undefined {
|
|
48
|
+
return asyncLocalStorage.getStore()?.currentSpan;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get the current trace ID
|
|
53
|
+
*/
|
|
54
|
+
static getTraceId(): string | undefined {
|
|
55
|
+
return asyncLocalStorage.getStore()?.traceId;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get the current span ID
|
|
60
|
+
*/
|
|
61
|
+
static getSpanId(): string | undefined {
|
|
62
|
+
return asyncLocalStorage.getStore()?.spanId;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Set the current transaction
|
|
67
|
+
*/
|
|
68
|
+
static setTransaction(transaction: Transaction): void {
|
|
69
|
+
const store = asyncLocalStorage.getStore();
|
|
70
|
+
if (store) {
|
|
71
|
+
store.transaction = transaction;
|
|
72
|
+
store.traceId = transaction.trace_id;
|
|
73
|
+
store.spanId = transaction.span_id;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Set the current span
|
|
79
|
+
*/
|
|
80
|
+
static setSpan(span: Span): void {
|
|
81
|
+
const store = asyncLocalStorage.getStore();
|
|
82
|
+
if (store) {
|
|
83
|
+
store.currentSpan = span;
|
|
84
|
+
store.spanId = span.span_id;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate a random trace ID (16 bytes / 32 hex chars)
|
|
5
|
+
*/
|
|
6
|
+
export function generateTraceId(): string {
|
|
7
|
+
return randomBytes(16).toString('hex');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate a random span ID (8 bytes / 16 hex chars)
|
|
12
|
+
*/
|
|
13
|
+
export function generateSpanId(): string {
|
|
14
|
+
return randomBytes(8).toString('hex');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Check if a value should be sampled based on sample rate
|
|
19
|
+
*/
|
|
20
|
+
export function shouldSample(sampleRate: number): boolean {
|
|
21
|
+
return Math.random() < sampleRate;
|
|
22
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"sourceMap": true,
|
|
9
|
+
"outDir": "./dist",
|
|
10
|
+
"rootDir": "./src",
|
|
11
|
+
"baseUrl": "./src",
|
|
12
|
+
"paths": {
|
|
13
|
+
"@/*": ["./*"]
|
|
14
|
+
},
|
|
15
|
+
"removeComments": true,
|
|
16
|
+
"esModuleInterop": true,
|
|
17
|
+
"forceConsistentCasingInFileNames": true,
|
|
18
|
+
"skipLibCheck": true,
|
|
19
|
+
"resolveJsonModule": true,
|
|
20
|
+
"moduleResolution": "node",
|
|
21
|
+
"types": ["node"],
|
|
22
|
+
"strict": false,
|
|
23
|
+
"noImplicitAny": false,
|
|
24
|
+
"strictNullChecks": false
|
|
25
|
+
},
|
|
26
|
+
"include": ["src/**/*"],
|
|
27
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
|
|
28
|
+
}
|