chronos-tracer 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/dist/config/chronos.config.d.ts +6 -0
- package/dist/config/chronos.config.js +15 -0
- package/dist/config/chronos.config.js.map +1 -0
- package/dist/context/trace-context.d.ts +21 -0
- package/dist/context/trace-context.js +39 -0
- package/dist/context/trace-context.js.map +1 -0
- package/dist/decorators/trace.decorator.d.ts +1 -0
- package/dist/decorators/trace.decorator.js +63 -0
- package/dist/decorators/trace.decorator.js.map +1 -0
- package/dist/emitter/emitter.d.ts +2 -0
- package/dist/emitter/emitter.js +20 -0
- package/dist/emitter/emitter.js.map +1 -0
- package/dist/error/error.d.ts +2 -0
- package/dist/error/error.js +84 -0
- package/dist/error/error.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/interceptor/trace.interceptor.d.ts +0 -0
- package/dist/interceptor/trace.interceptor.js +2 -0
- package/dist/interceptor/trace.interceptor.js.map +1 -0
- package/dist/middleware/trace.middleware.d.ts +1 -0
- package/dist/middleware/trace.middleware.js +45 -0
- package/dist/middleware/trace.middleware.js.map +1 -0
- package/dist/span/span.d.ts +1 -0
- package/dist/span/span.js +55 -0
- package/dist/span/span.js.map +1 -0
- package/dist/types/event.d.ts +18 -0
- package/dist/types/event.js +3 -0
- package/dist/types/event.js.map +1 -0
- package/index.js +1 -0
- package/package.json +20 -0
- package/src/config/chronos.config.ts +19 -0
- package/src/context/trace-context.ts +50 -0
- package/src/decorators/trace.decorator.ts +73 -0
- package/src/emitter/emitter.ts +18 -0
- package/src/error/error.ts +95 -0
- package/src/index.ts +5 -0
- package/src/middleware/trace.middleware.ts +49 -0
- package/src/span/span.ts +62 -0
- package/src/types/event.ts +25 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.initChronos = initChronos;
|
|
4
|
+
exports.getChronosConfig = getChronosConfig;
|
|
5
|
+
let config = null;
|
|
6
|
+
function initChronos(userConfig) {
|
|
7
|
+
config = userConfig;
|
|
8
|
+
}
|
|
9
|
+
function getChronosConfig() {
|
|
10
|
+
if (!config) {
|
|
11
|
+
throw new Error("Chronos is not initialized. Call initChronos() in your app startup.");
|
|
12
|
+
}
|
|
13
|
+
return config;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=chronos.config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chronos.config.js","sourceRoot":"","sources":["../../src/config/chronos.config.ts"],"names":[],"mappings":";;AAOA,kCAEC;AAED,4CAOC;AAbD,IAAI,MAAM,GAAyB,IAAI,CAAC;AAExC,SAAgB,WAAW,CAAC,UAAyB;IACnD,MAAM,GAAG,UAAU,CAAC;AACtB,CAAC;AAED,SAAgB,gBAAgB;IAC9B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,qEAAqE,CACtE,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type TraceStore = {
|
|
2
|
+
traceId: string;
|
|
3
|
+
rootSpanId: string;
|
|
4
|
+
service: string;
|
|
5
|
+
spanStack: string[];
|
|
6
|
+
stage?: string;
|
|
7
|
+
errorEmitted?: boolean;
|
|
8
|
+
};
|
|
9
|
+
declare class TraceContextClass {
|
|
10
|
+
private als;
|
|
11
|
+
run(store: Omit<TraceStore, "spanStack">, fn: () => void): void;
|
|
12
|
+
/** Get current store */
|
|
13
|
+
getStore(): TraceStore | undefined;
|
|
14
|
+
setStage(stage: string): void;
|
|
15
|
+
/** Span helpers */
|
|
16
|
+
getCurrentSpan(): string | undefined;
|
|
17
|
+
pushSpan(spanId: string): void;
|
|
18
|
+
popSpan(): void;
|
|
19
|
+
}
|
|
20
|
+
export declare const TraceContext: TraceContextClass;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TraceContext = void 0;
|
|
4
|
+
const async_hooks_1 = require("async_hooks");
|
|
5
|
+
class TraceContextClass {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.als = new async_hooks_1.AsyncLocalStorage();
|
|
8
|
+
}
|
|
9
|
+
run(store, fn) {
|
|
10
|
+
this.als.run({ ...store, spanStack: [store.rootSpanId] }, fn);
|
|
11
|
+
}
|
|
12
|
+
/** Get current store */
|
|
13
|
+
getStore() {
|
|
14
|
+
return this.als.getStore();
|
|
15
|
+
}
|
|
16
|
+
setStage(stage) {
|
|
17
|
+
const ctx = this.als.getStore();
|
|
18
|
+
if (ctx) {
|
|
19
|
+
ctx.stage = stage;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Span helpers */
|
|
23
|
+
getCurrentSpan() {
|
|
24
|
+
const store = this.getStore();
|
|
25
|
+
return store?.spanStack[store.spanStack.length - 1];
|
|
26
|
+
}
|
|
27
|
+
pushSpan(spanId) {
|
|
28
|
+
const store = this.getStore();
|
|
29
|
+
if (store)
|
|
30
|
+
store.spanStack.push(spanId);
|
|
31
|
+
}
|
|
32
|
+
popSpan() {
|
|
33
|
+
const store = this.getStore();
|
|
34
|
+
if (store)
|
|
35
|
+
store.spanStack.pop();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.TraceContext = new TraceContextClass();
|
|
39
|
+
//# sourceMappingURL=trace-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace-context.js","sourceRoot":"","sources":["../../src/context/trace-context.ts"],"names":[],"mappings":";;;AAAA,6CAAgD;AAYhD,MAAM,iBAAiB;IAAvB;QACU,QAAG,GAAG,IAAI,+BAAiB,EAAc,CAAC;IAkCpD,CAAC;IAhCC,GAAG,CAAC,KAAoC,EAAE,EAAc;QACtD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAChE,CAAC;IAGD,wBAAwB;IACxB,QAAQ;QACN,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;IAC7B,CAAC;IAED,QAAQ,CAAC,KAAa;QACpB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAChC,IAAI,GAAG,EAAE,CAAC;YACR,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC;QACpB,CAAC;IACH,CAAC;IAED,mBAAmB;IACnB,cAAc;QACZ,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACtD,CAAC;IAED,QAAQ,CAAC,MAAc;QACrB,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC9B,IAAI,KAAK;YAAE,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1C,CAAC;IAED,OAAO;QACL,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC9B,IAAI,KAAK;YAAE,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC;IACnC,CAAC;CACF;AAEY,QAAA,YAAY,GAAG,IAAI,iBAAiB,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function Trace(operation?: string): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Trace = Trace;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
const trace_context_1 = require("../context/trace-context");
|
|
6
|
+
const emitter_1 = require("../emitter/emitter");
|
|
7
|
+
function Trace(operation) {
|
|
8
|
+
return function (target, propertyKey, descriptor) {
|
|
9
|
+
const original = descriptor.value;
|
|
10
|
+
descriptor.value = async function (...args) {
|
|
11
|
+
const store = trace_context_1.TraceContext.getStore();
|
|
12
|
+
// 🔴 If no trace context, DO NOTHING
|
|
13
|
+
if (!store) {
|
|
14
|
+
return original.apply(this, args);
|
|
15
|
+
}
|
|
16
|
+
const spanId = (0, crypto_1.randomUUID)();
|
|
17
|
+
const parentSpanId = trace_context_1.TraceContext.getCurrentSpan();
|
|
18
|
+
const opName = operation ?? `${target.constructor.name}.${propertyKey}`;
|
|
19
|
+
trace_context_1.TraceContext.pushSpan(spanId);
|
|
20
|
+
trace_context_1.TraceContext.setStage(opName);
|
|
21
|
+
(0, emitter_1.emit)({
|
|
22
|
+
trace_id: store.traceId,
|
|
23
|
+
span_id: spanId,
|
|
24
|
+
parent_span_id: parentSpanId,
|
|
25
|
+
service: store.service,
|
|
26
|
+
operation: opName,
|
|
27
|
+
type: 'start',
|
|
28
|
+
timestamp: Date.now().toString(),
|
|
29
|
+
});
|
|
30
|
+
try {
|
|
31
|
+
const result = await original.apply(this, args);
|
|
32
|
+
(0, emitter_1.emit)({
|
|
33
|
+
trace_id: store.traceId,
|
|
34
|
+
span_id: spanId,
|
|
35
|
+
parent_span_id: parentSpanId,
|
|
36
|
+
service: store.service,
|
|
37
|
+
operation: opName,
|
|
38
|
+
type: 'end',
|
|
39
|
+
timestamp: Date.now().toString(),
|
|
40
|
+
});
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
(0, emitter_1.emit)({
|
|
45
|
+
trace_id: store.traceId,
|
|
46
|
+
span_id: spanId,
|
|
47
|
+
parent_span_id: parentSpanId,
|
|
48
|
+
service: store.service,
|
|
49
|
+
operation: opName,
|
|
50
|
+
type: 'error',
|
|
51
|
+
error_message: err?.message ?? 'Unknown error',
|
|
52
|
+
status_code: err?.status,
|
|
53
|
+
timestamp: Date.now().toString(),
|
|
54
|
+
});
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
trace_context_1.TraceContext.popSpan();
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=trace.decorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace.decorator.js","sourceRoot":"","sources":["../../src/decorators/trace.decorator.ts"],"names":[],"mappings":";;AAIA,sBAoEC;AAxED,mCAAoC;AACpC,4DAAwD;AACxD,gDAA0C;AAE1C,SAAgB,KAAK,CAAC,SAAkB;IACtC,OAAO,UACL,MAAW,EACX,WAAmB,EACnB,UAA8B;QAE9B,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC;QAElC,UAAU,CAAC,KAAK,GAAG,KAAK,WAAW,GAAG,IAAW;YAC/C,MAAM,KAAK,GAAG,4BAAY,CAAC,QAAQ,EAAE,CAAC;YAEtC,qCAAqC;YACrC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACpC,CAAC;YAED,MAAM,MAAM,GAAG,IAAA,mBAAU,GAAE,CAAC;YAC5B,MAAM,YAAY,GAAG,4BAAY,CAAC,cAAc,EAAE,CAAC;YACnD,MAAM,MAAM,GACV,SAAS,IAAI,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,IAAI,WAAW,EAAE,CAAC;YAE3D,4BAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YAE9B,4BAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YAE9B,IAAA,cAAI,EAAC;gBACH,QAAQ,EAAE,KAAK,CAAC,OAAO;gBACvB,OAAO,EAAE,MAAM;gBACf,cAAc,EAAE,YAAY;gBAC5B,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,SAAS,EAAE,MAAM;gBACjB,IAAI,EAAE,OAAO;gBACb,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;aACjC,CAAC,CAAC;YAEH,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;gBAEhD,IAAA,cAAI,EAAC;oBACH,QAAQ,EAAE,KAAK,CAAC,OAAO;oBACvB,OAAO,EAAE,MAAM;oBACf,cAAc,EAAE,YAAY;oBAC5B,OAAO,EAAE,KAAK,CAAC,OAAO;oBACtB,SAAS,EAAE,MAAM;oBACjB,IAAI,EAAE,KAAK;oBACX,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;iBACjC,CAAC,CAAC;gBAEH,OAAO,MAAM,CAAC;YAChB,CAAC;YAAC,OAAO,GAAQ,EAAE,CAAC;gBAClB,IAAA,cAAI,EAAC;oBACH,QAAQ,EAAE,KAAK,CAAC,OAAO;oBACvB,OAAO,EAAE,MAAM;oBACf,cAAc,EAAE,YAAY;oBAC5B,OAAO,EAAE,KAAK,CAAC,OAAO;oBACtB,SAAS,EAAE,MAAM;oBACjB,IAAI,EAAE,OAAO;oBACb,aAAa,EAAE,GAAG,EAAE,OAAO,IAAI,eAAe;oBAC9C,WAAW,EAAE,GAAG,EAAE,MAAM;oBACxB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;iBACjC,CAAC,CAAC;gBAEH,MAAM,GAAG,CAAC;YACZ,CAAC;oBAAS,CAAC;gBACT,4BAAY,CAAC,OAAO,EAAE,CAAC;YACzB,CAAC;QACH,CAAC,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.emit = emit;
|
|
4
|
+
const chronos_config_1 = require("../config/chronos.config");
|
|
5
|
+
async function emit(event) {
|
|
6
|
+
const { endpoint } = (0, chronos_config_1.getChronosConfig)();
|
|
7
|
+
try {
|
|
8
|
+
await fetch(endpoint, {
|
|
9
|
+
method: "POST",
|
|
10
|
+
headers: {
|
|
11
|
+
"Content-Type": "application/json"
|
|
12
|
+
},
|
|
13
|
+
body: JSON.stringify(event)
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
console.error("Failed to emit trace event:", err);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=emitter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emitter.js","sourceRoot":"","sources":["../../src/emitter/emitter.ts"],"names":[],"mappings":";;AAIA,oBAaC;AAjBD,6DAA4D;AAIrD,KAAK,UAAU,IAAI,CAAC,KAAiB;IACxC,MAAM,EAAC,QAAQ,EAAC,GAAG,IAAA,iCAAgB,GAAE,CAAA;IACrC,IAAG,CAAC;QACA,MAAM,KAAK,CAAC,QAAQ,EAAE;YAClB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACL,cAAc,EAAE,kBAAkB;aACrC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;SAC9B,CAAC,CAAC;IACP,CAAC;IAAA,OAAM,GAAG,EAAC,CAAC;QACR,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC;IACtD,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.errorFunction = errorFunction;
|
|
4
|
+
exports.extractMeaningfulMessage = extractMeaningfulMessage;
|
|
5
|
+
const trace_context_1 = require("../context/trace-context");
|
|
6
|
+
const emitter_1 = require("../emitter/emitter");
|
|
7
|
+
// export function errorFunction(error: unknown) {
|
|
8
|
+
// console.log("Logging error trace event", error);
|
|
9
|
+
// const ctx = TraceContext.getStore();
|
|
10
|
+
// emit({
|
|
11
|
+
// trace_id: ctx?.traceId || "unknown",
|
|
12
|
+
// span_id: ctx?.rootSpanId || "unknown",
|
|
13
|
+
// service: ctx?.service || "unknown",
|
|
14
|
+
// operation: `${ctx?.stage || "unknown"}`,
|
|
15
|
+
// type: "start",
|
|
16
|
+
// timestamp: new Date().toISOString(),
|
|
17
|
+
// failedAt: ctx?.stage ?? "UNKNOWN",
|
|
18
|
+
// errorType: error?.constructor?.name ?? "UnknownError",
|
|
19
|
+
// error_message:
|
|
20
|
+
// typeof error === "object" &&
|
|
21
|
+
// error !== null &&
|
|
22
|
+
// "getResponse" in error &&
|
|
23
|
+
// typeof (error as any).getResponse === "function"
|
|
24
|
+
// ? (() => {
|
|
25
|
+
// const r = (error as any).getResponse();
|
|
26
|
+
// if (Array.isArray(r?.message)) return r.message.join(", ");
|
|
27
|
+
// if (typeof r?.message === "string") return r.message;
|
|
28
|
+
// return error instanceof Error ? error.message : String(error);
|
|
29
|
+
// })()
|
|
30
|
+
// : error instanceof Error
|
|
31
|
+
// ? error.message
|
|
32
|
+
// : String(error),
|
|
33
|
+
// });
|
|
34
|
+
// }
|
|
35
|
+
function errorFunction(error) {
|
|
36
|
+
const ctx = trace_context_1.TraceContext.getStore();
|
|
37
|
+
console.log("Error trace event", error);
|
|
38
|
+
// 🚫 If already emitted once, do NOT emit again
|
|
39
|
+
if (ctx?.errorEmitted)
|
|
40
|
+
return;
|
|
41
|
+
if (ctx)
|
|
42
|
+
ctx.errorEmitted = true;
|
|
43
|
+
trace_context_1.TraceContext.setStage('ERROR');
|
|
44
|
+
(0, emitter_1.emit)({
|
|
45
|
+
trace_id: ctx?.traceId || "unknown",
|
|
46
|
+
span_id: ctx?.rootSpanId || "unknown",
|
|
47
|
+
service: ctx?.service || "unknown",
|
|
48
|
+
operation: `${ctx?.stage || "unknown"}`,
|
|
49
|
+
type: "error",
|
|
50
|
+
timestamp: new Date().toISOString(),
|
|
51
|
+
failedAt: ctx?.stage ?? "UNKNOWN",
|
|
52
|
+
errorType: error?.constructor?.name ?? "UnknownError",
|
|
53
|
+
error_message: extractMeaningfulMessage(error),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function extractMeaningfulMessage(error) {
|
|
57
|
+
// NestJS / HttpException-like errors (ValidationPipe, Guards, etc.)
|
|
58
|
+
if (typeof error === 'object' &&
|
|
59
|
+
error !== null &&
|
|
60
|
+
'getResponse' in error &&
|
|
61
|
+
typeof error.getResponse === 'function') {
|
|
62
|
+
try {
|
|
63
|
+
const response = error.getResponse();
|
|
64
|
+
// ValidationPipe: message is usually an array
|
|
65
|
+
if (Array.isArray(response?.message)) {
|
|
66
|
+
return response.message.join(', ');
|
|
67
|
+
}
|
|
68
|
+
// Other HttpExceptions
|
|
69
|
+
if (typeof response?.message === 'string') {
|
|
70
|
+
return response.message;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// fall through to generic handling
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Standard JS Error
|
|
78
|
+
if (error instanceof Error) {
|
|
79
|
+
return error.message;
|
|
80
|
+
}
|
|
81
|
+
// Fallback
|
|
82
|
+
return String(error);
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/error/error.ts"],"names":[],"mappings":";;AAkCA,sCAuBC;AAGD,4DAgCC;AA5FD,4DAAwD;AACxD,gDAA0C;AAE1C,kDAAkD;AAClD,qDAAqD;AACrD,yCAAyC;AAEzC,WAAW;AACX,2CAA2C;AAC3C,6CAA6C;AAC7C,0CAA0C;AAC1C,+CAA+C;AAC/C,qBAAqB;AACrB,2CAA2C;AAC3C,yCAAyC;AACzC,6DAA6D;AAC7D,qBAAqB;AACrB,qCAAqC;AACrC,0BAA0B;AAC1B,kCAAkC;AAClC,yDAAyD;AACzD,qBAAqB;AACrB,sDAAsD;AACtD,0EAA0E;AAC1E,oEAAoE;AACpE,6EAA6E;AAC7E,iBAAiB;AACjB,mCAAmC;AACnC,4BAA4B;AAC5B,6BAA6B;AAC7B,QAAQ;AACR,IAAI;AAGJ,SAAgB,aAAa,CAAC,KAAc;IAC1C,MAAM,GAAG,GAAG,4BAAY,CAAC,QAAQ,EAAE,CAAC;IAEpC,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,KAAK,CAAC,CAAC;IAExC,gDAAgD;IAChD,IAAI,GAAG,EAAE,YAAY;QAAE,OAAO;IAE9B,IAAI,GAAG;QAAE,GAAG,CAAC,YAAY,GAAG,IAAI,CAAC;IAEjC,4BAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAE/B,IAAA,cAAI,EAAC;QACH,QAAQ,EAAE,GAAG,EAAE,OAAO,IAAI,SAAS;QACnC,OAAO,EAAE,GAAG,EAAE,UAAU,IAAI,SAAS;QACrC,OAAO,EAAE,GAAG,EAAE,OAAO,IAAI,SAAS;QAClC,SAAS,EAAE,GAAG,GAAG,EAAE,KAAK,IAAI,SAAS,EAAE;QACvC,IAAI,EAAE,OAAO;QACb,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,QAAQ,EAAE,GAAG,EAAE,KAAK,IAAI,SAAS;QACjC,SAAS,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,IAAI,cAAc;QACrD,aAAa,EAAE,wBAAwB,CAAC,KAAK,CAAC;KAC/C,CAAC,CAAC;AACL,CAAC;AAGD,SAAgB,wBAAwB,CAAC,KAAc;IACrD,oEAAoE;IACpE,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,aAAa,IAAI,KAAK;QACtB,OAAQ,KAAa,CAAC,WAAW,KAAK,UAAU,EAChD,CAAC;QACD,IAAI,CAAC;YACH,MAAM,QAAQ,GAAI,KAAa,CAAC,WAAW,EAAE,CAAC;YAE9C,8CAA8C;YAC9C,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC;gBACrC,OAAO,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrC,CAAC;YAED,uBAAuB;YACvB,IAAI,OAAO,QAAQ,EAAE,OAAO,KAAK,QAAQ,EAAE,CAAC;gBAC1C,OAAO,QAAQ,CAAC,OAAO,CAAC;YAC1B,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,mCAAmC;QACrC,CAAC;IACH,CAAC;IAED,oBAAoB;IACpB,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC,OAAO,CAAC;IACvB,CAAC;IAED,WAAW;IACX,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.initChronos = exports.errorFunction = exports.traceMiddleware = exports.Trace = void 0;
|
|
4
|
+
var trace_decorator_1 = require("./decorators/trace.decorator");
|
|
5
|
+
Object.defineProperty(exports, "Trace", { enumerable: true, get: function () { return trace_decorator_1.Trace; } });
|
|
6
|
+
var trace_middleware_1 = require("./middleware/trace.middleware");
|
|
7
|
+
Object.defineProperty(exports, "traceMiddleware", { enumerable: true, get: function () { return trace_middleware_1.traceMiddleware; } });
|
|
8
|
+
var error_1 = require("./error/error");
|
|
9
|
+
Object.defineProperty(exports, "errorFunction", { enumerable: true, get: function () { return error_1.errorFunction; } });
|
|
10
|
+
var chronos_config_1 = require("./config/chronos.config");
|
|
11
|
+
Object.defineProperty(exports, "initChronos", { enumerable: true, get: function () { return chronos_config_1.initChronos; } });
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AACA,gEAAqD;AAA5C,wGAAA,KAAK,OAAA;AACd,kEAAgE;AAAvD,mHAAA,eAAe,OAAA;AACxB,uCAA8C;AAArC,sGAAA,aAAa,OAAA;AACtB,0DAAsD;AAA7C,6GAAA,WAAW,OAAA"}
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace.interceptor.js","sourceRoot":"","sources":["../../src/interceptor/trace.interceptor.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function traceMiddleware(req: any, res: any, next: () => void): void;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.traceMiddleware = traceMiddleware;
|
|
4
|
+
// middleware/trace-middleware.ts
|
|
5
|
+
const crypto_1 = require("crypto");
|
|
6
|
+
const trace_context_1 = require("../context/trace-context");
|
|
7
|
+
const emitter_1 = require("../emitter/emitter");
|
|
8
|
+
function traceMiddleware(req, res, next) {
|
|
9
|
+
const traceId = (0, crypto_1.randomUUID)();
|
|
10
|
+
const rootSpanId = (0, crypto_1.randomUUID)();
|
|
11
|
+
const store = {
|
|
12
|
+
traceId,
|
|
13
|
+
service: 'payment-service',
|
|
14
|
+
spanStack: [rootSpanId],
|
|
15
|
+
};
|
|
16
|
+
trace_context_1.TraceContext.run({
|
|
17
|
+
traceId,
|
|
18
|
+
rootSpanId,
|
|
19
|
+
service: 'payment-service',
|
|
20
|
+
}, () => {
|
|
21
|
+
trace_context_1.TraceContext.setStage('REQUEST');
|
|
22
|
+
(0, emitter_1.emit)({
|
|
23
|
+
trace_id: traceId,
|
|
24
|
+
span_id: rootSpanId,
|
|
25
|
+
service: store.service,
|
|
26
|
+
operation: `${req.method} ${req.url}`,
|
|
27
|
+
type: 'start',
|
|
28
|
+
timestamp: new Date().toISOString(),
|
|
29
|
+
});
|
|
30
|
+
res.on('finish', () => {
|
|
31
|
+
(0, emitter_1.emit)({
|
|
32
|
+
trace_id: traceId,
|
|
33
|
+
span_id: rootSpanId,
|
|
34
|
+
service: store.service,
|
|
35
|
+
operation: `${req.method} ${req.url}`,
|
|
36
|
+
type: 'end',
|
|
37
|
+
status_code: res.statusCode,
|
|
38
|
+
success: res.statusCode < 500,
|
|
39
|
+
timestamp: new Date().toISOString(),
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
next();
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=trace.middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace.middleware.js","sourceRoot":"","sources":["../../src/middleware/trace.middleware.ts"],"names":[],"mappings":";;AAKA,0CA2CC;AAhDD,iCAAiC;AACjC,mCAAoC;AACpC,4DAAwD;AACxD,gDAA0C;AAE1C,SAAgB,eAAe,CAAC,GAAQ,EAAE,GAAQ,EAAE,IAAgB;IAClE,MAAM,OAAO,GAAG,IAAA,mBAAU,GAAE,CAAC;IAC7B,MAAM,UAAU,GAAG,IAAA,mBAAU,GAAE,CAAC;IAEhC,MAAM,KAAK,GAAG;QACZ,OAAO;QACP,OAAO,EAAE,iBAAiB;QAC1B,SAAS,EAAE,CAAC,UAAU,CAAC;KACxB,CAAC;IAEF,4BAAY,CAAC,GAAG,CAAC;QACX,OAAO;QACP,UAAU;QACV,OAAO,EAAE,iBAAiB;KAC3B,EAAE,GAAG,EAAE;QAGV,4BAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAEjC,IAAA,cAAI,EAAC;YACH,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,UAAU;YACnB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,SAAS,EAAE,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,GAAG,EAAE;YACrC,IAAI,EAAE,OAAO;YACb,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC,CAAC;QAEH,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;YACpB,IAAA,cAAI,EAAC;gBACH,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,UAAU;gBACnB,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,SAAS,EAAE,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,GAAG,EAAE;gBACrC,IAAI,EAAE,KAAK;gBACX,WAAW,EAAE,GAAG,CAAC,UAAU;gBAC3B,OAAO,EAAE,GAAG,CAAC,UAAU,GAAG,GAAG;gBAC7B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aACpC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,IAAI,EAAE,CAAC;IACT,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runSpan<T>(operation: string, fn: () => Promise<T>, payload?: any): Promise<T>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runSpan = runSpan;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
const emitter_1 = require("../emitter/emitter");
|
|
6
|
+
const trace_context_1 = require("../context/trace-context");
|
|
7
|
+
async function runSpan(operation, fn, payload) {
|
|
8
|
+
const { getCurrentSpan, pushSpan, popSpan } = trace_context_1.TraceContext;
|
|
9
|
+
const store = trace_context_1.TraceContext.getStore();
|
|
10
|
+
if (!store)
|
|
11
|
+
return fn();
|
|
12
|
+
const spanId = (0, crypto_1.randomUUID)();
|
|
13
|
+
const parentSpanId = getCurrentSpan();
|
|
14
|
+
pushSpan(spanId);
|
|
15
|
+
await (0, emitter_1.emit)({
|
|
16
|
+
trace_id: store.traceId,
|
|
17
|
+
span_id: spanId,
|
|
18
|
+
parent_span_id: parentSpanId,
|
|
19
|
+
service: store.service,
|
|
20
|
+
operation,
|
|
21
|
+
type: "start",
|
|
22
|
+
timestamp: new Date().toISOString(),
|
|
23
|
+
payload: payload ? { actual: payload } : undefined,
|
|
24
|
+
});
|
|
25
|
+
try {
|
|
26
|
+
const result = await fn();
|
|
27
|
+
await (0, emitter_1.emit)({
|
|
28
|
+
trace_id: store.traceId,
|
|
29
|
+
span_id: spanId,
|
|
30
|
+
parent_span_id: parentSpanId,
|
|
31
|
+
service: store.service,
|
|
32
|
+
operation,
|
|
33
|
+
type: "end",
|
|
34
|
+
timestamp: new Date().toISOString(),
|
|
35
|
+
});
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
await (0, emitter_1.emit)({
|
|
40
|
+
trace_id: store.traceId,
|
|
41
|
+
span_id: spanId,
|
|
42
|
+
parent_span_id: parentSpanId,
|
|
43
|
+
service: store.service,
|
|
44
|
+
operation,
|
|
45
|
+
type: "error",
|
|
46
|
+
timestamp: new Date().toISOString(),
|
|
47
|
+
error_message: err?.message ?? "Unknown error",
|
|
48
|
+
});
|
|
49
|
+
throw err;
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
popSpan();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=span.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"span.js","sourceRoot":"","sources":["../../src/span/span.ts"],"names":[],"mappings":";;AAMA,0BAuDC;AA7DD,mCAAoC;AACpC,gDAA0C;AAC1C,4DAEkC;AAE3B,KAAK,UAAU,OAAO,CAC3B,SAAiB,EACjB,EAAoB,EACpB,OAAa;IAEb,MAAM,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,4BAAY,CAAC;IAC3D,MAAM,KAAK,GAAG,4BAAY,CAAC,QAAQ,EAAE,CAAC;IACtC,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,EAAE,CAAC;IAExB,MAAM,MAAM,GAAG,IAAA,mBAAU,GAAE,CAAC;IAC5B,MAAM,YAAY,GAAG,cAAc,EAAE,CAAC;IAEtC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAEjB,MAAM,IAAA,cAAI,EAAC;QACT,QAAQ,EAAE,KAAK,CAAC,OAAO;QACvB,OAAO,EAAE,MAAM;QACf,cAAc,EAAE,YAAY;QAC5B,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,SAAS;QACT,IAAI,EAAE,OAAO;QACb,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS;KACnD,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,EAAE,EAAE,CAAC;QAE1B,MAAM,IAAA,cAAI,EAAC;YACT,QAAQ,EAAE,KAAK,CAAC,OAAO;YACvB,OAAO,EAAE,MAAM;YACf,cAAc,EAAE,YAAY;YAC5B,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,SAAS;YACT,IAAI,EAAE,KAAK;YACX,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC,CAAC;QAEH,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,MAAM,IAAA,cAAI,EAAC;YACT,QAAQ,EAAE,KAAK,CAAC,OAAO;YACvB,OAAO,EAAE,MAAM;YACf,cAAc,EAAE,YAAY;YAC5B,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,SAAS;YACT,IAAI,EAAE,OAAO;YACb,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,aAAa,EAAE,GAAG,EAAE,OAAO,IAAI,eAAe;SAC/C,CAAC,CAAC;QAEH,MAAM,GAAG,CAAC;IACZ,CAAC;YAAS,CAAC;QACT,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface TraceEvent {
|
|
2
|
+
trace_id: string;
|
|
3
|
+
span_id: string;
|
|
4
|
+
parent_span_id?: string;
|
|
5
|
+
service: string;
|
|
6
|
+
operation: string;
|
|
7
|
+
type: "start" | "end" | "error";
|
|
8
|
+
timestamp: string;
|
|
9
|
+
status_code?: number;
|
|
10
|
+
success?: boolean;
|
|
11
|
+
error_message?: string;
|
|
12
|
+
errorType?: string;
|
|
13
|
+
failedAt?: string;
|
|
14
|
+
payload?: {
|
|
15
|
+
actual?: any;
|
|
16
|
+
expected?: any;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event.js","sourceRoot":"","sources":["../../src/types/event.ts"],"names":[],"mappings":""}
|
package/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import fs from "fs"
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "chronos-tracer",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"prepublishOnly": "npm run build"
|
|
10
|
+
},
|
|
11
|
+
"keywords": ["tracing", "chronos", "decorator"],
|
|
12
|
+
"author": "Yousuf Khan",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"description": "package for tracing using decorators",
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"esm": "^3.2.25",
|
|
17
|
+
"ts-node": "^10.9.2",
|
|
18
|
+
"typescript": "^5.9.3"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type ChronosConfig = {
|
|
2
|
+
endpoint: string;
|
|
3
|
+
service: string;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
let config: ChronosConfig | null = null;
|
|
7
|
+
|
|
8
|
+
export function initChronos(userConfig: ChronosConfig) {
|
|
9
|
+
config = userConfig;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getChronosConfig(): ChronosConfig {
|
|
13
|
+
if (!config) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
"Chronos is not initialized. Call initChronos() in your app startup."
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
return config;
|
|
19
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
|
|
4
|
+
export type TraceStore = {
|
|
5
|
+
traceId: string;
|
|
6
|
+
rootSpanId: string;
|
|
7
|
+
service: string;
|
|
8
|
+
spanStack: string[];
|
|
9
|
+
stage?: string;
|
|
10
|
+
errorEmitted?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
class TraceContextClass {
|
|
14
|
+
private als = new AsyncLocalStorage<TraceStore>();
|
|
15
|
+
|
|
16
|
+
run(store: Omit<TraceStore, "spanStack">, fn: () => void) {
|
|
17
|
+
this.als.run({ ...store, spanStack: [store.rootSpanId] }, fn);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
/** Get current store */
|
|
22
|
+
getStore(): TraceStore | undefined {
|
|
23
|
+
return this.als.getStore();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
setStage(stage: string) {
|
|
27
|
+
const ctx = this.als.getStore();
|
|
28
|
+
if (ctx) {
|
|
29
|
+
ctx.stage = stage;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Span helpers */
|
|
34
|
+
getCurrentSpan(): string | undefined {
|
|
35
|
+
const store = this.getStore();
|
|
36
|
+
return store?.spanStack[store.spanStack.length - 1];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
pushSpan(spanId: string) {
|
|
40
|
+
const store = this.getStore();
|
|
41
|
+
if (store) store.spanStack.push(spanId);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
popSpan() {
|
|
45
|
+
const store = this.getStore();
|
|
46
|
+
if (store) store.spanStack.pop();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const TraceContext = new TraceContextClass();
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import { TraceContext } from '../context/trace-context';
|
|
3
|
+
import { emit } from '../emitter/emitter';
|
|
4
|
+
|
|
5
|
+
export function Trace(operation?: string) {
|
|
6
|
+
return function (
|
|
7
|
+
target: any,
|
|
8
|
+
propertyKey: string,
|
|
9
|
+
descriptor: PropertyDescriptor
|
|
10
|
+
) {
|
|
11
|
+
const original = descriptor.value;
|
|
12
|
+
|
|
13
|
+
descriptor.value = async function (...args: any[]) {
|
|
14
|
+
const store = TraceContext.getStore();
|
|
15
|
+
|
|
16
|
+
// 🔴 If no trace context, DO NOTHING
|
|
17
|
+
if (!store) {
|
|
18
|
+
return original.apply(this, args);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const spanId = randomUUID();
|
|
22
|
+
const parentSpanId = TraceContext.getCurrentSpan();
|
|
23
|
+
const opName =
|
|
24
|
+
operation ?? `${target.constructor.name}.${propertyKey}`;
|
|
25
|
+
|
|
26
|
+
TraceContext.pushSpan(spanId);
|
|
27
|
+
|
|
28
|
+
TraceContext.setStage(opName);
|
|
29
|
+
|
|
30
|
+
emit({
|
|
31
|
+
trace_id: store.traceId,
|
|
32
|
+
span_id: spanId,
|
|
33
|
+
parent_span_id: parentSpanId,
|
|
34
|
+
service: store.service,
|
|
35
|
+
operation: opName,
|
|
36
|
+
type: 'start',
|
|
37
|
+
timestamp: Date.now().toString(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const result = await original.apply(this, args);
|
|
42
|
+
|
|
43
|
+
emit({
|
|
44
|
+
trace_id: store.traceId,
|
|
45
|
+
span_id: spanId,
|
|
46
|
+
parent_span_id: parentSpanId,
|
|
47
|
+
service: store.service,
|
|
48
|
+
operation: opName,
|
|
49
|
+
type: 'end',
|
|
50
|
+
timestamp: Date.now().toString(),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return result;
|
|
54
|
+
} catch (err: any) {
|
|
55
|
+
emit({
|
|
56
|
+
trace_id: store.traceId,
|
|
57
|
+
span_id: spanId,
|
|
58
|
+
parent_span_id: parentSpanId,
|
|
59
|
+
service: store.service,
|
|
60
|
+
operation: opName,
|
|
61
|
+
type: 'error',
|
|
62
|
+
error_message: err?.message ?? 'Unknown error',
|
|
63
|
+
status_code: err?.status,
|
|
64
|
+
timestamp: Date.now().toString(),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
throw err;
|
|
68
|
+
} finally {
|
|
69
|
+
TraceContext.popSpan();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getChronosConfig } from "../config/chronos.config";
|
|
2
|
+
import { TraceEvent } from "../types/event";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export async function emit(event: TraceEvent){
|
|
6
|
+
const {endpoint} = getChronosConfig()
|
|
7
|
+
try{
|
|
8
|
+
await fetch(endpoint, {
|
|
9
|
+
method: "POST",
|
|
10
|
+
headers: {
|
|
11
|
+
"Content-Type": "application/json"
|
|
12
|
+
},
|
|
13
|
+
body: JSON.stringify(event)
|
|
14
|
+
});
|
|
15
|
+
}catch(err){
|
|
16
|
+
console.error("Failed to emit trace event:", err);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { TraceContext } from "../context/trace-context";
|
|
2
|
+
import { emit } from "../emitter/emitter";
|
|
3
|
+
|
|
4
|
+
// export function errorFunction(error: unknown) {
|
|
5
|
+
// console.log("Logging error trace event", error);
|
|
6
|
+
// const ctx = TraceContext.getStore();
|
|
7
|
+
|
|
8
|
+
// emit({
|
|
9
|
+
// trace_id: ctx?.traceId || "unknown",
|
|
10
|
+
// span_id: ctx?.rootSpanId || "unknown",
|
|
11
|
+
// service: ctx?.service || "unknown",
|
|
12
|
+
// operation: `${ctx?.stage || "unknown"}`,
|
|
13
|
+
// type: "start",
|
|
14
|
+
// timestamp: new Date().toISOString(),
|
|
15
|
+
// failedAt: ctx?.stage ?? "UNKNOWN",
|
|
16
|
+
// errorType: error?.constructor?.name ?? "UnknownError",
|
|
17
|
+
// error_message:
|
|
18
|
+
// typeof error === "object" &&
|
|
19
|
+
// error !== null &&
|
|
20
|
+
// "getResponse" in error &&
|
|
21
|
+
// typeof (error as any).getResponse === "function"
|
|
22
|
+
// ? (() => {
|
|
23
|
+
// const r = (error as any).getResponse();
|
|
24
|
+
// if (Array.isArray(r?.message)) return r.message.join(", ");
|
|
25
|
+
// if (typeof r?.message === "string") return r.message;
|
|
26
|
+
// return error instanceof Error ? error.message : String(error);
|
|
27
|
+
// })()
|
|
28
|
+
// : error instanceof Error
|
|
29
|
+
// ? error.message
|
|
30
|
+
// : String(error),
|
|
31
|
+
// });
|
|
32
|
+
// }
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
export function errorFunction(error: unknown) {
|
|
36
|
+
const ctx = TraceContext.getStore();
|
|
37
|
+
|
|
38
|
+
console.log("Error trace event", error);
|
|
39
|
+
|
|
40
|
+
// 🚫 If already emitted once, do NOT emit again
|
|
41
|
+
if (ctx?.errorEmitted) return;
|
|
42
|
+
|
|
43
|
+
if (ctx) ctx.errorEmitted = true;
|
|
44
|
+
|
|
45
|
+
TraceContext.setStage('ERROR');
|
|
46
|
+
|
|
47
|
+
emit({
|
|
48
|
+
trace_id: ctx?.traceId || "unknown",
|
|
49
|
+
span_id: ctx?.rootSpanId || "unknown",
|
|
50
|
+
service: ctx?.service || "unknown",
|
|
51
|
+
operation: `${ctx?.stage || "unknown"}`,
|
|
52
|
+
type: "error",
|
|
53
|
+
timestamp: new Date().toISOString(),
|
|
54
|
+
failedAt: ctx?.stage ?? "UNKNOWN",
|
|
55
|
+
errorType: error?.constructor?.name ?? "UnknownError",
|
|
56
|
+
error_message: extractMeaningfulMessage(error),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
export function extractMeaningfulMessage(error: unknown): string {
|
|
62
|
+
// NestJS / HttpException-like errors (ValidationPipe, Guards, etc.)
|
|
63
|
+
if (
|
|
64
|
+
typeof error === 'object' &&
|
|
65
|
+
error !== null &&
|
|
66
|
+
'getResponse' in error &&
|
|
67
|
+
typeof (error as any).getResponse === 'function'
|
|
68
|
+
) {
|
|
69
|
+
try {
|
|
70
|
+
const response = (error as any).getResponse();
|
|
71
|
+
|
|
72
|
+
// ValidationPipe: message is usually an array
|
|
73
|
+
if (Array.isArray(response?.message)) {
|
|
74
|
+
return response.message.join(', ');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Other HttpExceptions
|
|
78
|
+
if (typeof response?.message === 'string') {
|
|
79
|
+
return response.message;
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
// fall through to generic handling
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Standard JS Error
|
|
87
|
+
if (error instanceof Error) {
|
|
88
|
+
return error.message;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Fallback
|
|
92
|
+
return String(error);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// middleware/trace-middleware.ts
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import { TraceContext } from '../context/trace-context';
|
|
4
|
+
import { emit } from '../emitter/emitter';
|
|
5
|
+
|
|
6
|
+
export function traceMiddleware(req: any, res: any, next: () => void) {
|
|
7
|
+
const traceId = randomUUID();
|
|
8
|
+
const rootSpanId = randomUUID();
|
|
9
|
+
|
|
10
|
+
const store = {
|
|
11
|
+
traceId,
|
|
12
|
+
service: 'payment-service',
|
|
13
|
+
spanStack: [rootSpanId],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
TraceContext.run({
|
|
17
|
+
traceId,
|
|
18
|
+
rootSpanId,
|
|
19
|
+
service: 'payment-service',
|
|
20
|
+
}, () => {
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
TraceContext.setStage('REQUEST');
|
|
24
|
+
|
|
25
|
+
emit({
|
|
26
|
+
trace_id: traceId,
|
|
27
|
+
span_id: rootSpanId,
|
|
28
|
+
service: store.service,
|
|
29
|
+
operation: `${req.method} ${req.url}`,
|
|
30
|
+
type: 'start',
|
|
31
|
+
timestamp: new Date().toISOString(),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
res.on('finish', () => {
|
|
35
|
+
emit({
|
|
36
|
+
trace_id: traceId,
|
|
37
|
+
span_id: rootSpanId,
|
|
38
|
+
service: store.service,
|
|
39
|
+
operation: `${req.method} ${req.url}`,
|
|
40
|
+
type: 'end',
|
|
41
|
+
status_code: res.statusCode,
|
|
42
|
+
success: res.statusCode < 500,
|
|
43
|
+
timestamp: new Date().toISOString(),
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
next();
|
|
48
|
+
});
|
|
49
|
+
}
|
package/src/span/span.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
2
|
+
import { emit } from "../emitter/emitter";
|
|
3
|
+
import {
|
|
4
|
+
TraceContext
|
|
5
|
+
} from "../context/trace-context";
|
|
6
|
+
|
|
7
|
+
export async function runSpan<T>(
|
|
8
|
+
operation: string,
|
|
9
|
+
fn: () => Promise<T>,
|
|
10
|
+
payload?: any
|
|
11
|
+
): Promise<T> {
|
|
12
|
+
const { getCurrentSpan, pushSpan, popSpan } = TraceContext;
|
|
13
|
+
const store = TraceContext.getStore();
|
|
14
|
+
if (!store) return fn();
|
|
15
|
+
|
|
16
|
+
const spanId = randomUUID();
|
|
17
|
+
const parentSpanId = getCurrentSpan();
|
|
18
|
+
|
|
19
|
+
pushSpan(spanId);
|
|
20
|
+
|
|
21
|
+
await emit({
|
|
22
|
+
trace_id: store.traceId,
|
|
23
|
+
span_id: spanId,
|
|
24
|
+
parent_span_id: parentSpanId,
|
|
25
|
+
service: store.service,
|
|
26
|
+
operation,
|
|
27
|
+
type: "start",
|
|
28
|
+
timestamp: new Date().toISOString(),
|
|
29
|
+
payload: payload ? { actual: payload } : undefined,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const result = await fn();
|
|
34
|
+
|
|
35
|
+
await emit({
|
|
36
|
+
trace_id: store.traceId,
|
|
37
|
+
span_id: spanId,
|
|
38
|
+
parent_span_id: parentSpanId,
|
|
39
|
+
service: store.service,
|
|
40
|
+
operation,
|
|
41
|
+
type: "end",
|
|
42
|
+
timestamp: new Date().toISOString(),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return result;
|
|
46
|
+
} catch (err: any) {
|
|
47
|
+
await emit({
|
|
48
|
+
trace_id: store.traceId,
|
|
49
|
+
span_id: spanId,
|
|
50
|
+
parent_span_id: parentSpanId,
|
|
51
|
+
service: store.service,
|
|
52
|
+
operation,
|
|
53
|
+
type: "error",
|
|
54
|
+
timestamp: new Date().toISOString(),
|
|
55
|
+
error_message: err?.message ?? "Unknown error",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
throw err;
|
|
59
|
+
} finally {
|
|
60
|
+
popSpan();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface TraceEvent {
|
|
2
|
+
trace_id: string;
|
|
3
|
+
span_id: string;
|
|
4
|
+
parent_span_id?: string;
|
|
5
|
+
|
|
6
|
+
service: string;
|
|
7
|
+
operation: string;
|
|
8
|
+
|
|
9
|
+
type: "start" | "end" | "error";
|
|
10
|
+
|
|
11
|
+
timestamp: string;
|
|
12
|
+
|
|
13
|
+
status_code?: number;
|
|
14
|
+
success?: boolean;
|
|
15
|
+
|
|
16
|
+
error_message?: string;
|
|
17
|
+
|
|
18
|
+
errorType?: string;
|
|
19
|
+
failedAt?: string;
|
|
20
|
+
|
|
21
|
+
payload?: {
|
|
22
|
+
actual?: any;
|
|
23
|
+
expected?: any;
|
|
24
|
+
};
|
|
25
|
+
}
|
package/tsconfig.json
ADDED