@zucker-framework/tracing 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/dist/index.mjs ADDED
@@ -0,0 +1,464 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/span.ts
5
+ import { randomUUID } from "crypto";
6
+ var Span = class {
7
+ static {
8
+ __name(this, "Span");
9
+ }
10
+ traceId;
11
+ spanId;
12
+ parentSpanId;
13
+ name;
14
+ startTime;
15
+ endTime;
16
+ attributes;
17
+ events = [];
18
+ links = [];
19
+ status = "UNSET";
20
+ statusMessage;
21
+ constructor(name, options) {
22
+ this.name = name;
23
+ this.traceId = options?.traceId ?? randomUUID().replace(/-/g, "");
24
+ this.spanId = randomUUID().replace(/-/g, "").substring(0, 16);
25
+ this.parentSpanId = options?.parentSpanId;
26
+ this.startTime = /* @__PURE__ */ new Date();
27
+ this.attributes = options?.attributes ?? {};
28
+ }
29
+ setAttribute(key, value) {
30
+ this.attributes[key] = value;
31
+ return this;
32
+ }
33
+ setAttributes(attrs) {
34
+ Object.assign(this.attributes, attrs);
35
+ return this;
36
+ }
37
+ addEvent(name, attributes) {
38
+ this.events.push({
39
+ name,
40
+ timestamp: /* @__PURE__ */ new Date(),
41
+ attributes
42
+ });
43
+ return this;
44
+ }
45
+ addLink(traceId, spanId, attributes) {
46
+ this.links.push({
47
+ traceId,
48
+ spanId,
49
+ attributes
50
+ });
51
+ return this;
52
+ }
53
+ setStatus(status, message) {
54
+ this.status = status;
55
+ this.statusMessage = message;
56
+ return this;
57
+ }
58
+ end() {
59
+ this.endTime = /* @__PURE__ */ new Date();
60
+ }
61
+ get duration() {
62
+ if (!this.endTime) return void 0;
63
+ return this.endTime.getTime() - this.startTime.getTime();
64
+ }
65
+ isRecording() {
66
+ return !this.endTime;
67
+ }
68
+ };
69
+ var SpanBuilder = class {
70
+ static {
71
+ __name(this, "SpanBuilder");
72
+ }
73
+ _name;
74
+ options = {};
75
+ _links = [];
76
+ constructor(name) {
77
+ this._name = name;
78
+ }
79
+ withTraceId(traceId) {
80
+ this.options.traceId = traceId;
81
+ return this;
82
+ }
83
+ withParentSpanId(parentSpanId) {
84
+ this.options.parentSpanId = parentSpanId;
85
+ return this;
86
+ }
87
+ withAttribute(key, value) {
88
+ if (!this.options.attributes) this.options.attributes = {};
89
+ this.options.attributes[key] = value;
90
+ return this;
91
+ }
92
+ withLink(traceId, spanId, attributes) {
93
+ this._links.push({
94
+ traceId,
95
+ spanId,
96
+ attributes
97
+ });
98
+ return this;
99
+ }
100
+ build() {
101
+ const span = new Span(this._name, this.options);
102
+ for (const link of this._links) {
103
+ span.addLink(link.traceId, link.spanId, link.attributes);
104
+ }
105
+ return span;
106
+ }
107
+ };
108
+
109
+ // src/tracer.ts
110
+ import { Injectable } from "@nestjs/common";
111
+ function _ts_decorate(decorators, target, key, desc) {
112
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
113
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
114
+ r = Reflect.decorate(decorators, target, key, desc);
115
+ } else {
116
+ for (var i = decorators.length - 1; i >= 0; i--) {
117
+ if (d = decorators[i]) {
118
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
119
+ }
120
+ }
121
+ }
122
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
123
+ }
124
+ __name(_ts_decorate, "_ts_decorate");
125
+ function _ts_metadata(metadataKey, metadataValue) {
126
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
127
+ return Reflect.metadata(metadataKey, metadataValue);
128
+ }
129
+ }
130
+ __name(_ts_metadata, "_ts_metadata");
131
+ var SamplingDecision = /* @__PURE__ */ (function(SamplingDecision2) {
132
+ SamplingDecision2["DROP"] = "DROP";
133
+ SamplingDecision2["RECORD_ONLY"] = "RECORD_ONLY";
134
+ SamplingDecision2["RECORD_AND_SAMPLE"] = "RECORD_AND_SAMPLE";
135
+ return SamplingDecision2;
136
+ })({});
137
+ var AlwaysOnSampler = class {
138
+ static {
139
+ __name(this, "AlwaysOnSampler");
140
+ }
141
+ shouldSample() {
142
+ return "RECORD_AND_SAMPLE";
143
+ }
144
+ };
145
+ var AlwaysOffSampler = class {
146
+ static {
147
+ __name(this, "AlwaysOffSampler");
148
+ }
149
+ shouldSample() {
150
+ return "DROP";
151
+ }
152
+ };
153
+ var RatioSampler = class {
154
+ static {
155
+ __name(this, "RatioSampler");
156
+ }
157
+ ratio;
158
+ constructor(ratio) {
159
+ this.ratio = ratio;
160
+ this.ratio = Math.max(0, Math.min(1, ratio));
161
+ }
162
+ shouldSample() {
163
+ return Math.random() < this.ratio ? "RECORD_AND_SAMPLE" : "DROP";
164
+ }
165
+ };
166
+ var ParentBasedSampler = class {
167
+ static {
168
+ __name(this, "ParentBasedSampler");
169
+ }
170
+ root;
171
+ constructor(root) {
172
+ this.root = root;
173
+ }
174
+ shouldSample(spanName, options) {
175
+ if (options?.parentSpanId) {
176
+ return "RECORD_AND_SAMPLE";
177
+ }
178
+ return this.root.shouldSample(spanName, options);
179
+ }
180
+ };
181
+ var InMemoryTracer = class {
182
+ static {
183
+ __name(this, "InMemoryTracer");
184
+ }
185
+ spans = [];
186
+ maxSpans = 1e4;
187
+ sampler;
188
+ constructor(sampler) {
189
+ this.sampler = sampler ?? new AlwaysOnSampler();
190
+ }
191
+ setSampler(sampler) {
192
+ this.sampler = sampler;
193
+ }
194
+ startSpan(name, options) {
195
+ const span = new Span(name, options);
196
+ const decision = this.sampler.shouldSample(name, options);
197
+ if (decision === "DROP") {
198
+ return span;
199
+ }
200
+ this.spans.push(span);
201
+ if (this.spans.length > this.maxSpans) {
202
+ this.spans = this.spans.slice(-this.maxSpans / 2);
203
+ }
204
+ return span;
205
+ }
206
+ getSpans() {
207
+ return [
208
+ ...this.spans
209
+ ];
210
+ }
211
+ getSpansByTraceId(traceId) {
212
+ return this.spans.filter((s) => s.traceId === traceId);
213
+ }
214
+ clear() {
215
+ this.spans = [];
216
+ }
217
+ };
218
+ InMemoryTracer = _ts_decorate([
219
+ Injectable(),
220
+ _ts_metadata("design:type", Function),
221
+ _ts_metadata("design:paramtypes", [
222
+ typeof Sampler === "undefined" ? Object : Sampler
223
+ ])
224
+ ], InMemoryTracer);
225
+
226
+ // src/trace-middleware.ts
227
+ import { Injectable as Injectable2 } from "@nestjs/common";
228
+ import { AsyncLocalStorage } from "async_hooks";
229
+ import { randomUUID as randomUUID2 } from "crypto";
230
+ function _ts_decorate2(decorators, target, key, desc) {
231
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
232
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
233
+ r = Reflect.decorate(decorators, target, key, desc);
234
+ } else {
235
+ for (var i = decorators.length - 1; i >= 0; i--) {
236
+ if (d = decorators[i]) {
237
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
238
+ }
239
+ }
240
+ }
241
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
242
+ }
243
+ __name(_ts_decorate2, "_ts_decorate");
244
+ var traceStorage = new AsyncLocalStorage();
245
+ function getTraceId() {
246
+ return traceStorage.getStore()?.traceId;
247
+ }
248
+ __name(getTraceId, "getTraceId");
249
+ function getSpanId() {
250
+ return traceStorage.getStore()?.parentSpanId;
251
+ }
252
+ __name(getSpanId, "getSpanId");
253
+ function getTraceContext() {
254
+ return traceStorage.getStore();
255
+ }
256
+ __name(getTraceContext, "getTraceContext");
257
+ function runWithTraceContext(ctx, fn) {
258
+ return traceStorage.run(ctx, fn);
259
+ }
260
+ __name(runWithTraceContext, "runWithTraceContext");
261
+ function generateTraceparent(traceId, spanId, sampled = true) {
262
+ const flags = sampled ? "01" : "00";
263
+ return `00-${traceId}-${spanId}-${flags}`;
264
+ }
265
+ __name(generateTraceparent, "generateTraceparent");
266
+ function parseTraceparent(header) {
267
+ const parts = header.split("-");
268
+ if (parts.length !== 4) return null;
269
+ const [version, traceId, parentId, flags] = parts;
270
+ if (version !== "00") return null;
271
+ if (traceId.length !== 32 || parentId.length !== 16) return null;
272
+ if (!/^[0-9a-fA-F]{32}$/.test(traceId) || !/^[0-9a-fA-F]{16}$/.test(parentId)) return null;
273
+ return {
274
+ traceId,
275
+ parentSpanId: parentId,
276
+ traceFlags: flags
277
+ };
278
+ }
279
+ __name(parseTraceparent, "parseTraceparent");
280
+ var TraceMiddleware = class {
281
+ static {
282
+ __name(this, "TraceMiddleware");
283
+ }
284
+ use(req, res, next) {
285
+ const traceparent = req.headers["traceparent"];
286
+ const tracestate = req.headers["tracestate"];
287
+ let ctx;
288
+ if (traceparent) {
289
+ const parsed = parseTraceparent(traceparent);
290
+ ctx = parsed ?? {
291
+ traceId: randomUUID2().replace(/-/g, ""),
292
+ traceFlags: "01"
293
+ };
294
+ } else {
295
+ ctx = {
296
+ traceId: randomUUID2().replace(/-/g, ""),
297
+ traceFlags: "01"
298
+ };
299
+ }
300
+ if (tracestate) {
301
+ ctx.traceState = tracestate;
302
+ }
303
+ const spanId = randomUUID2().replace(/-/g, "").substring(0, 16);
304
+ res.setHeader("traceparent", generateTraceparent(ctx.traceId, spanId, ctx.traceFlags === "01"));
305
+ if (ctx.traceState) {
306
+ res.setHeader("tracestate", ctx.traceState);
307
+ }
308
+ traceStorage.run(ctx, () => {
309
+ next();
310
+ });
311
+ }
312
+ };
313
+ TraceMiddleware = _ts_decorate2([
314
+ Injectable2()
315
+ ], TraceMiddleware);
316
+
317
+ // src/trace-interceptor.ts
318
+ import { Injectable as Injectable3 } from "@nestjs/common";
319
+ import { throwError } from "rxjs";
320
+ import { tap, catchError } from "rxjs/operators";
321
+ function _ts_decorate3(decorators, target, key, desc) {
322
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
323
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
324
+ r = Reflect.decorate(decorators, target, key, desc);
325
+ } else {
326
+ for (var i = decorators.length - 1; i >= 0; i--) {
327
+ if (d = decorators[i]) {
328
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
329
+ }
330
+ }
331
+ }
332
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
333
+ }
334
+ __name(_ts_decorate3, "_ts_decorate");
335
+ function _ts_metadata2(metadataKey, metadataValue) {
336
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
337
+ return Reflect.metadata(metadataKey, metadataValue);
338
+ }
339
+ }
340
+ __name(_ts_metadata2, "_ts_metadata");
341
+ var TraceInterceptor = class {
342
+ static {
343
+ __name(this, "TraceInterceptor");
344
+ }
345
+ tracer;
346
+ constructor(tracer) {
347
+ this.tracer = tracer;
348
+ }
349
+ intercept(context, next) {
350
+ const request = context.switchToHttp().getRequest();
351
+ const traceCtx = getTraceContext();
352
+ const routePath = request.route?.path ?? request.url;
353
+ const spanName = `${request.method} ${routePath}`;
354
+ const span = this.tracer.startSpan(spanName, {
355
+ traceId: traceCtx?.traceId,
356
+ parentSpanId: traceCtx?.parentSpanId,
357
+ attributes: {
358
+ "http.method": request.method,
359
+ "http.url": request.url,
360
+ "http.route": routePath,
361
+ "http.user_agent": request.headers?.["user-agent"],
362
+ "http.client_ip": request.ip || request.headers?.["x-forwarded-for"] || ""
363
+ }
364
+ });
365
+ return next.handle().pipe(tap(() => {
366
+ const response = context.switchToHttp().getResponse();
367
+ const statusCode = response?.statusCode ?? 200;
368
+ span.setAttribute("http.status_code", statusCode);
369
+ span.setStatus(statusCode < 400 ? "OK" : "ERROR");
370
+ span.end();
371
+ }), catchError((error) => {
372
+ const statusCode = error?.status ?? 500;
373
+ span.setAttribute("http.status_code", statusCode);
374
+ span.setAttribute("error.type", error?.constructor?.name ?? "Error");
375
+ span.setAttribute("error.message", error instanceof Error ? error.message : String(error));
376
+ span.setStatus("ERROR", error instanceof Error ? error.message : String(error));
377
+ span.addEvent("exception", {
378
+ "exception.type": error?.constructor?.name ?? "Error",
379
+ "exception.message": error instanceof Error ? error.message : String(error),
380
+ "exception.stacktrace": error instanceof Error ? error.stack : void 0
381
+ });
382
+ span.end();
383
+ return throwError(() => error);
384
+ }));
385
+ }
386
+ };
387
+ TraceInterceptor = _ts_decorate3([
388
+ Injectable3(),
389
+ _ts_metadata2("design:type", Function),
390
+ _ts_metadata2("design:paramtypes", [
391
+ typeof InMemoryTracer === "undefined" ? Object : InMemoryTracer
392
+ ])
393
+ ], TraceInterceptor);
394
+
395
+ // src/tracing.module.ts
396
+ import { Module, Global } from "@nestjs/common";
397
+ import { APP_INTERCEPTOR } from "@nestjs/core";
398
+ function _ts_decorate4(decorators, target, key, desc) {
399
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
400
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
401
+ r = Reflect.decorate(decorators, target, key, desc);
402
+ } else {
403
+ for (var i = decorators.length - 1; i >= 0; i--) {
404
+ if (d = decorators[i]) {
405
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
406
+ }
407
+ }
408
+ }
409
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
410
+ }
411
+ __name(_ts_decorate4, "_ts_decorate");
412
+ var ZuckerTracingModule = class _ZuckerTracingModule {
413
+ static {
414
+ __name(this, "ZuckerTracingModule");
415
+ }
416
+ configure(consumer) {
417
+ consumer.apply(TraceMiddleware).forRoutes("*");
418
+ }
419
+ static forRoot(options) {
420
+ const sampleRate = options?.sampleRate ?? 1;
421
+ return {
422
+ module: _ZuckerTracingModule,
423
+ global: true,
424
+ providers: [
425
+ {
426
+ provide: InMemoryTracer,
427
+ useFactory: /* @__PURE__ */ __name(() => {
428
+ const sampler = new ParentBasedSampler(new RatioSampler(sampleRate));
429
+ return new InMemoryTracer(sampler);
430
+ }, "useFactory")
431
+ },
432
+ {
433
+ provide: APP_INTERCEPTOR,
434
+ useClass: TraceInterceptor
435
+ }
436
+ ],
437
+ exports: [
438
+ InMemoryTracer
439
+ ]
440
+ };
441
+ }
442
+ };
443
+ ZuckerTracingModule = _ts_decorate4([
444
+ Global(),
445
+ Module({})
446
+ ], ZuckerTracingModule);
447
+ export {
448
+ AlwaysOffSampler,
449
+ AlwaysOnSampler,
450
+ InMemoryTracer,
451
+ ParentBasedSampler,
452
+ RatioSampler,
453
+ SamplingDecision,
454
+ Span,
455
+ SpanBuilder,
456
+ TraceInterceptor,
457
+ TraceMiddleware,
458
+ ZuckerTracingModule,
459
+ generateTraceparent,
460
+ getSpanId,
461
+ getTraceContext,
462
+ getTraceId,
463
+ runWithTraceContext
464
+ };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@zucker-framework/tracing",
3
+ "version": "1.0.0",
4
+ "main": "dist/index.js",
5
+ "module": "dist/index.mjs",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "LICENSE",
10
+ "NOTICE",
11
+ "THIRD_PARTY_NOTICES"
12
+ ],
13
+ "dependencies": {
14
+ "@zucker-framework/core": "1.0.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/node": "^20.19.43"
18
+ },
19
+ "peerDependencies": {
20
+ "@nestjs/common": "^12.0.0",
21
+ "@nestjs/core": "^12.0.0",
22
+ "rxjs": "^7.0.0"
23
+ },
24
+ "license": "Apache-2.0",
25
+ "author": "Lind",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/Lind-pro/zucker-framework.git",
29
+ "directory": "packages/tracing"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public",
33
+ "registry": "https://registry.npmjs.org/"
34
+ },
35
+ "scripts": {
36
+ "build": "tsup src/index.ts --format cjs,esm --dts",
37
+ "clean": "rm -rf dist",
38
+ "lint": "eslint src/"
39
+ }
40
+ }