@parsrun/service 0.1.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 +375 -0
- package/dist/client.d.ts +55 -0
- package/dist/client.js +1474 -0
- package/dist/client.js.map +1 -0
- package/dist/define.d.ts +82 -0
- package/dist/define.js +120 -0
- package/dist/define.js.map +1 -0
- package/dist/events/index.d.ts +285 -0
- package/dist/events/index.js +853 -0
- package/dist/events/index.js.map +1 -0
- package/dist/handler-CmiDUWZv.d.ts +204 -0
- package/dist/index-CVOAoJjZ.d.ts +268 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +3589 -0
- package/dist/index.js.map +1 -0
- package/dist/resilience/index.d.ts +197 -0
- package/dist/resilience/index.js +387 -0
- package/dist/resilience/index.js.map +1 -0
- package/dist/rpc/index.d.ts +5 -0
- package/dist/rpc/index.js +1175 -0
- package/dist/rpc/index.js.map +1 -0
- package/dist/serialization/index.d.ts +37 -0
- package/dist/serialization/index.js +320 -0
- package/dist/serialization/index.js.map +1 -0
- package/dist/server-DFE8n2Sx.d.ts +106 -0
- package/dist/tracing/index.d.ts +406 -0
- package/dist/tracing/index.js +820 -0
- package/dist/tracing/index.js.map +1 -0
- package/dist/transports/cloudflare/index.d.ts +237 -0
- package/dist/transports/cloudflare/index.js +746 -0
- package/dist/transports/cloudflare/index.js.map +1 -0
- package/dist/types-n4LLSPQU.d.ts +473 -0
- package/package.json +91 -0
|
@@ -0,0 +1,820 @@
|
|
|
1
|
+
// src/tracing/context.ts
|
|
2
|
+
function generateTraceId() {
|
|
3
|
+
const bytes = new Uint8Array(16);
|
|
4
|
+
crypto.getRandomValues(bytes);
|
|
5
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
6
|
+
}
|
|
7
|
+
function generateSpanId() {
|
|
8
|
+
const bytes = new Uint8Array(8);
|
|
9
|
+
crypto.getRandomValues(bytes);
|
|
10
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
11
|
+
}
|
|
12
|
+
function createTraceContext(options) {
|
|
13
|
+
const ctx = {
|
|
14
|
+
traceId: options?.traceId ?? generateTraceId(),
|
|
15
|
+
spanId: options?.spanId ?? generateSpanId(),
|
|
16
|
+
traceFlags: options?.traceFlags ?? 1
|
|
17
|
+
// Default: sampled
|
|
18
|
+
};
|
|
19
|
+
if (options?.traceState) {
|
|
20
|
+
ctx.traceState = options.traceState;
|
|
21
|
+
}
|
|
22
|
+
return ctx;
|
|
23
|
+
}
|
|
24
|
+
function createChildContext(parent) {
|
|
25
|
+
const ctx = {
|
|
26
|
+
traceId: parent.traceId,
|
|
27
|
+
spanId: generateSpanId(),
|
|
28
|
+
traceFlags: parent.traceFlags
|
|
29
|
+
};
|
|
30
|
+
if (parent.traceState) {
|
|
31
|
+
ctx.traceState = parent.traceState;
|
|
32
|
+
}
|
|
33
|
+
return ctx;
|
|
34
|
+
}
|
|
35
|
+
function formatTraceparent(ctx) {
|
|
36
|
+
const version = "00";
|
|
37
|
+
const flags = ctx.traceFlags.toString(16).padStart(2, "0");
|
|
38
|
+
return `${version}-${ctx.traceId}-${ctx.spanId}-${flags}`;
|
|
39
|
+
}
|
|
40
|
+
function parseTraceparent(header) {
|
|
41
|
+
const parts = header.trim().split("-");
|
|
42
|
+
if (parts.length !== 4) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const [version, traceId, spanId, flags] = parts;
|
|
46
|
+
if (version !== "00") {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
if (!traceId || traceId.length !== 32 || !/^[0-9a-f]+$/i.test(traceId)) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
if (!spanId || spanId.length !== 16 || !/^[0-9a-f]+$/i.test(spanId)) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
if (!flags || flags.length !== 2 || !/^[0-9a-f]+$/i.test(flags)) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
traceId: traceId.toLowerCase(),
|
|
60
|
+
spanId: spanId.toLowerCase(),
|
|
61
|
+
traceFlags: parseInt(flags, 16)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function formatTracestate(state) {
|
|
65
|
+
return Object.entries(state).map(([key, value]) => `${key}=${value}`).join(",");
|
|
66
|
+
}
|
|
67
|
+
function parseTracestate(header) {
|
|
68
|
+
const state = {};
|
|
69
|
+
for (const pair of header.split(",")) {
|
|
70
|
+
const [key, value] = pair.trim().split("=");
|
|
71
|
+
if (key && value) {
|
|
72
|
+
state[key] = value;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return state;
|
|
76
|
+
}
|
|
77
|
+
var TraceContextManager = class {
|
|
78
|
+
stack = [];
|
|
79
|
+
/**
|
|
80
|
+
* Get current trace context
|
|
81
|
+
*/
|
|
82
|
+
current() {
|
|
83
|
+
return this.stack[this.stack.length - 1];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Run a function with a trace context
|
|
87
|
+
*/
|
|
88
|
+
async run(ctx, fn) {
|
|
89
|
+
this.stack.push(ctx);
|
|
90
|
+
try {
|
|
91
|
+
return await fn();
|
|
92
|
+
} finally {
|
|
93
|
+
this.stack.pop();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Run a function with a new child context
|
|
98
|
+
*/
|
|
99
|
+
async runChild(fn) {
|
|
100
|
+
const parent = this.current();
|
|
101
|
+
const child = parent ? createChildContext(parent) : createTraceContext();
|
|
102
|
+
return this.run(child, fn);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Create context from incoming request headers
|
|
106
|
+
*/
|
|
107
|
+
fromHeaders(headers) {
|
|
108
|
+
const traceparent = headers instanceof Headers ? headers.get("traceparent") : headers["traceparent"];
|
|
109
|
+
if (!traceparent) {
|
|
110
|
+
return void 0;
|
|
111
|
+
}
|
|
112
|
+
const ctx = parseTraceparent(traceparent);
|
|
113
|
+
if (!ctx) {
|
|
114
|
+
return void 0;
|
|
115
|
+
}
|
|
116
|
+
const tracestate = headers instanceof Headers ? headers.get("tracestate") : headers["tracestate"];
|
|
117
|
+
if (tracestate) {
|
|
118
|
+
ctx.traceState = tracestate;
|
|
119
|
+
}
|
|
120
|
+
return ctx;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Add trace context to outgoing request headers
|
|
124
|
+
*/
|
|
125
|
+
toHeaders(ctx) {
|
|
126
|
+
const headers = {
|
|
127
|
+
traceparent: formatTraceparent(ctx)
|
|
128
|
+
};
|
|
129
|
+
if (ctx.traceState) {
|
|
130
|
+
headers["tracestate"] = ctx.traceState;
|
|
131
|
+
}
|
|
132
|
+
return headers;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Check if current context is sampled
|
|
136
|
+
*/
|
|
137
|
+
isSampled() {
|
|
138
|
+
const ctx = this.current();
|
|
139
|
+
return ctx ? (ctx.traceFlags & 1) === 1 : false;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Clear all contexts (for testing)
|
|
143
|
+
*/
|
|
144
|
+
clear() {
|
|
145
|
+
this.stack.length = 0;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
function shouldSample(sampler, traceId) {
|
|
149
|
+
if (sampler === "always") {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
if (sampler === "never") {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
if (traceId) {
|
|
156
|
+
const hash = parseInt(traceId.slice(-8), 16);
|
|
157
|
+
const threshold = Math.floor(sampler.ratio * 4294967295);
|
|
158
|
+
return hash < threshold;
|
|
159
|
+
}
|
|
160
|
+
return Math.random() < sampler.ratio;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/tracing/spans.ts
|
|
164
|
+
function createSpan(options) {
|
|
165
|
+
let traceContext;
|
|
166
|
+
if (options.parent) {
|
|
167
|
+
traceContext = {
|
|
168
|
+
traceId: options.parent.traceId,
|
|
169
|
+
spanId: generateSpanId(),
|
|
170
|
+
traceFlags: options.parent.traceFlags
|
|
171
|
+
};
|
|
172
|
+
if (options.parent.traceState) {
|
|
173
|
+
traceContext.traceState = options.parent.traceState;
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
traceContext = createTraceContext();
|
|
177
|
+
}
|
|
178
|
+
const span = {
|
|
179
|
+
name: options.name,
|
|
180
|
+
kind: options.kind ?? "internal",
|
|
181
|
+
traceContext,
|
|
182
|
+
startTime: options.startTime ?? Date.now(),
|
|
183
|
+
status: "unset",
|
|
184
|
+
attributes: options.attributes ?? {},
|
|
185
|
+
events: []
|
|
186
|
+
};
|
|
187
|
+
if (options.parent?.spanId) {
|
|
188
|
+
span.parentSpanId = options.parent.spanId;
|
|
189
|
+
}
|
|
190
|
+
return span;
|
|
191
|
+
}
|
|
192
|
+
var SpanManager = class {
|
|
193
|
+
spans = /* @__PURE__ */ new Map();
|
|
194
|
+
/**
|
|
195
|
+
* Start a new span
|
|
196
|
+
*/
|
|
197
|
+
startSpan(options) {
|
|
198
|
+
const span = createSpan(options);
|
|
199
|
+
this.spans.set(span.traceContext.spanId, span);
|
|
200
|
+
return span;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* End a span
|
|
204
|
+
*/
|
|
205
|
+
endSpan(span, status) {
|
|
206
|
+
span.endTime = Date.now();
|
|
207
|
+
if (status) {
|
|
208
|
+
span.status = status;
|
|
209
|
+
} else if (span.status === "unset") {
|
|
210
|
+
span.status = "ok";
|
|
211
|
+
}
|
|
212
|
+
return span;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Set span attribute
|
|
216
|
+
*/
|
|
217
|
+
setAttribute(span, key, value) {
|
|
218
|
+
span.attributes[key] = value;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Set multiple span attributes
|
|
222
|
+
*/
|
|
223
|
+
setAttributes(span, attributes) {
|
|
224
|
+
Object.assign(span.attributes, attributes);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Add span event
|
|
228
|
+
*/
|
|
229
|
+
addEvent(span, name, attributes) {
|
|
230
|
+
const event = {
|
|
231
|
+
name,
|
|
232
|
+
time: Date.now()
|
|
233
|
+
};
|
|
234
|
+
if (attributes) {
|
|
235
|
+
event.attributes = attributes;
|
|
236
|
+
}
|
|
237
|
+
span.events.push(event);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Set span status
|
|
241
|
+
*/
|
|
242
|
+
setStatus(span, status) {
|
|
243
|
+
span.status = status;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Record exception on span
|
|
247
|
+
*/
|
|
248
|
+
recordException(span, error) {
|
|
249
|
+
span.status = "error";
|
|
250
|
+
this.addEvent(span, "exception", {
|
|
251
|
+
"exception.type": error.name,
|
|
252
|
+
"exception.message": error.message,
|
|
253
|
+
"exception.stacktrace": error.stack ?? ""
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Get span by ID
|
|
258
|
+
*/
|
|
259
|
+
getSpan(spanId) {
|
|
260
|
+
return this.spans.get(spanId);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Get all completed spans and clear
|
|
264
|
+
*/
|
|
265
|
+
flush() {
|
|
266
|
+
const completed = Array.from(this.spans.values()).filter((s) => s.endTime);
|
|
267
|
+
for (const span of completed) {
|
|
268
|
+
this.spans.delete(span.traceContext.spanId);
|
|
269
|
+
}
|
|
270
|
+
return completed;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Clear all spans
|
|
274
|
+
*/
|
|
275
|
+
clear() {
|
|
276
|
+
this.spans.clear();
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
function getSpanDuration(span) {
|
|
280
|
+
if (!span.endTime) return void 0;
|
|
281
|
+
return span.endTime - span.startTime;
|
|
282
|
+
}
|
|
283
|
+
function isSpanCompleted(span) {
|
|
284
|
+
return span.endTime !== void 0;
|
|
285
|
+
}
|
|
286
|
+
function spanToLogObject(span) {
|
|
287
|
+
return {
|
|
288
|
+
traceId: span.traceContext.traceId,
|
|
289
|
+
spanId: span.traceContext.spanId,
|
|
290
|
+
parentSpanId: span.parentSpanId,
|
|
291
|
+
name: span.name,
|
|
292
|
+
kind: span.kind,
|
|
293
|
+
status: span.status,
|
|
294
|
+
startTime: new Date(span.startTime).toISOString(),
|
|
295
|
+
endTime: span.endTime ? new Date(span.endTime).toISOString() : void 0,
|
|
296
|
+
durationMs: getSpanDuration(span),
|
|
297
|
+
attributes: span.attributes,
|
|
298
|
+
events: span.events.map((e) => ({
|
|
299
|
+
name: e.name,
|
|
300
|
+
time: new Date(e.time).toISOString(),
|
|
301
|
+
attributes: e.attributes
|
|
302
|
+
}))
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
var SpanAttributes = {
|
|
306
|
+
// HTTP
|
|
307
|
+
HTTP_METHOD: "http.method",
|
|
308
|
+
HTTP_URL: "http.url",
|
|
309
|
+
HTTP_STATUS_CODE: "http.status_code",
|
|
310
|
+
HTTP_REQUEST_CONTENT_LENGTH: "http.request_content_length",
|
|
311
|
+
HTTP_RESPONSE_CONTENT_LENGTH: "http.response_content_length",
|
|
312
|
+
// RPC
|
|
313
|
+
RPC_SYSTEM: "rpc.system",
|
|
314
|
+
RPC_SERVICE: "rpc.service",
|
|
315
|
+
RPC_METHOD: "rpc.method",
|
|
316
|
+
// Database
|
|
317
|
+
DB_SYSTEM: "db.system",
|
|
318
|
+
DB_NAME: "db.name",
|
|
319
|
+
DB_OPERATION: "db.operation",
|
|
320
|
+
DB_STATEMENT: "db.statement",
|
|
321
|
+
// Messaging
|
|
322
|
+
MESSAGING_SYSTEM: "messaging.system",
|
|
323
|
+
MESSAGING_DESTINATION: "messaging.destination",
|
|
324
|
+
MESSAGING_MESSAGE_ID: "messaging.message_id",
|
|
325
|
+
// Service
|
|
326
|
+
SERVICE_NAME: "service.name",
|
|
327
|
+
SERVICE_VERSION: "service.version",
|
|
328
|
+
// Error
|
|
329
|
+
EXCEPTION_TYPE: "exception.type",
|
|
330
|
+
EXCEPTION_MESSAGE: "exception.message",
|
|
331
|
+
EXCEPTION_STACKTRACE: "exception.stacktrace",
|
|
332
|
+
// Custom Pars attributes
|
|
333
|
+
PARS_TENANT_ID: "pars.tenant_id",
|
|
334
|
+
PARS_USER_ID: "pars.user_id",
|
|
335
|
+
PARS_REQUEST_ID: "pars.request_id"
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// src/tracing/exporters.ts
|
|
339
|
+
import { createLogger } from "@parsrun/core";
|
|
340
|
+
var ConsoleExporter = class {
|
|
341
|
+
name = "console";
|
|
342
|
+
logger;
|
|
343
|
+
pretty;
|
|
344
|
+
includeAttributes;
|
|
345
|
+
includeEvents;
|
|
346
|
+
constructor(options = {}) {
|
|
347
|
+
this.logger = options.logger ?? createLogger({ name: "trace-exporter" });
|
|
348
|
+
this.pretty = options.pretty ?? true;
|
|
349
|
+
this.includeAttributes = options.includeAttributes ?? true;
|
|
350
|
+
this.includeEvents = options.includeEvents ?? true;
|
|
351
|
+
}
|
|
352
|
+
async export(spans) {
|
|
353
|
+
for (const span of spans) {
|
|
354
|
+
const duration = getSpanDuration(span);
|
|
355
|
+
const status = span.status === "error" ? "ERROR" : span.status === "ok" ? "OK" : "UNSET";
|
|
356
|
+
if (this.pretty) {
|
|
357
|
+
const indent = span.parentSpanId ? " \u2514\u2500" : "\u2500\u2500";
|
|
358
|
+
const statusIcon = span.status === "error" ? "\u2717" : span.status === "ok" ? "\u2713" : "\u25CB";
|
|
359
|
+
const durationStr = duration !== void 0 ? `${duration}ms` : "?ms";
|
|
360
|
+
console.log(
|
|
361
|
+
`${indent} ${statusIcon} [${span.kind}] ${span.name} (${durationStr}) trace=${span.traceContext.traceId.slice(0, 8)}`
|
|
362
|
+
);
|
|
363
|
+
if (this.includeAttributes && Object.keys(span.attributes).length > 0) {
|
|
364
|
+
console.log(` attributes:`, span.attributes);
|
|
365
|
+
}
|
|
366
|
+
if (this.includeEvents && span.events.length > 0) {
|
|
367
|
+
for (const event of span.events) {
|
|
368
|
+
console.log(` event: ${event.name}`, event.attributes ?? "");
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
} else {
|
|
372
|
+
const logObj = spanToLogObject(span);
|
|
373
|
+
this.logger.info(`Span: ${span.name}`, {
|
|
374
|
+
...logObj,
|
|
375
|
+
status,
|
|
376
|
+
durationMs: duration
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
async shutdown() {
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
function createConsoleExporter(options) {
|
|
385
|
+
return new ConsoleExporter(options);
|
|
386
|
+
}
|
|
387
|
+
var OtlpExporter = class {
|
|
388
|
+
name = "otlp";
|
|
389
|
+
endpoint;
|
|
390
|
+
serviceName;
|
|
391
|
+
serviceVersion;
|
|
392
|
+
headers;
|
|
393
|
+
timeout;
|
|
394
|
+
batchSize;
|
|
395
|
+
flushInterval;
|
|
396
|
+
logger;
|
|
397
|
+
buffer = [];
|
|
398
|
+
flushTimer = null;
|
|
399
|
+
constructor(options) {
|
|
400
|
+
this.endpoint = options.endpoint.replace(/\/$/, "");
|
|
401
|
+
this.serviceName = options.serviceName;
|
|
402
|
+
this.serviceVersion = options.serviceVersion ?? "1.0.0";
|
|
403
|
+
this.headers = options.headers ?? {};
|
|
404
|
+
this.timeout = options.timeout ?? 1e4;
|
|
405
|
+
this.batchSize = options.batchSize ?? 100;
|
|
406
|
+
this.flushInterval = options.flushInterval ?? 5e3;
|
|
407
|
+
this.logger = options.logger ?? createLogger({ name: "otlp-exporter" });
|
|
408
|
+
this.flushTimer = setInterval(() => this.flush(), this.flushInterval);
|
|
409
|
+
}
|
|
410
|
+
async export(spans) {
|
|
411
|
+
this.buffer.push(...spans);
|
|
412
|
+
if (this.buffer.length >= this.batchSize) {
|
|
413
|
+
await this.flush();
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
async flush() {
|
|
417
|
+
if (this.buffer.length === 0) return;
|
|
418
|
+
const spansToExport = this.buffer.splice(0, this.batchSize);
|
|
419
|
+
try {
|
|
420
|
+
const payload = this.buildOtlpPayload(spansToExport);
|
|
421
|
+
const controller = new AbortController();
|
|
422
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
423
|
+
try {
|
|
424
|
+
const response = await fetch(`${this.endpoint}/v1/traces`, {
|
|
425
|
+
method: "POST",
|
|
426
|
+
headers: {
|
|
427
|
+
"Content-Type": "application/json",
|
|
428
|
+
...this.headers
|
|
429
|
+
},
|
|
430
|
+
body: JSON.stringify(payload),
|
|
431
|
+
signal: controller.signal
|
|
432
|
+
});
|
|
433
|
+
if (!response.ok) {
|
|
434
|
+
throw new Error(`OTLP export failed: ${response.status} ${response.statusText}`);
|
|
435
|
+
}
|
|
436
|
+
this.logger.debug(`Exported ${spansToExport.length} spans to OTLP`);
|
|
437
|
+
} finally {
|
|
438
|
+
clearTimeout(timeoutId);
|
|
439
|
+
}
|
|
440
|
+
} catch (error) {
|
|
441
|
+
this.logger.error(`Failed to export spans to OTLP`, error);
|
|
442
|
+
this.buffer.unshift(...spansToExport);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
buildOtlpPayload(spans) {
|
|
446
|
+
return {
|
|
447
|
+
resourceSpans: [
|
|
448
|
+
{
|
|
449
|
+
resource: {
|
|
450
|
+
attributes: [
|
|
451
|
+
{ key: "service.name", value: { stringValue: this.serviceName } },
|
|
452
|
+
{ key: "service.version", value: { stringValue: this.serviceVersion } }
|
|
453
|
+
]
|
|
454
|
+
},
|
|
455
|
+
scopeSpans: [
|
|
456
|
+
{
|
|
457
|
+
scope: {
|
|
458
|
+
name: "@parsrun/service",
|
|
459
|
+
version: "0.1.0"
|
|
460
|
+
},
|
|
461
|
+
spans: spans.map((span) => this.convertSpan(span))
|
|
462
|
+
}
|
|
463
|
+
]
|
|
464
|
+
}
|
|
465
|
+
]
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
convertSpan(span) {
|
|
469
|
+
const otlpSpan = {
|
|
470
|
+
traceId: span.traceContext.traceId,
|
|
471
|
+
spanId: span.traceContext.spanId,
|
|
472
|
+
name: span.name,
|
|
473
|
+
kind: this.convertSpanKind(span.kind),
|
|
474
|
+
startTimeUnixNano: String(span.startTime * 1e6),
|
|
475
|
+
attributes: Object.entries(span.attributes).map(([key, value]) => ({
|
|
476
|
+
key,
|
|
477
|
+
value: this.convertAttributeValue(value)
|
|
478
|
+
})),
|
|
479
|
+
events: span.events.map((event) => ({
|
|
480
|
+
name: event.name,
|
|
481
|
+
timeUnixNano: String(event.time * 1e6),
|
|
482
|
+
attributes: event.attributes ? Object.entries(event.attributes).map(([key, value]) => ({
|
|
483
|
+
key,
|
|
484
|
+
value: this.convertAttributeValue(value)
|
|
485
|
+
})) : []
|
|
486
|
+
})),
|
|
487
|
+
status: {
|
|
488
|
+
code: span.status === "error" ? 2 : span.status === "ok" ? 1 : 0
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
if (span.parentSpanId) {
|
|
492
|
+
otlpSpan.parentSpanId = span.parentSpanId;
|
|
493
|
+
}
|
|
494
|
+
if (span.endTime) {
|
|
495
|
+
otlpSpan.endTimeUnixNano = String(span.endTime * 1e6);
|
|
496
|
+
}
|
|
497
|
+
return otlpSpan;
|
|
498
|
+
}
|
|
499
|
+
convertSpanKind(kind) {
|
|
500
|
+
switch (kind) {
|
|
501
|
+
case "internal":
|
|
502
|
+
return 1;
|
|
503
|
+
case "server":
|
|
504
|
+
return 2;
|
|
505
|
+
case "client":
|
|
506
|
+
return 3;
|
|
507
|
+
case "producer":
|
|
508
|
+
return 4;
|
|
509
|
+
case "consumer":
|
|
510
|
+
return 5;
|
|
511
|
+
default:
|
|
512
|
+
return 0;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
convertAttributeValue(value) {
|
|
516
|
+
if (typeof value === "string") {
|
|
517
|
+
return { stringValue: value };
|
|
518
|
+
}
|
|
519
|
+
if (typeof value === "number") {
|
|
520
|
+
if (Number.isInteger(value)) {
|
|
521
|
+
return { intValue: String(value) };
|
|
522
|
+
}
|
|
523
|
+
return { doubleValue: value };
|
|
524
|
+
}
|
|
525
|
+
if (typeof value === "boolean") {
|
|
526
|
+
return { boolValue: value };
|
|
527
|
+
}
|
|
528
|
+
if (Array.isArray(value)) {
|
|
529
|
+
return {
|
|
530
|
+
arrayValue: {
|
|
531
|
+
values: value.map((v) => this.convertAttributeValue(v))
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
return { stringValue: String(value) };
|
|
536
|
+
}
|
|
537
|
+
async shutdown() {
|
|
538
|
+
if (this.flushTimer) {
|
|
539
|
+
clearInterval(this.flushTimer);
|
|
540
|
+
this.flushTimer = null;
|
|
541
|
+
}
|
|
542
|
+
await this.flush();
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
function createOtlpExporter(options) {
|
|
546
|
+
return new OtlpExporter(options);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// src/tracing/tracer.ts
|
|
550
|
+
import { createLogger as createLogger2 } from "@parsrun/core";
|
|
551
|
+
var Tracer = class {
|
|
552
|
+
serviceName;
|
|
553
|
+
serviceVersion;
|
|
554
|
+
sampler;
|
|
555
|
+
exporter;
|
|
556
|
+
logger;
|
|
557
|
+
contextManager;
|
|
558
|
+
spanManager;
|
|
559
|
+
enabled;
|
|
560
|
+
flushTimer = null;
|
|
561
|
+
constructor(options) {
|
|
562
|
+
this.serviceName = options.serviceName;
|
|
563
|
+
this.serviceVersion = options.serviceVersion ?? "1.0.0";
|
|
564
|
+
this.enabled = options.config?.enabled ?? true;
|
|
565
|
+
this.sampler = options.config?.sampler ?? { ratio: 0.1 };
|
|
566
|
+
this.exporter = options.exporter ?? new ConsoleExporter();
|
|
567
|
+
this.logger = options.logger ?? createLogger2({ name: `tracer:${options.serviceName}` });
|
|
568
|
+
this.contextManager = new TraceContextManager();
|
|
569
|
+
this.spanManager = new SpanManager();
|
|
570
|
+
if (this.enabled) {
|
|
571
|
+
this.flushTimer = setInterval(() => this.flush(), 5e3);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Start a new span
|
|
576
|
+
*/
|
|
577
|
+
startSpan(name, options) {
|
|
578
|
+
if (!this.enabled) return null;
|
|
579
|
+
const parent = options?.parent ?? this.contextManager.current();
|
|
580
|
+
if (!parent && !shouldSample(this.sampler)) {
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
const spanOptions = {
|
|
584
|
+
name,
|
|
585
|
+
kind: options?.kind ?? "internal",
|
|
586
|
+
attributes: {
|
|
587
|
+
[SpanAttributes.SERVICE_NAME]: this.serviceName,
|
|
588
|
+
[SpanAttributes.SERVICE_VERSION]: this.serviceVersion,
|
|
589
|
+
...options?.attributes
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
if (parent) {
|
|
593
|
+
spanOptions.parent = parent;
|
|
594
|
+
}
|
|
595
|
+
const span = this.spanManager.startSpan(spanOptions);
|
|
596
|
+
return span;
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* End a span
|
|
600
|
+
*/
|
|
601
|
+
endSpan(span, error) {
|
|
602
|
+
if (!span) return;
|
|
603
|
+
if (error) {
|
|
604
|
+
this.spanManager.recordException(span, error);
|
|
605
|
+
}
|
|
606
|
+
this.spanManager.endSpan(span, error ? "error" : "ok");
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Run a function with automatic span creation
|
|
610
|
+
*/
|
|
611
|
+
async trace(name, fn, options) {
|
|
612
|
+
const span = this.startSpan(name, options);
|
|
613
|
+
if (!span) {
|
|
614
|
+
return fn(null);
|
|
615
|
+
}
|
|
616
|
+
try {
|
|
617
|
+
const result = await this.contextManager.run(span.traceContext, () => fn(span));
|
|
618
|
+
this.endSpan(span);
|
|
619
|
+
return result;
|
|
620
|
+
} catch (error) {
|
|
621
|
+
this.endSpan(span, error);
|
|
622
|
+
throw error;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Add attribute to current span
|
|
627
|
+
*/
|
|
628
|
+
setAttribute(key, value) {
|
|
629
|
+
const ctx = this.contextManager.current();
|
|
630
|
+
if (!ctx) return;
|
|
631
|
+
const span = this.spanManager.getSpan(ctx.spanId);
|
|
632
|
+
if (span) {
|
|
633
|
+
this.spanManager.setAttribute(span, key, value);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Add event to current span
|
|
638
|
+
*/
|
|
639
|
+
addEvent(name, attributes) {
|
|
640
|
+
const ctx = this.contextManager.current();
|
|
641
|
+
if (!ctx) return;
|
|
642
|
+
const span = this.spanManager.getSpan(ctx.spanId);
|
|
643
|
+
if (span) {
|
|
644
|
+
this.spanManager.addEvent(span, name, attributes);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Get current trace context
|
|
649
|
+
*/
|
|
650
|
+
currentContext() {
|
|
651
|
+
return this.contextManager.current();
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Get context manager for advanced use
|
|
655
|
+
*/
|
|
656
|
+
getContextManager() {
|
|
657
|
+
return this.contextManager;
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Extract trace context from incoming request
|
|
661
|
+
*/
|
|
662
|
+
extract(headers) {
|
|
663
|
+
return this.contextManager.fromHeaders(headers);
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Inject trace context into outgoing request headers
|
|
667
|
+
*/
|
|
668
|
+
inject(ctx) {
|
|
669
|
+
const context = ctx ?? this.contextManager.current();
|
|
670
|
+
if (!context) return {};
|
|
671
|
+
return this.contextManager.toHeaders(context);
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Flush completed spans to exporter
|
|
675
|
+
*/
|
|
676
|
+
async flush() {
|
|
677
|
+
const spans = this.spanManager.flush();
|
|
678
|
+
if (spans.length > 0) {
|
|
679
|
+
try {
|
|
680
|
+
await this.exporter.export(spans);
|
|
681
|
+
} catch (error) {
|
|
682
|
+
this.logger.error("Failed to export spans", error);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Shutdown tracer
|
|
688
|
+
*/
|
|
689
|
+
async shutdown() {
|
|
690
|
+
if (this.flushTimer) {
|
|
691
|
+
clearInterval(this.flushTimer);
|
|
692
|
+
this.flushTimer = null;
|
|
693
|
+
}
|
|
694
|
+
await this.flush();
|
|
695
|
+
await this.exporter.shutdown();
|
|
696
|
+
this.spanManager.clear();
|
|
697
|
+
this.contextManager.clear();
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
function createTracer(options) {
|
|
701
|
+
return new Tracer(options);
|
|
702
|
+
}
|
|
703
|
+
var globalTracer = null;
|
|
704
|
+
function getGlobalTracer() {
|
|
705
|
+
return globalTracer;
|
|
706
|
+
}
|
|
707
|
+
function setGlobalTracer(tracer) {
|
|
708
|
+
globalTracer = tracer;
|
|
709
|
+
}
|
|
710
|
+
function resetGlobalTracer() {
|
|
711
|
+
globalTracer = null;
|
|
712
|
+
}
|
|
713
|
+
function createTracingMiddleware(tracer) {
|
|
714
|
+
return async (request, next) => {
|
|
715
|
+
const parentCtx = tracer.extract(request.headers);
|
|
716
|
+
const url = new URL(request.url);
|
|
717
|
+
const spanOpts = {
|
|
718
|
+
kind: "server",
|
|
719
|
+
attributes: {
|
|
720
|
+
[SpanAttributes.HTTP_METHOD]: request.method,
|
|
721
|
+
[SpanAttributes.HTTP_URL]: request.url
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
if (parentCtx) {
|
|
725
|
+
spanOpts.parent = parentCtx;
|
|
726
|
+
}
|
|
727
|
+
const span = tracer.startSpan(`${request.method} ${url.pathname}`, spanOpts);
|
|
728
|
+
if (!span) {
|
|
729
|
+
return next();
|
|
730
|
+
}
|
|
731
|
+
try {
|
|
732
|
+
const response = await tracer.getContextManager().run(span.traceContext, next);
|
|
733
|
+
tracer.endSpan(span);
|
|
734
|
+
span.attributes[SpanAttributes.HTTP_STATUS_CODE] = response.status;
|
|
735
|
+
return response;
|
|
736
|
+
} catch (error) {
|
|
737
|
+
tracer.endSpan(span, error);
|
|
738
|
+
throw error;
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
function createRpcTracing(tracer) {
|
|
743
|
+
return {
|
|
744
|
+
/**
|
|
745
|
+
* Trace an outgoing RPC call
|
|
746
|
+
*/
|
|
747
|
+
async traceCall(service, method, fn) {
|
|
748
|
+
return tracer.trace(
|
|
749
|
+
`rpc.${service}.${method}`,
|
|
750
|
+
fn,
|
|
751
|
+
{
|
|
752
|
+
kind: "client",
|
|
753
|
+
attributes: {
|
|
754
|
+
[SpanAttributes.RPC_SYSTEM]: "pars",
|
|
755
|
+
[SpanAttributes.RPC_SERVICE]: service,
|
|
756
|
+
[SpanAttributes.RPC_METHOD]: method
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
);
|
|
760
|
+
},
|
|
761
|
+
/**
|
|
762
|
+
* Trace an incoming RPC request
|
|
763
|
+
*/
|
|
764
|
+
async traceHandler(service, method, fn, parentCtx) {
|
|
765
|
+
const handlerOpts = {
|
|
766
|
+
kind: "server",
|
|
767
|
+
attributes: {
|
|
768
|
+
[SpanAttributes.RPC_SYSTEM]: "pars",
|
|
769
|
+
[SpanAttributes.RPC_SERVICE]: service,
|
|
770
|
+
[SpanAttributes.RPC_METHOD]: method
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
if (parentCtx) {
|
|
774
|
+
handlerOpts.parent = parentCtx;
|
|
775
|
+
}
|
|
776
|
+
const span = tracer.startSpan(`rpc.${service}.${method}`, handlerOpts);
|
|
777
|
+
if (!span) {
|
|
778
|
+
return fn();
|
|
779
|
+
}
|
|
780
|
+
try {
|
|
781
|
+
const result = await tracer.getContextManager().run(span.traceContext, fn);
|
|
782
|
+
tracer.endSpan(span);
|
|
783
|
+
return result;
|
|
784
|
+
} catch (error) {
|
|
785
|
+
tracer.endSpan(span, error);
|
|
786
|
+
throw error;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
export {
|
|
792
|
+
ConsoleExporter,
|
|
793
|
+
OtlpExporter,
|
|
794
|
+
SpanAttributes,
|
|
795
|
+
SpanManager,
|
|
796
|
+
TraceContextManager,
|
|
797
|
+
Tracer,
|
|
798
|
+
createChildContext,
|
|
799
|
+
createConsoleExporter,
|
|
800
|
+
createOtlpExporter,
|
|
801
|
+
createRpcTracing,
|
|
802
|
+
createSpan,
|
|
803
|
+
createTraceContext,
|
|
804
|
+
createTracer,
|
|
805
|
+
createTracingMiddleware,
|
|
806
|
+
formatTraceparent,
|
|
807
|
+
formatTracestate,
|
|
808
|
+
generateSpanId,
|
|
809
|
+
generateTraceId,
|
|
810
|
+
getGlobalTracer,
|
|
811
|
+
getSpanDuration,
|
|
812
|
+
isSpanCompleted,
|
|
813
|
+
parseTraceparent,
|
|
814
|
+
parseTracestate,
|
|
815
|
+
resetGlobalTracer,
|
|
816
|
+
setGlobalTracer,
|
|
817
|
+
shouldSample,
|
|
818
|
+
spanToLogObject
|
|
819
|
+
};
|
|
820
|
+
//# sourceMappingURL=index.js.map
|