@graphorin/observability 0.5.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/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +115 -0
- package/dist/cost/cost-tracker.d.ts +28 -0
- package/dist/cost/cost-tracker.d.ts.map +1 -0
- package/dist/cost/cost-tracker.js +169 -0
- package/dist/cost/cost-tracker.js.map +1 -0
- package/dist/cost/index.d.ts +3 -0
- package/dist/cost/index.js +3 -0
- package/dist/cost/types.d.ts +104 -0
- package/dist/cost/types.d.ts.map +1 -0
- package/dist/eval/index.d.ts +3 -0
- package/dist/eval/index.js +3 -0
- package/dist/eval/runner.d.ts +11 -0
- package/dist/eval/runner.d.ts.map +1 -0
- package/dist/eval/runner.js +113 -0
- package/dist/eval/runner.js.map +1 -0
- package/dist/eval/types.d.ts +108 -0
- package/dist/eval/types.d.ts.map +1 -0
- package/dist/exporters/console.d.ts +27 -0
- package/dist/exporters/console.d.ts.map +1 -0
- package/dist/exporters/console.js +47 -0
- package/dist/exporters/console.js.map +1 -0
- package/dist/exporters/index.d.ts +6 -0
- package/dist/exporters/index.js +7 -0
- package/dist/exporters/jsonl.d.ts +57 -0
- package/dist/exporters/jsonl.d.ts.map +1 -0
- package/dist/exporters/jsonl.js +95 -0
- package/dist/exporters/jsonl.js.map +1 -0
- package/dist/exporters/otlp-http.d.ts +38 -0
- package/dist/exporters/otlp-http.d.ts.map +1 -0
- package/dist/exporters/otlp-http.js +115 -0
- package/dist/exporters/otlp-http.js.map +1 -0
- package/dist/exporters/types.d.ts +72 -0
- package/dist/exporters/types.d.ts.map +1 -0
- package/dist/exporters/types.js +13 -0
- package/dist/exporters/types.js.map +1 -0
- package/dist/exporters/with-validation.d.ts +45 -0
- package/dist/exporters/with-validation.d.ts.map +1 -0
- package/dist/exporters/with-validation.js +99 -0
- package/dist/exporters/with-validation.js.map +1 -0
- package/dist/gen-ai/emit.d.ts +27 -0
- package/dist/gen-ai/emit.d.ts.map +1 -0
- package/dist/gen-ai/emit.js +88 -0
- package/dist/gen-ai/emit.js.map +1 -0
- package/dist/gen-ai/index.d.ts +5 -0
- package/dist/gen-ai/index.js +5 -0
- package/dist/gen-ai/operation-mapping.d.ts +24 -0
- package/dist/gen-ai/operation-mapping.d.ts.map +1 -0
- package/dist/gen-ai/operation-mapping.js +67 -0
- package/dist/gen-ai/operation-mapping.js.map +1 -0
- package/dist/gen-ai/system-derivation.d.ts +41 -0
- package/dist/gen-ai/system-derivation.d.ts.map +1 -0
- package/dist/gen-ai/system-derivation.js +67 -0
- package/dist/gen-ai/system-derivation.js.map +1 -0
- package/dist/gen-ai/types.d.ts +87 -0
- package/dist/gen-ai/types.d.ts.map +1 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/index.d.ts +2 -0
- package/dist/logger/index.js +3 -0
- package/dist/logger/logger.d.ts +53 -0
- package/dist/logger/logger.d.ts.map +1 -0
- package/dist/logger/logger.js +145 -0
- package/dist/logger/logger.js.map +1 -0
- package/dist/openinference/index.d.ts +45 -0
- package/dist/openinference/index.d.ts.map +1 -0
- package/dist/openinference/index.js +92 -0
- package/dist/openinference/index.js.map +1 -0
- package/dist/redaction/config.d.ts +39 -0
- package/dist/redaction/config.d.ts.map +1 -0
- package/dist/redaction/config.js +15 -0
- package/dist/redaction/config.js.map +1 -0
- package/dist/redaction/errors.d.ts +36 -0
- package/dist/redaction/errors.d.ts.map +1 -0
- package/dist/redaction/errors.js +42 -0
- package/dist/redaction/errors.js.map +1 -0
- package/dist/redaction/imperative-patterns.d.ts +108 -0
- package/dist/redaction/imperative-patterns.d.ts.map +1 -0
- package/dist/redaction/imperative-patterns.js +187 -0
- package/dist/redaction/imperative-patterns.js.map +1 -0
- package/dist/redaction/index.d.ts +7 -0
- package/dist/redaction/index.js +7 -0
- package/dist/redaction/patterns.d.ts +85 -0
- package/dist/redaction/patterns.d.ts.map +1 -0
- package/dist/redaction/patterns.js +172 -0
- package/dist/redaction/patterns.js.map +1 -0
- package/dist/redaction/types.d.ts +103 -0
- package/dist/redaction/types.d.ts.map +1 -0
- package/dist/redaction/validator.d.ts +26 -0
- package/dist/redaction/validator.d.ts.map +1 -0
- package/dist/redaction/validator.js +195 -0
- package/dist/redaction/validator.js.map +1 -0
- package/dist/replay/config.d.ts +56 -0
- package/dist/replay/config.d.ts.map +1 -0
- package/dist/replay/config.js +18 -0
- package/dist/replay/config.js.map +1 -0
- package/dist/replay/index.d.ts +5 -0
- package/dist/replay/index.js +5 -0
- package/dist/replay/log.d.ts +39 -0
- package/dist/replay/log.d.ts.map +1 -0
- package/dist/replay/log.js +59 -0
- package/dist/replay/log.js.map +1 -0
- package/dist/replay/replay.d.ts +25 -0
- package/dist/replay/replay.d.ts.map +1 -0
- package/dist/replay/replay.js +173 -0
- package/dist/replay/replay.js.map +1 -0
- package/dist/replay/types.d.ts +110 -0
- package/dist/replay/types.d.ts.map +1 -0
- package/dist/telemetry/index.d.ts +63 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +49 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/tracer/ids.d.ts +23 -0
- package/dist/tracer/ids.d.ts.map +1 -0
- package/dist/tracer/ids.js +30 -0
- package/dist/tracer/ids.js.map +1 -0
- package/dist/tracer/index.d.ts +6 -0
- package/dist/tracer/index.js +6 -0
- package/dist/tracer/sampling.d.ts +77 -0
- package/dist/tracer/sampling.d.ts.map +1 -0
- package/dist/tracer/sampling.js +67 -0
- package/dist/tracer/sampling.js.map +1 -0
- package/dist/tracer/span-names.d.ts +11 -0
- package/dist/tracer/span-names.d.ts.map +1 -0
- package/dist/tracer/span-names.js +11 -0
- package/dist/tracer/span-names.js.map +1 -0
- package/dist/tracer/span.d.ts +27 -0
- package/dist/tracer/span.d.ts.map +1 -0
- package/dist/tracer/span.js +80 -0
- package/dist/tracer/span.js.map +1 -0
- package/dist/tracer/tracer.d.ts +101 -0
- package/dist/tracer/tracer.d.ts.map +1 -0
- package/dist/tracer/tracer.js +150 -0
- package/dist/tracer/tracer.js.map +1 -0
- package/package.json +138 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
//#region src/eval/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Public types for the minimal inline eval runner. Full orchestrator,
|
|
4
|
+
* scorer libraries, dataset loaders, and reporters live in the
|
|
5
|
+
* separate `@graphorin/evals` package (post-MVP).
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* One sample from an eval dataset.
|
|
11
|
+
*
|
|
12
|
+
* @stable
|
|
13
|
+
*/
|
|
14
|
+
interface Case<I, O = unknown, M = Readonly<Record<string, unknown>>> {
|
|
15
|
+
readonly id?: string;
|
|
16
|
+
readonly input: I;
|
|
17
|
+
readonly expected?: O;
|
|
18
|
+
readonly metadata?: M;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* @stable
|
|
22
|
+
*/
|
|
23
|
+
interface Dataset<I, O = unknown, M = Readonly<Record<string, unknown>>> {
|
|
24
|
+
readonly cases: ReadonlyArray<Case<I, O, M>>;
|
|
25
|
+
readonly metadata?: {
|
|
26
|
+
readonly name?: string;
|
|
27
|
+
readonly description?: string;
|
|
28
|
+
readonly createdAt?: Date;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Output of {@link Scorer.score}.
|
|
33
|
+
*
|
|
34
|
+
* @stable
|
|
35
|
+
*/
|
|
36
|
+
interface ScoreResult {
|
|
37
|
+
readonly pass: boolean;
|
|
38
|
+
/** Optional normalized score in `[0, 1]`. */
|
|
39
|
+
readonly score?: number;
|
|
40
|
+
readonly reason?: string;
|
|
41
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* @stable
|
|
45
|
+
*/
|
|
46
|
+
interface Scorer<I, O = unknown> {
|
|
47
|
+
readonly name: string;
|
|
48
|
+
score(args: {
|
|
49
|
+
readonly case: Case<I, O>;
|
|
50
|
+
readonly output: O;
|
|
51
|
+
readonly durationMs: number;
|
|
52
|
+
}): Promise<ScoreResult>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Per-case result.
|
|
56
|
+
*
|
|
57
|
+
* @stable
|
|
58
|
+
*/
|
|
59
|
+
interface EvalCaseResult<I, O> {
|
|
60
|
+
readonly caseId: string;
|
|
61
|
+
readonly input: I;
|
|
62
|
+
readonly output: O;
|
|
63
|
+
readonly durationMs: number;
|
|
64
|
+
readonly scores: ReadonlyArray<{
|
|
65
|
+
readonly scorer: string;
|
|
66
|
+
readonly result: ScoreResult;
|
|
67
|
+
}>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Final report shape.
|
|
71
|
+
*
|
|
72
|
+
* @stable
|
|
73
|
+
*/
|
|
74
|
+
interface EvalReport<I, O> {
|
|
75
|
+
readonly results: ReadonlyArray<EvalCaseResult<I, O>>;
|
|
76
|
+
readonly summary: {
|
|
77
|
+
readonly total: number;
|
|
78
|
+
readonly passed: number;
|
|
79
|
+
readonly failed: number;
|
|
80
|
+
readonly avgDurationMs: number;
|
|
81
|
+
readonly byScorer: Readonly<Record<string, {
|
|
82
|
+
readonly passed: number;
|
|
83
|
+
readonly failed: number;
|
|
84
|
+
readonly avgScore: number | null;
|
|
85
|
+
}>>;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* `true` when the run was cut short by an aborted signal — `results` and
|
|
89
|
+
* `summary` then cover only the cases that finished before the abort (a
|
|
90
|
+
* partial report). Absent on a normal full run. See `runEvals`.
|
|
91
|
+
*/
|
|
92
|
+
readonly aborted?: boolean;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* @stable
|
|
96
|
+
*/
|
|
97
|
+
interface RunEvalOptions<I, O> {
|
|
98
|
+
readonly agent: {
|
|
99
|
+
readonly run: (input: I) => Promise<O>;
|
|
100
|
+
};
|
|
101
|
+
readonly dataset: Dataset<I, O>;
|
|
102
|
+
readonly scorers: ReadonlyArray<Scorer<I, O>>;
|
|
103
|
+
readonly iterations?: number;
|
|
104
|
+
readonly signal?: AbortSignal;
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { Case, Dataset, EvalCaseResult, EvalReport, RunEvalOptions, ScoreResult, Scorer };
|
|
108
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/eval/types.ts"],"sourcesContent":[],"mappings":";;AAaA;;;;;;;AAUA;;;;AACwC,UAXvB,IAWuB,CAAA,CAAA,EAAA,IAAA,OAAA,EAAA,IAXE,QAWF,CAXW,MAWX,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA,CAAA,CAAA;EAAG,SAAA,EAAA,CAAA,EAAA,MAAA;EAAX,SAAA,KAAA,EATd,CASc;EAAd,SAAA,QAAA,CAAA,EARI,CAQJ;EAIO,SAAA,QAAA,CAAA,EAXH,CAWG;;AASzB;AAWA;;AAG2B,UA5BV,OA4BU,CAAA,CAAA,EAAA,IAAA,OAAA,EAAA,IA5BkB,QA4BlB,CA5B2B,MA4B3B,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA,CAAA,CAAA;EAAR,SAAA,KAAA,EA3BD,aA2BC,CA3Ba,IA2Bb,CA3BkB,CA2BlB,EA3BqB,CA2BrB,EA3BwB,CA2BxB,CAAA,CAAA;EACE,SAAA,QAAA,CAAA,EAAA;IAEP,SAAA,IAAA,CAAA,EAAA,MAAA;IAAR,SAAA,WAAA,CAAA,EAAA,MAAA;IAAO,SAAA,SAAA,CAAA,EA1BY,IA0BZ;EAQI,CAAA;;;;;;AAajB;AACiD,UAvChC,WAAA,CAuCgC;EAAG,SAAA,IAAA,EAAA,OAAA;EAAlB;EAAd,SAAA,KAAA,CAAA,EAAA,MAAA;EAOd,SAAA,MAAA,CAAA,EAAA,MAAA;EADiB,SAAA,QAAA,CAAA,EAxCD,QAwCC,CAxCQ,MAwCR,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA;;AAkBvB;;;AACgD,UArD/B,MAqD+B,CAAA,CAAA,EAAA,IAAA,OAAA,CAAA,CAAA;EACpB,SAAA,IAAA,EAAA,MAAA;EAAG,KAAA,CAAA,IAAA,EAAA;IAAX,SAAA,IAAA,EAnDD,IAmDC,CAnDI,CAmDJ,EAnDO,CAmDP,CAAA;IACqB,SAAA,MAAA,EAnDpB,CAmDoB;IAAG,SAAA,UAAA,EAAA,MAAA;EAAV,CAAA,CAAA,EAjD5B,OAiD4B,CAjDpB,WAiDoB,CAAA;;;;;;;UAzCjB;;kBAEC;mBACC;;mBAEA;;qBAA0D;;;;;;;;UAQ5D;oBACG,cAAc,eAAe,GAAG;;;;;;uBAM7B,SACjB;;;;;;;;;;;;;;;;UAiBW;;0BACyB,MAAM,QAAQ;;oBACpC,QAAQ,GAAG;oBACX,cAAc,OAAO,GAAG;;oBAExB"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { TraceExporter } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/exporters/console.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configuration shape for {@link createConsoleExporter}.
|
|
7
|
+
*
|
|
8
|
+
* @stable
|
|
9
|
+
*/
|
|
10
|
+
interface ConsoleExporterOptions {
|
|
11
|
+
/** Identifier reported via `exporter.id`. Defaults to `'console'`. */
|
|
12
|
+
readonly id?: string;
|
|
13
|
+
/** When `true`, emit JSON pretty-printed across multiple lines. */
|
|
14
|
+
readonly pretty?: boolean;
|
|
15
|
+
/** Custom sink. Defaults to `console.log`. */
|
|
16
|
+
readonly sink?: (line: string) => void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Build a console-based trace exporter. Call `withValidation(exporter)`
|
|
20
|
+
* before passing the result to `createTracer({ exporters })`.
|
|
21
|
+
*
|
|
22
|
+
* @stable
|
|
23
|
+
*/
|
|
24
|
+
declare function createConsoleExporter(opts?: ConsoleExporterOptions): TraceExporter;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { ConsoleExporterOptions, createConsoleExporter };
|
|
27
|
+
//# sourceMappingURL=console.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"console.d.ts","names":[],"sources":["../../src/exporters/console.ts"],"sourcesContent":[],"mappings":";;;;;;;;;UAiBiB,sBAAA;;;;;;;;;;;;;;iBAeD,qBAAA,QAA4B,yBAA8B"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//#region src/exporters/console.ts
|
|
2
|
+
/**
|
|
3
|
+
* Build a console-based trace exporter. Call `withValidation(exporter)`
|
|
4
|
+
* before passing the result to `createTracer({ exporters })`.
|
|
5
|
+
*
|
|
6
|
+
* @stable
|
|
7
|
+
*/
|
|
8
|
+
function createConsoleExporter(opts = {}) {
|
|
9
|
+
const id = opts.id ?? "console";
|
|
10
|
+
const pretty = opts.pretty === true;
|
|
11
|
+
const sink = opts.sink ?? ((line) => console.log(line));
|
|
12
|
+
let closed = false;
|
|
13
|
+
return {
|
|
14
|
+
id,
|
|
15
|
+
async export(record) {
|
|
16
|
+
if (closed) return;
|
|
17
|
+
sink(pretty ? JSON.stringify(serializableRecord(record), null, 2) : JSON.stringify(serializableRecord(record)));
|
|
18
|
+
},
|
|
19
|
+
async flush() {},
|
|
20
|
+
async shutdown() {
|
|
21
|
+
closed = true;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
function serializableRecord(record) {
|
|
29
|
+
return {
|
|
30
|
+
type: record.type,
|
|
31
|
+
id: record.id,
|
|
32
|
+
traceId: record.traceId,
|
|
33
|
+
...record.parentId === void 0 ? {} : { parentId: record.parentId },
|
|
34
|
+
name: record.name,
|
|
35
|
+
startUnixNano: record.startUnixNano,
|
|
36
|
+
endUnixNano: record.endUnixNano,
|
|
37
|
+
status: record.status,
|
|
38
|
+
...record.statusMessage === void 0 ? {} : { statusMessage: record.statusMessage },
|
|
39
|
+
attributes: record.attributes,
|
|
40
|
+
events: record.events,
|
|
41
|
+
...record.droppedReason === void 0 ? {} : { droppedReason: record.droppedReason }
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
export { createConsoleExporter, serializableRecord };
|
|
47
|
+
//# sourceMappingURL=console.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"console.js","names":[],"sources":["../../src/exporters/console.ts"],"sourcesContent":["/**\n * `ConsoleExporter` — pretty-prints finished spans to `console.log`.\n *\n * Useful for development and unit testing. Production deployments\n * should use {@link JSONLExporter} (for replay) or\n * {@link OTLPHttpExporter} (for remote OTLP collectors).\n *\n * @packageDocumentation\n */\n\nimport type { SpanRecord, TraceExporter } from './types.js';\n\n/**\n * Configuration shape for {@link createConsoleExporter}.\n *\n * @stable\n */\nexport interface ConsoleExporterOptions {\n /** Identifier reported via `exporter.id`. Defaults to `'console'`. */\n readonly id?: string;\n /** When `true`, emit JSON pretty-printed across multiple lines. */\n readonly pretty?: boolean;\n /** Custom sink. Defaults to `console.log`. */\n readonly sink?: (line: string) => void;\n}\n\n/**\n * Build a console-based trace exporter. Call `withValidation(exporter)`\n * before passing the result to `createTracer({ exporters })`.\n *\n * @stable\n */\nexport function createConsoleExporter(opts: ConsoleExporterOptions = {}): TraceExporter {\n const id = opts.id ?? 'console';\n const pretty = opts.pretty === true;\n const sink = opts.sink ?? ((line: string) => console.log(line));\n\n let closed = false;\n return {\n id,\n async export(record: SpanRecord): Promise<void> {\n if (closed) return;\n const payload = pretty\n ? JSON.stringify(serializableRecord(record), null, 2)\n : JSON.stringify(serializableRecord(record));\n sink(payload);\n },\n async flush(): Promise<void> {\n // Console.log is synchronous on Node; nothing to flush.\n },\n async shutdown(): Promise<void> {\n closed = true;\n },\n };\n}\n\n/**\n * @internal\n */\nexport function serializableRecord(record: SpanRecord): Record<string, unknown> {\n return {\n type: record.type,\n id: record.id,\n traceId: record.traceId,\n ...(record.parentId === undefined ? {} : { parentId: record.parentId }),\n name: record.name,\n startUnixNano: record.startUnixNano,\n endUnixNano: record.endUnixNano,\n status: record.status,\n ...(record.statusMessage === undefined ? {} : { statusMessage: record.statusMessage }),\n attributes: record.attributes,\n events: record.events,\n ...(record.droppedReason === undefined ? {} : { droppedReason: record.droppedReason }),\n };\n}\n"],"mappings":";;;;;;;AAgCA,SAAgB,sBAAsB,OAA+B,EAAE,EAAiB;CACtF,MAAM,KAAK,KAAK,MAAM;CACtB,MAAM,SAAS,KAAK,WAAW;CAC/B,MAAM,OAAO,KAAK,UAAU,SAAiB,QAAQ,IAAI,KAAK;CAE9D,IAAI,SAAS;AACb,QAAO;EACL;EACA,MAAM,OAAO,QAAmC;AAC9C,OAAI,OAAQ;AAIZ,QAHgB,SACZ,KAAK,UAAU,mBAAmB,OAAO,EAAE,MAAM,EAAE,GACnD,KAAK,UAAU,mBAAmB,OAAO,CAAC,CACjC;;EAEf,MAAM,QAAuB;EAG7B,MAAM,WAA0B;AAC9B,YAAS;;EAEZ;;;;;AAMH,SAAgB,mBAAmB,QAA6C;AAC9E,QAAO;EACL,MAAM,OAAO;EACb,IAAI,OAAO;EACX,SAAS,OAAO;EAChB,GAAI,OAAO,aAAa,SAAY,EAAE,GAAG,EAAE,UAAU,OAAO,UAAU;EACtE,MAAM,OAAO;EACb,eAAe,OAAO;EACtB,aAAa,OAAO;EACpB,QAAQ,OAAO;EACf,GAAI,OAAO,kBAAkB,SAAY,EAAE,GAAG,EAAE,eAAe,OAAO,eAAe;EACrF,YAAY,OAAO;EACnB,QAAQ,OAAO;EACf,GAAI,OAAO,kBAAkB,SAAY,EAAE,GAAG,EAAE,eAAe,OAAO,eAAe;EACtF"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { SpanRecord, SpanRecordEvent, TraceExporter, VALIDATED_EXPORTER_BRAND } from "./types.js";
|
|
2
|
+
import { ConsoleExporterOptions, createConsoleExporter } from "./console.js";
|
|
3
|
+
import { JSONLExporterOptions, createJSONLExporter } from "./jsonl.js";
|
|
4
|
+
import { OTLPHttpExporterOptions, createOTLPHttpExporter } from "./otlp-http.js";
|
|
5
|
+
import { WithValidationOptions, isValidatedExporter, tryGetValidatorCounters, withValidation } from "./with-validation.js";
|
|
6
|
+
export { type ConsoleExporterOptions, type JSONLExporterOptions, type OTLPHttpExporterOptions, type SpanRecord, type SpanRecordEvent, type TraceExporter, VALIDATED_EXPORTER_BRAND, type WithValidationOptions, createConsoleExporter, createJSONLExporter, createOTLPHttpExporter, isValidatedExporter, tryGetValidatorCounters, withValidation };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createConsoleExporter } from "./console.js";
|
|
2
|
+
import { createJSONLExporter } from "./jsonl.js";
|
|
3
|
+
import { createOTLPHttpExporter } from "./otlp-http.js";
|
|
4
|
+
import { VALIDATED_EXPORTER_BRAND } from "./types.js";
|
|
5
|
+
import { isValidatedExporter, tryGetValidatorCounters, withValidation } from "./with-validation.js";
|
|
6
|
+
|
|
7
|
+
export { VALIDATED_EXPORTER_BRAND, createConsoleExporter, createJSONLExporter, createOTLPHttpExporter, isValidatedExporter, tryGetValidatorCounters, withValidation };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { SpanRecord, TraceExporter } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/exporters/jsonl.d.ts
|
|
4
|
+
|
|
5
|
+
/** @internal — used by tests to override the date provider. */
|
|
6
|
+
type DateProvider = () => Date;
|
|
7
|
+
/**
|
|
8
|
+
* Configuration shape for {@link createJSONLExporter}.
|
|
9
|
+
*
|
|
10
|
+
* @stable
|
|
11
|
+
*/
|
|
12
|
+
interface JSONLExporterOptions {
|
|
13
|
+
/** Identifier reported via `exporter.id`. Defaults to `'jsonl'`. */
|
|
14
|
+
readonly id?: string;
|
|
15
|
+
/** Root directory for the trace files. Created if missing. */
|
|
16
|
+
readonly path: string;
|
|
17
|
+
/**
|
|
18
|
+
* Resolver that picks the file path for a given span record. Defaults
|
|
19
|
+
* to `<rootPath>/<YYYY-MM>/<sessionId>.jsonl` keyed off the
|
|
20
|
+
* `graphorin.session.id` attribute (or `unsessioned.jsonl` when
|
|
21
|
+
* absent).
|
|
22
|
+
*/
|
|
23
|
+
readonly resolveFilePath?: (record: SpanRecord, root: string) => string;
|
|
24
|
+
/**
|
|
25
|
+
* Override the date used when picking the rotation directory. Defaults
|
|
26
|
+
* to `() => new Date()`.
|
|
27
|
+
*
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
readonly now?: DateProvider;
|
|
31
|
+
/**
|
|
32
|
+
* Upper bound on simultaneously-open file handles (RP-20). The pool keys
|
|
33
|
+
* by `(session, UTC-month)`, so a long-living server would otherwise hold
|
|
34
|
+
* one fd per pair forever; the least-recently-used handle is closed once
|
|
35
|
+
* the cap is reached. Defaults to `64`.
|
|
36
|
+
*/
|
|
37
|
+
readonly maxOpenHandles?: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A JSONL exporter, plus an introspection seam for the bounded handle pool.
|
|
41
|
+
*
|
|
42
|
+
* @stable
|
|
43
|
+
*/
|
|
44
|
+
interface JSONLExporter extends TraceExporter {
|
|
45
|
+
/** Number of file handles currently open in the pool (RP-20). */
|
|
46
|
+
openHandleCount(): number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Build a JSONL trace exporter. Call `withValidation(exporter)` before
|
|
50
|
+
* passing the result to `createTracer({ exporters })`.
|
|
51
|
+
*
|
|
52
|
+
* @stable
|
|
53
|
+
*/
|
|
54
|
+
declare function createJSONLExporter(opts: JSONLExporterOptions): JSONLExporter;
|
|
55
|
+
//#endregion
|
|
56
|
+
export { JSONLExporterOptions, createJSONLExporter };
|
|
57
|
+
//# sourceMappingURL=jsonl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonl.d.ts","names":[],"sources":["../../src/exporters/jsonl.ts"],"sourcesContent":[],"mappings":";;;;;KAuBY,YAAA,SAAqB;;;;;;UAOhB,oBAAA;;;;;;;;;;;sCAWqB;;;;;;;iBAOrB;;;;;;;;;;;;;;UAeA,aAAA,SAAsB;;;;;;;;;;iBAWvB,mBAAA,OAA0B,uBAAuB"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { serializableRecord } from "./console.js";
|
|
2
|
+
import { mkdir, open } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
|
|
5
|
+
//#region src/exporters/jsonl.ts
|
|
6
|
+
/**
|
|
7
|
+
* `JSONLExporter` — append-only newline-delimited JSON output. Used as
|
|
8
|
+
* the default trace replay log; encryption-at-rest is opt-in via the
|
|
9
|
+
* encryption hook (Phase 16).
|
|
10
|
+
*
|
|
11
|
+
* The exporter:
|
|
12
|
+
* - creates the target directory with mode `0700`,
|
|
13
|
+
* - opens the per-session file with mode `0600`,
|
|
14
|
+
* - rotates per UTC month + per session id (`YYYY-MM/<sessionId>.jsonl`),
|
|
15
|
+
* - flushes every write (no internal buffer) so the file is suitable
|
|
16
|
+
* for tail-style replay even after a crash.
|
|
17
|
+
*
|
|
18
|
+
* The output is deterministic — see {@link serializableRecord}.
|
|
19
|
+
*
|
|
20
|
+
* @packageDocumentation
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Build a JSONL trace exporter. Call `withValidation(exporter)` before
|
|
24
|
+
* passing the result to `createTracer({ exporters })`.
|
|
25
|
+
*
|
|
26
|
+
* @stable
|
|
27
|
+
*/
|
|
28
|
+
function createJSONLExporter(opts) {
|
|
29
|
+
const id = opts.id ?? "jsonl";
|
|
30
|
+
const root = opts.path;
|
|
31
|
+
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
32
|
+
const resolveFilePath = opts.resolveFilePath ?? defaultPathResolver(now);
|
|
33
|
+
const maxOpenHandles = Math.max(1, opts.maxOpenHandles ?? 64);
|
|
34
|
+
const handles = /* @__PURE__ */ new Map();
|
|
35
|
+
let closed = false;
|
|
36
|
+
return {
|
|
37
|
+
id,
|
|
38
|
+
async export(record) {
|
|
39
|
+
if (closed) return;
|
|
40
|
+
const entry = await openOrReuse(handles, resolveFilePath(record, root), maxOpenHandles);
|
|
41
|
+
const line = `${JSON.stringify(serializableRecord(record))}\n`;
|
|
42
|
+
await entry.handle.write(line, null, "utf8");
|
|
43
|
+
},
|
|
44
|
+
async flush() {
|
|
45
|
+
for (const entry of handles.values()) await entry.handle.datasync();
|
|
46
|
+
},
|
|
47
|
+
async shutdown() {
|
|
48
|
+
closed = true;
|
|
49
|
+
for (const entry of handles.values()) await entry.handle.close();
|
|
50
|
+
handles.clear();
|
|
51
|
+
},
|
|
52
|
+
openHandleCount() {
|
|
53
|
+
return handles.size;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async function openOrReuse(pool, filePath, maxOpenHandles) {
|
|
58
|
+
const cached = pool.get(filePath);
|
|
59
|
+
if (cached !== void 0) {
|
|
60
|
+
pool.delete(filePath);
|
|
61
|
+
pool.set(filePath, cached);
|
|
62
|
+
return cached;
|
|
63
|
+
}
|
|
64
|
+
while (pool.size >= maxOpenHandles) {
|
|
65
|
+
const lruKey = pool.keys().next().value;
|
|
66
|
+
if (lruKey === void 0) break;
|
|
67
|
+
const lru = pool.get(lruKey);
|
|
68
|
+
pool.delete(lruKey);
|
|
69
|
+
if (lru !== void 0) try {
|
|
70
|
+
await lru.handle.close();
|
|
71
|
+
} catch {}
|
|
72
|
+
}
|
|
73
|
+
await mkdir(dirname(filePath), {
|
|
74
|
+
recursive: true,
|
|
75
|
+
mode: 448
|
|
76
|
+
});
|
|
77
|
+
const entry = { handle: await open(filePath, "a", 384) };
|
|
78
|
+
pool.set(filePath, entry);
|
|
79
|
+
return entry;
|
|
80
|
+
}
|
|
81
|
+
function defaultPathResolver(now) {
|
|
82
|
+
return (record, root) => {
|
|
83
|
+
const sessionAttr = record.attributes["graphorin.session.id"];
|
|
84
|
+
const sessionId = typeof sessionAttr === "string" ? sessionAttr : "unsessioned";
|
|
85
|
+
const date = now();
|
|
86
|
+
const yyyy = String(date.getUTCFullYear());
|
|
87
|
+
const mm = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
88
|
+
const safeSession = sessionId.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
89
|
+
return join(root, `${yyyy}-${mm}`, `${safeSession}.jsonl`);
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
94
|
+
export { createJSONLExporter };
|
|
95
|
+
//# sourceMappingURL=jsonl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonl.js","names":["entry: FileHandleEntry"],"sources":["../../src/exporters/jsonl.ts"],"sourcesContent":["/**\n * `JSONLExporter` — append-only newline-delimited JSON output. Used as\n * the default trace replay log; encryption-at-rest is opt-in via the\n * encryption hook (Phase 16).\n *\n * The exporter:\n * - creates the target directory with mode `0700`,\n * - opens the per-session file with mode `0600`,\n * - rotates per UTC month + per session id (`YYYY-MM/<sessionId>.jsonl`),\n * - flushes every write (no internal buffer) so the file is suitable\n * for tail-style replay even after a crash.\n *\n * The output is deterministic — see {@link serializableRecord}.\n *\n * @packageDocumentation\n */\n\nimport { mkdir, open } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { serializableRecord } from './console.js';\nimport type { SpanRecord, TraceExporter } from './types.js';\n\n/** @internal — used by tests to override the date provider. */\nexport type DateProvider = () => Date;\n\n/**\n * Configuration shape for {@link createJSONLExporter}.\n *\n * @stable\n */\nexport interface JSONLExporterOptions {\n /** Identifier reported via `exporter.id`. Defaults to `'jsonl'`. */\n readonly id?: string;\n /** Root directory for the trace files. Created if missing. */\n readonly path: string;\n /**\n * Resolver that picks the file path for a given span record. Defaults\n * to `<rootPath>/<YYYY-MM>/<sessionId>.jsonl` keyed off the\n * `graphorin.session.id` attribute (or `unsessioned.jsonl` when\n * absent).\n */\n readonly resolveFilePath?: (record: SpanRecord, root: string) => string;\n /**\n * Override the date used when picking the rotation directory. Defaults\n * to `() => new Date()`.\n *\n * @internal\n */\n readonly now?: DateProvider;\n /**\n * Upper bound on simultaneously-open file handles (RP-20). The pool keys\n * by `(session, UTC-month)`, so a long-living server would otherwise hold\n * one fd per pair forever; the least-recently-used handle is closed once\n * the cap is reached. Defaults to `64`.\n */\n readonly maxOpenHandles?: number;\n}\n\n/**\n * A JSONL exporter, plus an introspection seam for the bounded handle pool.\n *\n * @stable\n */\nexport interface JSONLExporter extends TraceExporter {\n /** Number of file handles currently open in the pool (RP-20). */\n openHandleCount(): number;\n}\n\n/**\n * Build a JSONL trace exporter. Call `withValidation(exporter)` before\n * passing the result to `createTracer({ exporters })`.\n *\n * @stable\n */\nexport function createJSONLExporter(opts: JSONLExporterOptions): JSONLExporter {\n const id = opts.id ?? 'jsonl';\n const root = opts.path;\n const now = opts.now ?? (() => new Date());\n const resolveFilePath = opts.resolveFilePath ?? defaultPathResolver(now);\n const maxOpenHandles = Math.max(1, opts.maxOpenHandles ?? 64);\n\n // Pool of file handles, keyed by absolute path. Each handle is opened\n // in append mode the first time we touch the path; subsequent exports\n // re-use the existing handle. The pool is LRU-bounded (RP-20) — the Map's\n // insertion order is the recency order, so the first entry is the LRU.\n const handles = new Map<string, FileHandleEntry>();\n let closed = false;\n\n return {\n id,\n async export(record: SpanRecord): Promise<void> {\n if (closed) return;\n const filePath = resolveFilePath(record, root);\n const entry = await openOrReuse(handles, filePath, maxOpenHandles);\n const line = `${JSON.stringify(serializableRecord(record))}\\n`;\n await entry.handle.write(line, null, 'utf8');\n },\n async flush(): Promise<void> {\n for (const entry of handles.values()) {\n await entry.handle.datasync();\n }\n },\n async shutdown(): Promise<void> {\n closed = true;\n for (const entry of handles.values()) {\n await entry.handle.close();\n }\n handles.clear();\n },\n openHandleCount(): number {\n return handles.size;\n },\n };\n}\n\ninterface FileHandleEntry {\n readonly handle: import('node:fs/promises').FileHandle;\n}\n\nasync function openOrReuse(\n pool: Map<string, FileHandleEntry>,\n filePath: string,\n maxOpenHandles: number,\n): Promise<FileHandleEntry> {\n const cached = pool.get(filePath);\n if (cached !== undefined) {\n // Mark most-recently-used by re-inserting at the tail of the Map.\n pool.delete(filePath);\n pool.set(filePath, cached);\n return cached;\n }\n\n // RP-20: evict least-recently-used handles before exceeding the cap so the\n // open-fd count stays bounded. An evicted path simply re-opens (append mode)\n // on its next export — no data is lost.\n while (pool.size >= maxOpenHandles) {\n const lruKey = pool.keys().next().value;\n if (lruKey === undefined) break;\n const lru = pool.get(lruKey);\n pool.delete(lruKey);\n if (lru !== undefined) {\n try {\n await lru.handle.close();\n } catch {\n // Best-effort — a failed close must not block the new open.\n }\n }\n }\n\n await mkdir(dirname(filePath), { recursive: true, mode: 0o700 });\n const handle = await open(filePath, 'a', 0o600);\n const entry: FileHandleEntry = { handle };\n pool.set(filePath, entry);\n return entry;\n}\n\nfunction defaultPathResolver(now: DateProvider) {\n return (record: SpanRecord, root: string): string => {\n const sessionAttr = record.attributes['graphorin.session.id'];\n const sessionId = typeof sessionAttr === 'string' ? sessionAttr : 'unsessioned';\n const date = now();\n const yyyy = String(date.getUTCFullYear());\n const mm = String(date.getUTCMonth() + 1).padStart(2, '0');\n const safeSession = sessionId.replace(/[^A-Za-z0-9._-]/g, '_');\n return join(root, `${yyyy}-${mm}`, `${safeSession}.jsonl`);\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA0EA,SAAgB,oBAAoB,MAA2C;CAC7E,MAAM,KAAK,KAAK,MAAM;CACtB,MAAM,OAAO,KAAK;CAClB,MAAM,MAAM,KAAK,8BAAc,IAAI,MAAM;CACzC,MAAM,kBAAkB,KAAK,mBAAmB,oBAAoB,IAAI;CACxE,MAAM,iBAAiB,KAAK,IAAI,GAAG,KAAK,kBAAkB,GAAG;CAM7D,MAAM,0BAAU,IAAI,KAA8B;CAClD,IAAI,SAAS;AAEb,QAAO;EACL;EACA,MAAM,OAAO,QAAmC;AAC9C,OAAI,OAAQ;GAEZ,MAAM,QAAQ,MAAM,YAAY,SADf,gBAAgB,QAAQ,KAAK,EACK,eAAe;GAClE,MAAM,OAAO,GAAG,KAAK,UAAU,mBAAmB,OAAO,CAAC,CAAC;AAC3D,SAAM,MAAM,OAAO,MAAM,MAAM,MAAM,OAAO;;EAE9C,MAAM,QAAuB;AAC3B,QAAK,MAAM,SAAS,QAAQ,QAAQ,CAClC,OAAM,MAAM,OAAO,UAAU;;EAGjC,MAAM,WAA0B;AAC9B,YAAS;AACT,QAAK,MAAM,SAAS,QAAQ,QAAQ,CAClC,OAAM,MAAM,OAAO,OAAO;AAE5B,WAAQ,OAAO;;EAEjB,kBAA0B;AACxB,UAAO,QAAQ;;EAElB;;AAOH,eAAe,YACb,MACA,UACA,gBAC0B;CAC1B,MAAM,SAAS,KAAK,IAAI,SAAS;AACjC,KAAI,WAAW,QAAW;AAExB,OAAK,OAAO,SAAS;AACrB,OAAK,IAAI,UAAU,OAAO;AAC1B,SAAO;;AAMT,QAAO,KAAK,QAAQ,gBAAgB;EAClC,MAAM,SAAS,KAAK,MAAM,CAAC,MAAM,CAAC;AAClC,MAAI,WAAW,OAAW;EAC1B,MAAM,MAAM,KAAK,IAAI,OAAO;AAC5B,OAAK,OAAO,OAAO;AACnB,MAAI,QAAQ,OACV,KAAI;AACF,SAAM,IAAI,OAAO,OAAO;UAClB;;AAMZ,OAAM,MAAM,QAAQ,SAAS,EAAE;EAAE,WAAW;EAAM,MAAM;EAAO,CAAC;CAEhE,MAAMA,QAAyB,EAAE,QADlB,MAAM,KAAK,UAAU,KAAK,IAAM,EACN;AACzC,MAAK,IAAI,UAAU,MAAM;AACzB,QAAO;;AAGT,SAAS,oBAAoB,KAAmB;AAC9C,SAAQ,QAAoB,SAAyB;EACnD,MAAM,cAAc,OAAO,WAAW;EACtC,MAAM,YAAY,OAAO,gBAAgB,WAAW,cAAc;EAClE,MAAM,OAAO,KAAK;EAClB,MAAM,OAAO,OAAO,KAAK,gBAAgB,CAAC;EAC1C,MAAM,KAAK,OAAO,KAAK,aAAa,GAAG,EAAE,CAAC,SAAS,GAAG,IAAI;EAC1D,MAAM,cAAc,UAAU,QAAQ,oBAAoB,IAAI;AAC9D,SAAO,KAAK,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,YAAY,QAAQ"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { TraceExporter } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/exporters/otlp-http.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configuration shape for {@link createOTLPHttpExporter}.
|
|
7
|
+
*
|
|
8
|
+
* @stable
|
|
9
|
+
*/
|
|
10
|
+
interface OTLPHttpExporterOptions {
|
|
11
|
+
/** Identifier reported via `exporter.id`. Defaults to `'otlp-http'`. */
|
|
12
|
+
readonly id?: string;
|
|
13
|
+
/** OTLP collector URL (e.g. `http://localhost:4318/v1/traces`). */
|
|
14
|
+
readonly url: string;
|
|
15
|
+
/** Optional headers (auth, tenant id, …). Avoid passing secrets in plain. */
|
|
16
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
17
|
+
/** Service name embedded in the OTLP `Resource`. */
|
|
18
|
+
readonly serviceName?: string;
|
|
19
|
+
/** Optional `fetch` override — useful for testing without the network. */
|
|
20
|
+
readonly fetchImpl?: typeof fetch;
|
|
21
|
+
/** Optional timeout (ms). Defaults to 10000. */
|
|
22
|
+
readonly timeoutMs?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Build a minimal OTLP-over-HTTP trace exporter. Call
|
|
26
|
+
* `withValidation(exporter)` before passing the result to
|
|
27
|
+
* `createTracer({ exporters })`.
|
|
28
|
+
*
|
|
29
|
+
* The implementation issues one `POST` per finished span — no
|
|
30
|
+
* batching. Operators wanting batching should wrap this exporter
|
|
31
|
+
* with their own queue or use a sidecar collector.
|
|
32
|
+
*
|
|
33
|
+
* @stable
|
|
34
|
+
*/
|
|
35
|
+
declare function createOTLPHttpExporter(opts: OTLPHttpExporterOptions): TraceExporter;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { OTLPHttpExporterOptions, createOTLPHttpExporter };
|
|
38
|
+
//# sourceMappingURL=otlp-http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otlp-http.d.ts","names":[],"sources":["../../src/exporters/otlp-http.ts"],"sourcesContent":[],"mappings":";;;;;;;;;UA4BiB,uBAAA;;;;;;qBAMI,SAAS;;;;8BAIA;;;;;;;;;;;;;;;iBAgBd,sBAAA,OAA6B,0BAA0B"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
//#region src/exporters/otlp-http.ts
|
|
2
|
+
/**
|
|
3
|
+
* Build a minimal OTLP-over-HTTP trace exporter. Call
|
|
4
|
+
* `withValidation(exporter)` before passing the result to
|
|
5
|
+
* `createTracer({ exporters })`.
|
|
6
|
+
*
|
|
7
|
+
* The implementation issues one `POST` per finished span — no
|
|
8
|
+
* batching. Operators wanting batching should wrap this exporter
|
|
9
|
+
* with their own queue or use a sidecar collector.
|
|
10
|
+
*
|
|
11
|
+
* @stable
|
|
12
|
+
*/
|
|
13
|
+
function createOTLPHttpExporter(opts) {
|
|
14
|
+
const id = opts.id ?? "otlp-http";
|
|
15
|
+
const url = opts.url;
|
|
16
|
+
const headers = opts.headers ?? {};
|
|
17
|
+
const serviceName = opts.serviceName ?? "graphorin";
|
|
18
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
19
|
+
const timeoutMs = opts.timeoutMs ?? 1e4;
|
|
20
|
+
let closed = false;
|
|
21
|
+
if (typeof fetchImpl !== "function") throw new TypeError("createOTLPHttpExporter: no fetch implementation available. Provide `fetchImpl` explicitly or run on a Node.js version with global fetch (>= 18).");
|
|
22
|
+
return {
|
|
23
|
+
id,
|
|
24
|
+
async export(record) {
|
|
25
|
+
if (closed) return;
|
|
26
|
+
const body = toOtlpEnvelope(record, serviceName);
|
|
27
|
+
const controller = new AbortController();
|
|
28
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
29
|
+
try {
|
|
30
|
+
const res = await fetchImpl(url, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: {
|
|
33
|
+
"content-type": "application/json",
|
|
34
|
+
...headers
|
|
35
|
+
},
|
|
36
|
+
body: JSON.stringify(body),
|
|
37
|
+
signal: controller.signal
|
|
38
|
+
});
|
|
39
|
+
if (!res.ok) throw new Error(`OTLP exporter received HTTP ${res.status} ${res.statusText}`);
|
|
40
|
+
} finally {
|
|
41
|
+
clearTimeout(timer);
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
async flush() {},
|
|
45
|
+
async shutdown() {
|
|
46
|
+
closed = true;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* @internal — exposed for unit tests
|
|
52
|
+
*/
|
|
53
|
+
function toOtlpEnvelope(record, serviceName) {
|
|
54
|
+
const otelStatus = record.status === "ok" ? 1 : record.status === "error" ? 2 : 0;
|
|
55
|
+
return { resourceSpans: [{
|
|
56
|
+
resource: { attributes: [{
|
|
57
|
+
key: "service.name",
|
|
58
|
+
value: { stringValue: serviceName }
|
|
59
|
+
}, {
|
|
60
|
+
key: "graphorin.framework.version",
|
|
61
|
+
value: { stringValue: "0.5.0" }
|
|
62
|
+
}] },
|
|
63
|
+
scopeSpans: [{
|
|
64
|
+
scope: {
|
|
65
|
+
name: "@graphorin/observability",
|
|
66
|
+
version: "0.5.0"
|
|
67
|
+
},
|
|
68
|
+
spans: [{
|
|
69
|
+
traceId: record.traceId,
|
|
70
|
+
spanId: record.id,
|
|
71
|
+
...record.parentId === void 0 ? {} : { parentSpanId: record.parentId },
|
|
72
|
+
name: record.name,
|
|
73
|
+
kind: spanKindFor(record.type),
|
|
74
|
+
startTimeUnixNano: String(record.startUnixNano),
|
|
75
|
+
endTimeUnixNano: String(record.endUnixNano),
|
|
76
|
+
attributes: toOtlpAttributes(record.attributes),
|
|
77
|
+
events: record.events.map(toOtlpEvent),
|
|
78
|
+
status: {
|
|
79
|
+
code: otelStatus,
|
|
80
|
+
...record.statusMessage === void 0 ? {} : { message: record.statusMessage }
|
|
81
|
+
}
|
|
82
|
+
}]
|
|
83
|
+
}]
|
|
84
|
+
}] };
|
|
85
|
+
}
|
|
86
|
+
function spanKindFor(type) {
|
|
87
|
+
if (type === "provider.generate" || type === "provider.stream" || type === "mcp.call") return 3;
|
|
88
|
+
if (type === "tool.execute" || type === "memory.embed") return 1;
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
function toOtlpAttributes(attrs) {
|
|
92
|
+
return Object.entries(attrs).map(([k, v]) => ({
|
|
93
|
+
key: k,
|
|
94
|
+
value: toOtlpAttributeValue(v)
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
function toOtlpEvent(event) {
|
|
98
|
+
return {
|
|
99
|
+
timeUnixNano: String(event.timeUnixNano),
|
|
100
|
+
name: event.name,
|
|
101
|
+
attributes: toOtlpAttributes(event.attributes)
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function toOtlpAttributeValue(v) {
|
|
105
|
+
if (typeof v === "string") return { stringValue: v };
|
|
106
|
+
if (typeof v === "number") return Number.isInteger(v) ? { intValue: String(v) } : { doubleValue: v };
|
|
107
|
+
if (typeof v === "boolean") return { boolValue: v };
|
|
108
|
+
if (v === null) return { stringValue: "" };
|
|
109
|
+
if (Array.isArray(v)) return { arrayValue: { values: v.map((item) => toOtlpAttributeValue(item)) } };
|
|
110
|
+
return { stringValue: String(v) };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
//#endregion
|
|
114
|
+
export { createOTLPHttpExporter };
|
|
115
|
+
//# sourceMappingURL=otlp-http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otlp-http.js","names":[],"sources":["../../src/exporters/otlp-http.ts"],"sourcesContent":["/**\n * `OTLPHttpExporter` — reference implementation that POSTs span\n * records to an OpenTelemetry HTTP endpoint.\n *\n * The exporter is deliberately minimal: production hardening (retry\n * logic, batching policy tuning, gzip compression, bearer-token auth\n * rotation) is left to operators. The wire format follows the OTLP\n * `traces` resource shape and is compatible with collectors that accept\n * the `application/json` content type.\n *\n * Operators wanting the upstream OpenTelemetry SDK exporter\n * (`@opentelemetry/exporter-trace-otlp-http`) can adapt\n * {@link toOtlpEnvelope} into their own wrapper — the helper is\n * exported so the OTel adapter does not need to duplicate the wire\n * shape.\n *\n * @packageDocumentation\n */\n\nimport type { SpanAttributes, SpanAttributeValue, SpanType } from '@graphorin/core';\n\nimport type { SpanRecord, SpanRecordEvent, TraceExporter } from './types.js';\n\n/**\n * Configuration shape for {@link createOTLPHttpExporter}.\n *\n * @stable\n */\nexport interface OTLPHttpExporterOptions {\n /** Identifier reported via `exporter.id`. Defaults to `'otlp-http'`. */\n readonly id?: string;\n /** OTLP collector URL (e.g. `http://localhost:4318/v1/traces`). */\n readonly url: string;\n /** Optional headers (auth, tenant id, …). Avoid passing secrets in plain. */\n readonly headers?: Readonly<Record<string, string>>;\n /** Service name embedded in the OTLP `Resource`. */\n readonly serviceName?: string;\n /** Optional `fetch` override — useful for testing without the network. */\n readonly fetchImpl?: typeof fetch;\n /** Optional timeout (ms). Defaults to 10000. */\n readonly timeoutMs?: number;\n}\n\n/**\n * Build a minimal OTLP-over-HTTP trace exporter. Call\n * `withValidation(exporter)` before passing the result to\n * `createTracer({ exporters })`.\n *\n * The implementation issues one `POST` per finished span — no\n * batching. Operators wanting batching should wrap this exporter\n * with their own queue or use a sidecar collector.\n *\n * @stable\n */\nexport function createOTLPHttpExporter(opts: OTLPHttpExporterOptions): TraceExporter {\n const id = opts.id ?? 'otlp-http';\n const url = opts.url;\n const headers = opts.headers ?? {};\n const serviceName = opts.serviceName ?? 'graphorin';\n const fetchImpl = opts.fetchImpl ?? globalThis.fetch;\n const timeoutMs = opts.timeoutMs ?? 10_000;\n let closed = false;\n\n if (typeof fetchImpl !== 'function') {\n throw new TypeError(\n 'createOTLPHttpExporter: no fetch implementation available. Provide ' +\n '`fetchImpl` explicitly or run on a Node.js version with global fetch ' +\n '(>= 18).',\n );\n }\n\n return {\n id,\n async export(record: SpanRecord): Promise<void> {\n if (closed) return;\n const body = toOtlpEnvelope(record, serviceName);\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = await fetchImpl(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...headers },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n if (!res.ok) {\n throw new Error(`OTLP exporter received HTTP ${res.status} ${res.statusText}`);\n }\n } finally {\n clearTimeout(timer);\n }\n },\n async flush(): Promise<void> {\n // No internal queue; each export() awaits the POST already.\n },\n async shutdown(): Promise<void> {\n closed = true;\n },\n };\n}\n\n/**\n * @internal — exposed for unit tests\n */\nexport function toOtlpEnvelope(record: SpanRecord, serviceName: string): unknown {\n const otelStatus = record.status === 'ok' ? 1 : record.status === 'error' ? 2 : 0;\n return {\n resourceSpans: [\n {\n resource: {\n attributes: [\n { key: 'service.name', value: { stringValue: serviceName } },\n { key: 'graphorin.framework.version', value: { stringValue: '0.5.0' } },\n ],\n },\n scopeSpans: [\n {\n scope: { name: '@graphorin/observability', version: '0.5.0' },\n spans: [\n {\n traceId: record.traceId,\n spanId: record.id,\n ...(record.parentId === undefined ? {} : { parentSpanId: record.parentId }),\n name: record.name,\n kind: spanKindFor(record.type),\n startTimeUnixNano: String(record.startUnixNano),\n endTimeUnixNano: String(record.endUnixNano),\n attributes: toOtlpAttributes(record.attributes),\n events: record.events.map(toOtlpEvent),\n status: {\n code: otelStatus,\n ...(record.statusMessage === undefined ? {} : { message: record.statusMessage }),\n },\n },\n ],\n },\n ],\n },\n ],\n };\n}\n\nfunction spanKindFor(type: SpanType): number {\n // OTel SpanKind enum: 0 unspecified, 1 internal, 2 server, 3 client, 4 producer, 5 consumer.\n if (type === 'provider.generate' || type === 'provider.stream' || type === 'mcp.call') return 3;\n if (type === 'tool.execute' || type === 'memory.embed') return 1;\n return 1;\n}\n\nfunction toOtlpAttributes(attrs: SpanAttributes): unknown[] {\n return Object.entries(attrs).map(([k, v]) => ({ key: k, value: toOtlpAttributeValue(v) }));\n}\n\nfunction toOtlpEvent(event: SpanRecordEvent): unknown {\n return {\n timeUnixNano: String(event.timeUnixNano),\n name: event.name,\n attributes: toOtlpAttributes(event.attributes),\n };\n}\n\nfunction toOtlpAttributeValue(v: SpanAttributeValue): unknown {\n if (typeof v === 'string') return { stringValue: v };\n if (typeof v === 'number') {\n return Number.isInteger(v) ? { intValue: String(v) } : { doubleValue: v };\n }\n if (typeof v === 'boolean') return { boolValue: v };\n if (v === null) return { stringValue: '' };\n if (Array.isArray(v)) {\n return {\n arrayValue: {\n values: v.map((item) => toOtlpAttributeValue(item as SpanAttributeValue)),\n },\n };\n }\n return { stringValue: String(v) };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { SpanAttributeValue, SpanAttributes, SpanStatus, SpanType } from "@graphorin/core";
|
|
2
|
+
|
|
3
|
+
//#region src/exporters/types.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Discriminator marker — every exporter that has been wrapped via
|
|
7
|
+
* `withValidation(...)` is branded with this symbol so the tracer can
|
|
8
|
+
* fail-fast at startup when a raw exporter is registered.
|
|
9
|
+
*
|
|
10
|
+
* @stable
|
|
11
|
+
*/
|
|
12
|
+
declare const VALIDATED_EXPORTER_BRAND: unique symbol;
|
|
13
|
+
/**
|
|
14
|
+
* Sanitized, JSON-serialisable representation of a finished span. The
|
|
15
|
+
* exporters never see the live OTel span; the tracer materialises this
|
|
16
|
+
* record once the span ends and runs it through the validator.
|
|
17
|
+
*
|
|
18
|
+
* @stable
|
|
19
|
+
*/
|
|
20
|
+
interface SpanRecord<T extends SpanType = SpanType> {
|
|
21
|
+
readonly type: T;
|
|
22
|
+
readonly id: string;
|
|
23
|
+
readonly traceId: string;
|
|
24
|
+
readonly parentId?: string;
|
|
25
|
+
readonly name: string;
|
|
26
|
+
readonly startUnixNano: number;
|
|
27
|
+
readonly endUnixNano: number;
|
|
28
|
+
readonly status: SpanStatus;
|
|
29
|
+
readonly statusMessage?: string;
|
|
30
|
+
readonly attributes: SpanAttributes;
|
|
31
|
+
readonly events: ReadonlyArray<SpanRecordEvent>;
|
|
32
|
+
/** Optional per-attribute sensitivity map — see `setAttribute({ sensitivity })`. */
|
|
33
|
+
readonly sensitivityByAttribute?: Readonly<Record<string, SpanAttributeValue>>;
|
|
34
|
+
/** Set when the validator dropped the span entirely (replay marker). */
|
|
35
|
+
readonly droppedReason?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Single span event carried alongside the span record.
|
|
39
|
+
*
|
|
40
|
+
* @stable
|
|
41
|
+
*/
|
|
42
|
+
interface SpanRecordEvent {
|
|
43
|
+
readonly name: string;
|
|
44
|
+
readonly timeUnixNano: number;
|
|
45
|
+
readonly attributes: SpanAttributes;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A trace exporter contract. Exporters consume a stream of finished
|
|
49
|
+
* spans and forward them to a sink (console, file, OTLP wire, …).
|
|
50
|
+
*
|
|
51
|
+
* @stable
|
|
52
|
+
*/
|
|
53
|
+
interface TraceExporter {
|
|
54
|
+
readonly id: string;
|
|
55
|
+
/** Forward a finished span record. Implementations should be cheap. */
|
|
56
|
+
export(record: SpanRecord): Promise<void>;
|
|
57
|
+
/** Flush any buffered spans. Called on `tracer.shutdown()`. */
|
|
58
|
+
flush(): Promise<void>;
|
|
59
|
+
/** Close any underlying resources. Idempotent. */
|
|
60
|
+
shutdown(): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Branded-marker stub. Set by `withValidation(...)` to signal that the
|
|
63
|
+
* exporter has been wrapped. Direct exporters omit the brand and the
|
|
64
|
+
* tracer fails fast at startup.
|
|
65
|
+
*
|
|
66
|
+
* @internal
|
|
67
|
+
*/
|
|
68
|
+
readonly [VALIDATED_EXPORTER_BRAND]?: true;
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { SpanRecord, SpanRecordEvent, TraceExporter, VALIDATED_EXPORTER_BRAND };
|
|
72
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/exporters/types.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;AAuC6C,cAxBhC,wBAwBgC,EAAA,OAAA,MAAA;;;AAU7C;AAYA;;;;AAOc,UA1CG,UA0CH,CAAA,UA1CwB,QA0CxB,GA1CmC,QA0CnC,CAAA,CAAA;EAQF,SAAA,IAAA,EAjDK,CAiDL;EAAwB,SAAA,EAAA,EAAA,MAAA;;;;;;mBA1CjB;;uBAEI;mBACJ,cAAc;;oCAEG,SAAS,eAAe;;;;;;;;;UAU3C,eAAA;;;uBAGM;;;;;;;;UASN,aAAA;;;iBAGA,aAAa;;WAEnB;;cAEG;;;;;;;;YAQF,wBAAA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/exporters/types.ts
|
|
2
|
+
/**
|
|
3
|
+
* Discriminator marker — every exporter that has been wrapped via
|
|
4
|
+
* `withValidation(...)` is branded with this symbol so the tracer can
|
|
5
|
+
* fail-fast at startup when a raw exporter is registered.
|
|
6
|
+
*
|
|
7
|
+
* @stable
|
|
8
|
+
*/
|
|
9
|
+
const VALIDATED_EXPORTER_BRAND = Symbol.for("graphorin.observability.exporter.validated");
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
export { VALIDATED_EXPORTER_BRAND };
|
|
13
|
+
//# sourceMappingURL=types.js.map
|