@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 @@
|
|
|
1
|
+
{"version":3,"file":"sampling.js","names":["decisionMaker: SamplingDecisionMaker","windowStart: number | undefined"],"sources":["../../src/tracer/sampling.ts"],"sourcesContent":["/**\n * Head + tail sampling helpers. The tracer pairs the configured\n * sampler with a per-type override registry so chatty span types\n * (`memory.embed`, `tool.execute.partial`, …) can be downsampled\n * without affecting the rest of the trace stream.\n *\n * @packageDocumentation\n */\n\nimport type { SpanType } from '@graphorin/core';\n\n/** @stable */\nexport type SamplingDecisionMaker = 'parent-based' | 'always-on' | 'rate-limit';\n\n/**\n * Per-span-type rate override.\n *\n * @stable\n */\nexport interface SamplingRule {\n readonly type: SpanType | string;\n readonly rate: number;\n}\n\n/**\n * Configuration shape consumed by {@link createSampler}.\n *\n * @stable\n */\nexport interface SamplingOptions {\n /** Default head-sampling rate. Must be in `[0, 1]`. Defaults to `1.0`. */\n readonly rate?: number;\n /** Per-type overrides. Last write wins on duplicate `type`. */\n readonly rules?: ReadonlyArray<SamplingRule>;\n /** Decision maker. Defaults to `'parent-based'`. */\n readonly decisionMaker?: SamplingDecisionMaker;\n /**\n * Cap for the `'rate-limit'` decision maker: at most this many root spans\n * are sampled per rolling 1-second window (RP-19). `undefined` ⇒ no cap\n * (samples everything); `0` ⇒ sample nothing. Ignored by the other\n * decision makers.\n */\n readonly maxPerSecond?: number;\n /**\n * Clock for the `'rate-limit'` window. Defaults to `Date.now`.\n *\n * @internal\n */\n readonly now?: () => number;\n /**\n * Optional override for streaming-event sampling.\n * @see RB-52 — streaming event family `tool.execute.{progress,partial}`.\n */\n readonly streaming?: {\n readonly eventSamplingRate?: number;\n readonly includeChunkContent?: 'none' | 'text-only' | 'all';\n };\n /**\n * Override for the random source. Useful for deterministic tests.\n *\n * @internal\n */\n readonly random?: () => number;\n}\n\n/**\n * @stable\n */\nexport interface Sampler {\n /** Decide whether a span of the given type should be recorded. */\n shouldSample(type: SpanType | string, parentSampled?: boolean): boolean;\n /** Decide whether a span event of the given name should be recorded. */\n shouldRecordEvent(name: string): boolean;\n /** Returns whether chunk *content* should travel through the exporter. */\n shouldIncludeChunkContent(): boolean;\n}\n\n/**\n * Build a {@link Sampler} from the supplied options. The sampler is\n * intentionally inexpensive — every decision boils down to a single\n * `random < threshold` comparison.\n *\n * @stable\n */\nexport function createSampler(opts: SamplingOptions = {}): Sampler {\n const baseRate = clampRate(opts.rate ?? 1.0);\n const decisionMaker: SamplingDecisionMaker = opts.decisionMaker ?? 'parent-based';\n const ruleByType = new Map<string, number>();\n for (const rule of opts.rules ?? []) {\n ruleByType.set(rule.type, clampRate(rule.rate));\n }\n const streamingRate = clampRate(opts.streaming?.eventSamplingRate ?? 1.0);\n const includeChunks = opts.streaming?.includeChunkContent ?? 'none';\n const random = opts.random ?? Math.random;\n const maxPerSecond = opts.maxPerSecond;\n const now = opts.now ?? Date.now;\n let windowStart: number | undefined;\n let windowCount = 0;\n\n return {\n shouldSample(type, parentSampled): boolean {\n if (decisionMaker === 'always-on') return true;\n if (decisionMaker === 'parent-based') {\n // RP-19: true parent-based — follow the parent's real sampling\n // decision. A child of an unsampled parent is NOT recorded (it would\n // otherwise be an orphan); a root span (no parent) falls through to\n // the rate.\n if (parentSampled === true) return true;\n if (parentSampled === false) return false;\n }\n if (decisionMaker === 'rate-limit') {\n // RP-19: a real token-window limiter, distinct from the probabilistic\n // path. Caps sampled spans to `maxPerSecond` per rolling second.\n if (maxPerSecond === undefined) return true;\n if (maxPerSecond <= 0) return false;\n const t = now();\n if (windowStart === undefined || t - windowStart >= 1000) {\n windowStart = t;\n windowCount = 0;\n }\n if (windowCount < maxPerSecond) {\n windowCount += 1;\n return true;\n }\n return false;\n }\n const rate = ruleByType.get(type) ?? baseRate;\n if (rate >= 1) return true;\n if (rate <= 0) return false;\n return random() < rate;\n },\n shouldRecordEvent(name): boolean {\n const rate = ruleByType.get(name) ?? streamingRate;\n if (rate >= 1) return true;\n if (rate <= 0) return false;\n return random() < rate;\n },\n shouldIncludeChunkContent(): boolean {\n return includeChunks !== 'none';\n },\n };\n}\n\nfunction clampRate(rate: number): number {\n if (Number.isNaN(rate)) return 1;\n if (rate < 0) return 0;\n if (rate > 1) return 1;\n return rate;\n}\n"],"mappings":";;;;;;;;AAoFA,SAAgB,cAAc,OAAwB,EAAE,EAAW;CACjE,MAAM,WAAW,UAAU,KAAK,QAAQ,EAAI;CAC5C,MAAMA,gBAAuC,KAAK,iBAAiB;CACnE,MAAM,6BAAa,IAAI,KAAqB;AAC5C,MAAK,MAAM,QAAQ,KAAK,SAAS,EAAE,CACjC,YAAW,IAAI,KAAK,MAAM,UAAU,KAAK,KAAK,CAAC;CAEjD,MAAM,gBAAgB,UAAU,KAAK,WAAW,qBAAqB,EAAI;CACzE,MAAM,gBAAgB,KAAK,WAAW,uBAAuB;CAC7D,MAAM,SAAS,KAAK,UAAU,KAAK;CACnC,MAAM,eAAe,KAAK;CAC1B,MAAM,MAAM,KAAK,OAAO,KAAK;CAC7B,IAAIC;CACJ,IAAI,cAAc;AAElB,QAAO;EACL,aAAa,MAAM,eAAwB;AACzC,OAAI,kBAAkB,YAAa,QAAO;AAC1C,OAAI,kBAAkB,gBAAgB;AAKpC,QAAI,kBAAkB,KAAM,QAAO;AACnC,QAAI,kBAAkB,MAAO,QAAO;;AAEtC,OAAI,kBAAkB,cAAc;AAGlC,QAAI,iBAAiB,OAAW,QAAO;AACvC,QAAI,gBAAgB,EAAG,QAAO;IAC9B,MAAM,IAAI,KAAK;AACf,QAAI,gBAAgB,UAAa,IAAI,eAAe,KAAM;AACxD,mBAAc;AACd,mBAAc;;AAEhB,QAAI,cAAc,cAAc;AAC9B,oBAAe;AACf,YAAO;;AAET,WAAO;;GAET,MAAM,OAAO,WAAW,IAAI,KAAK,IAAI;AACrC,OAAI,QAAQ,EAAG,QAAO;AACtB,OAAI,QAAQ,EAAG,QAAO;AACtB,UAAO,QAAQ,GAAG;;EAEpB,kBAAkB,MAAe;GAC/B,MAAM,OAAO,WAAW,IAAI,KAAK,IAAI;AACrC,OAAI,QAAQ,EAAG,QAAO;AACtB,OAAI,QAAQ,EAAG,QAAO;AACtB,UAAO,QAAQ,GAAG;;EAEpB,4BAAqC;AACnC,UAAO,kBAAkB;;EAE5B;;AAGH,SAAS,UAAU,MAAsB;AACvC,KAAI,OAAO,MAAM,KAAK,CAAE,QAAO;AAC/B,KAAI,OAAO,EAAG,QAAO;AACrB,KAAI,OAAO,EAAG,QAAO;AACrB,QAAO"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"span-names.d.ts","names":[],"sources":["../../src/tracer/span-names.ts"],"sourcesContent":[],"mappings":";;;;;;;iBAagB,WAAA,OAAkB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"span-names.js","names":[],"sources":["../../src/tracer/span-names.ts"],"sourcesContent":["/**\n * Default human-readable span name per {@link SpanType}. Operators\n * can override the name by passing `name` through a custom\n * `setAttributes(...)` call after `startSpan(...)` returns.\n *\n * @packageDocumentation\n */\n\nimport type { SpanType } from '@graphorin/core';\n\n/**\n * @stable\n */\nexport function spanNameFor(type: SpanType): string {\n return type;\n}\n"],"mappings":";;;;AAaA,SAAgB,YAAY,MAAwB;AAClD,QAAO"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AISpan, Sensitivity, SpanAttributeValue, SpanAttributes, SpanType } from "@graphorin/core";
|
|
2
|
+
|
|
3
|
+
//#region src/tracer/span.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Optional metadata accepted by `setAttribute(...)` to declare the
|
|
7
|
+
* sensitivity of a single attribute. The validator uses the declared
|
|
8
|
+
* tier when deciding whether to drop the attribute.
|
|
9
|
+
*
|
|
10
|
+
* @stable
|
|
11
|
+
*/
|
|
12
|
+
interface SetAttributeOptions {
|
|
13
|
+
readonly sensitivity?: Sensitivity;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The internal span carries the convenience `setAttribute(...)` method
|
|
17
|
+
* exposed by the tracer surface (per-attribute sensitivity tagging) on
|
|
18
|
+
* top of the standard {@link AISpan} contract.
|
|
19
|
+
*
|
|
20
|
+
* @stable
|
|
21
|
+
*/
|
|
22
|
+
interface GraphorinSpan<T extends SpanType = SpanType> extends AISpan<T> {
|
|
23
|
+
setAttribute(name: string, value: SpanAttributeValue, opts?: SetAttributeOptions): void;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { GraphorinSpan, SetAttributeOptions };
|
|
27
|
+
//# sourceMappingURL=span.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"span.d.ts","names":[],"sources":["../../src/tracer/span.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;UA0BiB,mBAAA;yBACQ;;;;;;;;;UAUR,wBAAwB,WAAW,kBAAkB,OAAO;oCACzC,2BAA2B"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
//#region src/tracer/span.ts
|
|
2
|
+
/**
|
|
3
|
+
* @internal
|
|
4
|
+
*/
|
|
5
|
+
function createSpan(input) {
|
|
6
|
+
const startUnixNano = Math.floor(input.now() * 1e6);
|
|
7
|
+
const attributes = { ...input.attrs ?? {} };
|
|
8
|
+
const sensitivities = { ...input.attrSensitivities ?? {} };
|
|
9
|
+
const events = [];
|
|
10
|
+
let status = "ok";
|
|
11
|
+
let statusMessage;
|
|
12
|
+
let ended = false;
|
|
13
|
+
return {
|
|
14
|
+
type: input.type,
|
|
15
|
+
id: input.id,
|
|
16
|
+
traceId: input.traceId,
|
|
17
|
+
...input.parentId === void 0 ? {} : { parentId: input.parentId },
|
|
18
|
+
setAttributes(attrs) {
|
|
19
|
+
if (ended) return;
|
|
20
|
+
for (const [k, v] of Object.entries(attrs)) attributes[k] = v;
|
|
21
|
+
},
|
|
22
|
+
setAttribute(name, value, opts) {
|
|
23
|
+
if (ended) return;
|
|
24
|
+
attributes[name] = value;
|
|
25
|
+
if (opts?.sensitivity !== void 0) sensitivities[name] = opts.sensitivity;
|
|
26
|
+
},
|
|
27
|
+
addEvent(name, attrs) {
|
|
28
|
+
if (ended) return;
|
|
29
|
+
if (!input.recordEvent(name)) return;
|
|
30
|
+
events.push({
|
|
31
|
+
name,
|
|
32
|
+
timeUnixNano: Math.floor(input.now() * 1e6),
|
|
33
|
+
attributes: Object.freeze({ ...attrs ?? {} })
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
recordException(err) {
|
|
37
|
+
if (ended) return;
|
|
38
|
+
const e = err;
|
|
39
|
+
const attrs = {
|
|
40
|
+
"exception.type": typeof e?.name === "string" && e.name.length > 0 ? e.name : "Error",
|
|
41
|
+
"exception.message": typeof e?.message === "string" ? e.message : String(err),
|
|
42
|
+
...typeof e?.stack === "string" ? { "exception.stacktrace": e.stack } : {}
|
|
43
|
+
};
|
|
44
|
+
events.push({
|
|
45
|
+
name: "exception",
|
|
46
|
+
timeUnixNano: Math.floor(input.now() * 1e6),
|
|
47
|
+
attributes: attrs
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
setStatus(s, message) {
|
|
51
|
+
if (ended) return;
|
|
52
|
+
status = s;
|
|
53
|
+
if (message !== void 0) statusMessage = message;
|
|
54
|
+
},
|
|
55
|
+
end() {
|
|
56
|
+
if (ended) return;
|
|
57
|
+
ended = true;
|
|
58
|
+
const endUnixNano = Math.floor(input.now() * 1e6);
|
|
59
|
+
const record = {
|
|
60
|
+
type: input.type,
|
|
61
|
+
id: input.id,
|
|
62
|
+
traceId: input.traceId,
|
|
63
|
+
...input.parentId === void 0 ? {} : { parentId: input.parentId },
|
|
64
|
+
name: input.name,
|
|
65
|
+
startUnixNano,
|
|
66
|
+
endUnixNano,
|
|
67
|
+
status,
|
|
68
|
+
...statusMessage === void 0 ? {} : { statusMessage },
|
|
69
|
+
attributes: Object.freeze({ ...attributes }),
|
|
70
|
+
events: Object.freeze([...events]),
|
|
71
|
+
...Object.keys(sensitivities).length === 0 ? {} : { sensitivityByAttribute: Object.freeze({ ...sensitivities }) }
|
|
72
|
+
};
|
|
73
|
+
input.sink(record);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//#endregion
|
|
79
|
+
export { createSpan };
|
|
80
|
+
//# sourceMappingURL=span.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"span.js","names":["attributes: Record<string, SpanAttributeValue>","sensitivities: Record<string, Sensitivity>","events: SpanRecord['events'][number][]","status: SpanStatus","statusMessage: string | undefined","attrs: SpanAttributes","record: SpanRecord<T>"],"sources":["../../src/tracer/span.ts"],"sourcesContent":["/**\n * Concrete `AISpan<T>` implementation. The span is intentionally\n * decoupled from `@opentelemetry/api`: the tracer materialises a\n * {@link SpanRecord} on `end()` and lets exporters do the rest.\n *\n * @packageDocumentation\n */\n\nimport type {\n AISpan,\n Sensitivity,\n SpanAttributes,\n SpanAttributeValue,\n SpanStatus,\n SpanType,\n} from '@graphorin/core';\n\nimport type { SpanRecord } from '../exporters/types.js';\n\n/**\n * Optional metadata accepted by `setAttribute(...)` to declare the\n * sensitivity of a single attribute. The validator uses the declared\n * tier when deciding whether to drop the attribute.\n *\n * @stable\n */\nexport interface SetAttributeOptions {\n readonly sensitivity?: Sensitivity;\n}\n\n/**\n * The internal span carries the convenience `setAttribute(...)` method\n * exposed by the tracer surface (per-attribute sensitivity tagging) on\n * top of the standard {@link AISpan} contract.\n *\n * @stable\n */\nexport interface GraphorinSpan<T extends SpanType = SpanType> extends AISpan<T> {\n setAttribute(name: string, value: SpanAttributeValue, opts?: SetAttributeOptions): void;\n}\n\n/**\n * @internal — sink invoked when a span ends.\n */\nexport type SpanSink = (record: SpanRecord) => void;\n\n/**\n * @internal — parameters passed by the tracer to {@link createSpan}.\n */\nexport interface SpanCreateInput<T extends SpanType = SpanType> {\n readonly type: T;\n readonly name: string;\n readonly traceId: string;\n readonly id: string;\n readonly parentId?: string;\n readonly attrs?: SpanAttributes;\n readonly attrSensitivities?: Readonly<Record<string, Sensitivity>>;\n readonly sink: SpanSink;\n readonly now: () => number;\n readonly recordEvent: (name: string) => boolean;\n}\n\n/**\n * @internal\n */\nexport function createSpan<T extends SpanType>(input: SpanCreateInput<T>): GraphorinSpan<T> {\n const startUnixNano = Math.floor(input.now() * 1_000_000);\n const attributes: Record<string, SpanAttributeValue> = { ...(input.attrs ?? {}) };\n const sensitivities: Record<string, Sensitivity> = { ...(input.attrSensitivities ?? {}) };\n const events: SpanRecord['events'][number][] = [];\n let status: SpanStatus = 'ok';\n let statusMessage: string | undefined;\n let ended = false;\n\n const span: GraphorinSpan<T> = {\n type: input.type,\n id: input.id,\n traceId: input.traceId,\n ...(input.parentId === undefined ? {} : { parentId: input.parentId }),\n setAttributes(attrs: SpanAttributes): void {\n if (ended) return;\n for (const [k, v] of Object.entries(attrs)) {\n attributes[k] = v;\n }\n },\n setAttribute(name: string, value: SpanAttributeValue, opts?: SetAttributeOptions): void {\n if (ended) return;\n attributes[name] = value;\n if (opts?.sensitivity !== undefined) {\n sensitivities[name] = opts.sensitivity;\n }\n },\n addEvent(name: string, attrs?: SpanAttributes): void {\n if (ended) return;\n if (!input.recordEvent(name)) return;\n events.push({\n name,\n timeUnixNano: Math.floor(input.now() * 1_000_000),\n attributes: Object.freeze({ ...(attrs ?? {}) }) as SpanAttributes,\n });\n },\n recordException(err: unknown): void {\n if (ended) return;\n const e = err as { name?: unknown; message?: unknown; stack?: unknown };\n const attrs: SpanAttributes = {\n 'exception.type': typeof e?.name === 'string' && e.name.length > 0 ? e.name : 'Error',\n 'exception.message': typeof e?.message === 'string' ? e.message : String(err),\n ...(typeof e?.stack === 'string' ? { 'exception.stacktrace': e.stack } : {}),\n };\n events.push({\n name: 'exception',\n timeUnixNano: Math.floor(input.now() * 1_000_000),\n attributes: attrs,\n });\n },\n setStatus(s: SpanStatus, message?: string): void {\n if (ended) return;\n status = s;\n if (message !== undefined) statusMessage = message;\n },\n end(): void {\n if (ended) return;\n ended = true;\n const endUnixNano = Math.floor(input.now() * 1_000_000);\n const record: SpanRecord<T> = {\n type: input.type,\n id: input.id,\n traceId: input.traceId,\n ...(input.parentId === undefined ? {} : { parentId: input.parentId }),\n name: input.name,\n startUnixNano,\n endUnixNano,\n status,\n ...(statusMessage === undefined ? {} : { statusMessage }),\n attributes: Object.freeze({ ...attributes }) as SpanAttributes,\n events: Object.freeze([...events]) as SpanRecord['events'],\n ...(Object.keys(sensitivities).length === 0\n ? {}\n : {\n sensitivityByAttribute: Object.freeze({ ...sensitivities }) as Readonly<\n Record<string, SpanAttributeValue>\n >,\n }),\n };\n input.sink(record);\n },\n };\n\n return span;\n}\n"],"mappings":";;;;AAiEA,SAAgB,WAA+B,OAA6C;CAC1F,MAAM,gBAAgB,KAAK,MAAM,MAAM,KAAK,GAAG,IAAU;CACzD,MAAMA,aAAiD,EAAE,GAAI,MAAM,SAAS,EAAE,EAAG;CACjF,MAAMC,gBAA6C,EAAE,GAAI,MAAM,qBAAqB,EAAE,EAAG;CACzF,MAAMC,SAAyC,EAAE;CACjD,IAAIC,SAAqB;CACzB,IAAIC;CACJ,IAAI,QAAQ;AA4EZ,QA1E+B;EAC7B,MAAM,MAAM;EACZ,IAAI,MAAM;EACV,SAAS,MAAM;EACf,GAAI,MAAM,aAAa,SAAY,EAAE,GAAG,EAAE,UAAU,MAAM,UAAU;EACpE,cAAc,OAA6B;AACzC,OAAI,MAAO;AACX,QAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAM,CACxC,YAAW,KAAK;;EAGpB,aAAa,MAAc,OAA2B,MAAkC;AACtF,OAAI,MAAO;AACX,cAAW,QAAQ;AACnB,OAAI,MAAM,gBAAgB,OACxB,eAAc,QAAQ,KAAK;;EAG/B,SAAS,MAAc,OAA8B;AACnD,OAAI,MAAO;AACX,OAAI,CAAC,MAAM,YAAY,KAAK,CAAE;AAC9B,UAAO,KAAK;IACV;IACA,cAAc,KAAK,MAAM,MAAM,KAAK,GAAG,IAAU;IACjD,YAAY,OAAO,OAAO,EAAE,GAAI,SAAS,EAAE,EAAG,CAAC;IAChD,CAAC;;EAEJ,gBAAgB,KAAoB;AAClC,OAAI,MAAO;GACX,MAAM,IAAI;GACV,MAAMC,QAAwB;IAC5B,kBAAkB,OAAO,GAAG,SAAS,YAAY,EAAE,KAAK,SAAS,IAAI,EAAE,OAAO;IAC9E,qBAAqB,OAAO,GAAG,YAAY,WAAW,EAAE,UAAU,OAAO,IAAI;IAC7E,GAAI,OAAO,GAAG,UAAU,WAAW,EAAE,wBAAwB,EAAE,OAAO,GAAG,EAAE;IAC5E;AACD,UAAO,KAAK;IACV,MAAM;IACN,cAAc,KAAK,MAAM,MAAM,KAAK,GAAG,IAAU;IACjD,YAAY;IACb,CAAC;;EAEJ,UAAU,GAAe,SAAwB;AAC/C,OAAI,MAAO;AACX,YAAS;AACT,OAAI,YAAY,OAAW,iBAAgB;;EAE7C,MAAY;AACV,OAAI,MAAO;AACX,WAAQ;GACR,MAAM,cAAc,KAAK,MAAM,MAAM,KAAK,GAAG,IAAU;GACvD,MAAMC,SAAwB;IAC5B,MAAM,MAAM;IACZ,IAAI,MAAM;IACV,SAAS,MAAM;IACf,GAAI,MAAM,aAAa,SAAY,EAAE,GAAG,EAAE,UAAU,MAAM,UAAU;IACpE,MAAM,MAAM;IACZ;IACA;IACA;IACA,GAAI,kBAAkB,SAAY,EAAE,GAAG,EAAE,eAAe;IACxD,YAAY,OAAO,OAAO,EAAE,GAAG,YAAY,CAAC;IAC5C,QAAQ,OAAO,OAAO,CAAC,GAAG,OAAO,CAAC;IAClC,GAAI,OAAO,KAAK,cAAc,CAAC,WAAW,IACtC,EAAE,GACF,EACE,wBAAwB,OAAO,OAAO,EAAE,GAAG,eAAe,CAAC,EAG5D;IACN;AACD,SAAM,KAAK,OAAO;;EAErB"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { TraceExporter } from "../exporters/types.js";
|
|
2
|
+
import { RedactionCounters, RedactionValidatorInstance, RedactionValidatorOptions } from "../redaction/types.js";
|
|
3
|
+
import { SamplingOptions } from "./sampling.js";
|
|
4
|
+
import { GraphorinSpan } from "./span.js";
|
|
5
|
+
import { AISpan, Sensitivity, SpanType, Tracer } from "@graphorin/core";
|
|
6
|
+
|
|
7
|
+
//#region src/tracer/tracer.d.ts
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Configuration shape consumed by {@link createTracer}.
|
|
11
|
+
*
|
|
12
|
+
* @stable
|
|
13
|
+
*/
|
|
14
|
+
interface TracerOptions {
|
|
15
|
+
/** Logical service name. Embedded in the OTLP `Resource`. */
|
|
16
|
+
readonly serviceName?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Configured exporters. Each exporter MUST be wrapped via
|
|
19
|
+
* `withValidation(...)` before reaching the tracer — pass them
|
|
20
|
+
* through unwrapped to use the tracer-managed validator (set by
|
|
21
|
+
* `validation`) or pass already-wrapped exporters when you want
|
|
22
|
+
* per-exporter policies.
|
|
23
|
+
*/
|
|
24
|
+
readonly exporters: ReadonlyArray<TraceExporter>;
|
|
25
|
+
/**
|
|
26
|
+
* Tracer-managed validator policy.
|
|
27
|
+
*
|
|
28
|
+
* - `RedactionValidatorOptions` (default): the tracer auto-wraps any
|
|
29
|
+
* un-wrapped exporter with `withValidation(...)` using these
|
|
30
|
+
* options.
|
|
31
|
+
* - `'off'`: NOT recommended. Skips auto-wrap, and the tracer logs a
|
|
32
|
+
* startup WARN to the supplied {@link warnSink}. Pre-wrapped
|
|
33
|
+
* exporters still flow through their validators.
|
|
34
|
+
*
|
|
35
|
+
* @default `{ minTier: 'public', failOnUnredactedSensitive: false }`
|
|
36
|
+
*/
|
|
37
|
+
readonly validation?: RedactionValidatorOptions | 'off';
|
|
38
|
+
/** Sampler configuration. */
|
|
39
|
+
readonly sampling?: SamplingOptions;
|
|
40
|
+
/**
|
|
41
|
+
* Default sensitivity for attributes that omit `setAttribute(_, _, { sensitivity })`.
|
|
42
|
+
* Defaults to `'internal'` per the default-deny non-public posture.
|
|
43
|
+
*/
|
|
44
|
+
readonly defaultAttributeSensitivity?: Sensitivity;
|
|
45
|
+
/**
|
|
46
|
+
* Sink used for startup WARNings. Defaults to `console.warn`.
|
|
47
|
+
*
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
readonly warnSink?: (line: string) => void;
|
|
51
|
+
/**
|
|
52
|
+
* Override the wall clock used for span timestamps. Returns
|
|
53
|
+
* milliseconds-since-epoch as a `number`. Default: `Date.now`.
|
|
54
|
+
*
|
|
55
|
+
* @internal
|
|
56
|
+
*/
|
|
57
|
+
readonly now?: () => number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The {@link createTracer} return value extends the standard
|
|
61
|
+
* {@link Tracer} contract from `@graphorin/core` with introspection
|
|
62
|
+
* helpers (counter snapshots, validator handle).
|
|
63
|
+
*
|
|
64
|
+
* @stable
|
|
65
|
+
*/
|
|
66
|
+
interface GraphorinTracer extends Tracer {
|
|
67
|
+
/** Service name embedded in the OTLP resource. */
|
|
68
|
+
readonly serviceName: string;
|
|
69
|
+
/**
|
|
70
|
+
* Snapshot of the redaction counters (`droppedTotal`,
|
|
71
|
+
* `droppedByReason`, `matchesByPattern`) maintained by the
|
|
72
|
+
* tracer-managed validator.
|
|
73
|
+
*/
|
|
74
|
+
getMetrics(): RedactionCounters;
|
|
75
|
+
/** The tracer-managed validator. `null` when `validation: 'off'`. */
|
|
76
|
+
readonly validator: RedactionValidatorInstance | null;
|
|
77
|
+
/** Force-flush every registered exporter. */
|
|
78
|
+
flush(): Promise<void>;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Build a {@link GraphorinTracer} from the supplied options. Every
|
|
82
|
+
* exporter passed in must already be wrapped via
|
|
83
|
+
* `withValidation(...)` OR `validation` must be set to a concrete
|
|
84
|
+
* options object so the tracer can auto-wrap on your behalf.
|
|
85
|
+
* Registering a raw exporter while `validation: 'off'` triggers an
|
|
86
|
+
* {@link UnvalidatedExporterError} at startup.
|
|
87
|
+
*
|
|
88
|
+
* @stable
|
|
89
|
+
*/
|
|
90
|
+
declare function createTracer(opts: TracerOptions): GraphorinTracer;
|
|
91
|
+
/**
|
|
92
|
+
* Returns the underlying {@link GraphorinSpan} when `span` is a Graphorin
|
|
93
|
+
* span. Useful when callers want to reach the per-attribute sensitivity
|
|
94
|
+
* helper from a generic `AISpan<T>`.
|
|
95
|
+
*
|
|
96
|
+
* @stable
|
|
97
|
+
*/
|
|
98
|
+
declare function asGraphorinSpan<T extends SpanType>(span: AISpan<T>): GraphorinSpan<T> | null;
|
|
99
|
+
//#endregion
|
|
100
|
+
export { GraphorinTracer, TracerOptions, asGraphorinSpan, createTracer };
|
|
101
|
+
//# sourceMappingURL=tracer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracer.d.ts","names":[],"sources":["../../src/tracer/tracer.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AAuFA;;;AAYW,UAjEM,aAAA,CAiEN;EAZ8B;EAAM,SAAA,WAAA,CAAA,EAAA,MAAA;EA8B/B;AA0LhB;;;;;;EAAmF,SAAA,SAAA,EAnQ7D,aAmQ6D,CAnQ/C,aAmQ+C,CAAA;;;;;;;;;;;;;wBAtP3D;;sBAEF;;;;;yCAKmB;;;;;;;;;;;;;;;;;;;;;;UAuBxB,eAAA,SAAwB;;;;;;;;gBAAR;;sBAUX;;WAEX;;;;;;;;;;;;iBAkBK,YAAA,OAAmB,gBAAgB;;;;;;;;iBA0LnC,0BAA0B,gBAAgB,OAAO,KAAK,cAAc"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { createConsoleExporter } from "../exporters/console.js";
|
|
2
|
+
import { UnvalidatedExporterError } from "../redaction/errors.js";
|
|
3
|
+
import { createRedactionValidator } from "../redaction/validator.js";
|
|
4
|
+
import { isValidatedExporter, withValidation } from "../exporters/with-validation.js";
|
|
5
|
+
import { newSpanId, newTraceId } from "./ids.js";
|
|
6
|
+
import { createSampler } from "./sampling.js";
|
|
7
|
+
import { createSpan } from "./span.js";
|
|
8
|
+
import { spanNameFor } from "./span-names.js";
|
|
9
|
+
|
|
10
|
+
//#region src/tracer/tracer.ts
|
|
11
|
+
const DEFAULT_VALIDATION = Object.freeze({
|
|
12
|
+
minTier: "public",
|
|
13
|
+
failOnUnredactedSensitive: false
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* Build a {@link GraphorinTracer} from the supplied options. Every
|
|
17
|
+
* exporter passed in must already be wrapped via
|
|
18
|
+
* `withValidation(...)` OR `validation` must be set to a concrete
|
|
19
|
+
* options object so the tracer can auto-wrap on your behalf.
|
|
20
|
+
* Registering a raw exporter while `validation: 'off'` triggers an
|
|
21
|
+
* {@link UnvalidatedExporterError} at startup.
|
|
22
|
+
*
|
|
23
|
+
* @stable
|
|
24
|
+
*/
|
|
25
|
+
function createTracer(opts) {
|
|
26
|
+
const serviceName = opts.serviceName ?? "graphorin";
|
|
27
|
+
const sampler = createSampler(opts.sampling ?? {});
|
|
28
|
+
const warnSink = opts.warnSink ?? ((line) => console.warn(line));
|
|
29
|
+
const now = opts.now ?? (() => Date.now());
|
|
30
|
+
const defaultAttrSensitivity = opts.defaultAttributeSensitivity ?? "internal";
|
|
31
|
+
const validationMode = opts.validation ?? DEFAULT_VALIDATION;
|
|
32
|
+
let validator = null;
|
|
33
|
+
let exporters = [];
|
|
34
|
+
if (validationMode === "off") {
|
|
35
|
+
warnSink("[graphorin/observability] WARN: validation: 'off' — exporters are NOT auto-wrapped. Every exporter must call withValidation(...) explicitly. See ADR-035: OTLP RedactionValidator + default-deny non-public.");
|
|
36
|
+
for (const exporter of opts.exporters) {
|
|
37
|
+
if (!isValidatedExporter(exporter)) throw new UnvalidatedExporterError(exporter.id);
|
|
38
|
+
exporters.push(exporter);
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
validator = createRedactionValidator(validationMode);
|
|
42
|
+
for (const exporter of opts.exporters) exporters.push(isValidatedExporter(exporter) ? exporter : withValidation(exporter, { validator }));
|
|
43
|
+
}
|
|
44
|
+
if (exporters.length === 0) {
|
|
45
|
+
const fallback = withValidation(createConsoleExporter({ id: "console-fallback" }), { validator: validator ?? createRedactionValidator(DEFAULT_VALIDATION) });
|
|
46
|
+
warnSink("[graphorin/observability] WARN: createTracer() received zero exporters. Falling back to a sanitized ConsoleExporter so spans are not lost.");
|
|
47
|
+
exporters = [fallback];
|
|
48
|
+
}
|
|
49
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
50
|
+
function sink(record) {
|
|
51
|
+
for (const exporter of exporters) {
|
|
52
|
+
const p = Promise.resolve().then(() => exporter.export(record)).catch((err) => {
|
|
53
|
+
warnSink(`[graphorin/observability] WARN: exporter ${exporter.id} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
54
|
+
});
|
|
55
|
+
inFlight.add(p);
|
|
56
|
+
p.finally(() => {
|
|
57
|
+
inFlight.delete(p);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function startSpan(spec) {
|
|
62
|
+
const traceId = spec.parent?.traceId ?? newTraceId();
|
|
63
|
+
const id = newSpanId();
|
|
64
|
+
const parentId = spec.parent?.id;
|
|
65
|
+
const parentSampled = spec.parent === void 0 ? void 0 : asGraphorinSpan(spec.parent) !== null;
|
|
66
|
+
if (!sampler.shouldSample(spec.type, parentSampled)) return noopSpan(spec.type, traceId, id, parentId);
|
|
67
|
+
const sensitivities = readAttrSensitivities(spec.attrs);
|
|
68
|
+
return createSpan({
|
|
69
|
+
type: spec.type,
|
|
70
|
+
name: spanNameFor(spec.type),
|
|
71
|
+
traceId,
|
|
72
|
+
id,
|
|
73
|
+
...parentId === void 0 ? {} : { parentId },
|
|
74
|
+
...spec.attrs === void 0 ? {} : { attrs: spec.attrs },
|
|
75
|
+
...sensitivities === void 0 ? {} : { attrSensitivities: sensitivities },
|
|
76
|
+
sink,
|
|
77
|
+
now,
|
|
78
|
+
recordEvent: (name) => sampler.shouldRecordEvent(name)
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
async function span(spec, fn) {
|
|
82
|
+
const s = startSpan(spec);
|
|
83
|
+
try {
|
|
84
|
+
const out = await fn(s);
|
|
85
|
+
s.setStatus("ok");
|
|
86
|
+
return out;
|
|
87
|
+
} catch (err) {
|
|
88
|
+
s.recordException(err);
|
|
89
|
+
s.setStatus("error", err instanceof Error ? err.message : String(err));
|
|
90
|
+
throw err;
|
|
91
|
+
} finally {
|
|
92
|
+
s.end();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function flush() {
|
|
96
|
+
while (inFlight.size > 0) await Promise.all([...inFlight]);
|
|
97
|
+
await Promise.all(exporters.map((e) => e.flush()));
|
|
98
|
+
}
|
|
99
|
+
async function shutdown() {
|
|
100
|
+
await flush();
|
|
101
|
+
await Promise.all(exporters.map((e) => e.shutdown()));
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
startSpan,
|
|
105
|
+
span,
|
|
106
|
+
flush,
|
|
107
|
+
shutdown,
|
|
108
|
+
serviceName,
|
|
109
|
+
getMetrics: () => validator?.counters() ?? {
|
|
110
|
+
droppedTotal: 0,
|
|
111
|
+
droppedByReason: Object.freeze({}),
|
|
112
|
+
matchesByPattern: Object.freeze({})
|
|
113
|
+
},
|
|
114
|
+
validator
|
|
115
|
+
};
|
|
116
|
+
function readAttrSensitivities(attrs) {
|
|
117
|
+
if (attrs === void 0) return void 0;
|
|
118
|
+
const out = {};
|
|
119
|
+
for (const key of Object.keys(attrs)) out[key] = defaultAttrSensitivity;
|
|
120
|
+
return Object.freeze(out);
|
|
121
|
+
}
|
|
122
|
+
function noopSpan(type, traceId, id, parentId) {
|
|
123
|
+
return {
|
|
124
|
+
type,
|
|
125
|
+
id,
|
|
126
|
+
traceId,
|
|
127
|
+
...parentId === void 0 ? {} : { parentId },
|
|
128
|
+
setAttributes() {},
|
|
129
|
+
addEvent() {},
|
|
130
|
+
recordException() {},
|
|
131
|
+
setStatus() {},
|
|
132
|
+
end() {}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Returns the underlying {@link GraphorinSpan} when `span` is a Graphorin
|
|
138
|
+
* span. Useful when callers want to reach the per-attribute sensitivity
|
|
139
|
+
* helper from a generic `AISpan<T>`.
|
|
140
|
+
*
|
|
141
|
+
* @stable
|
|
142
|
+
*/
|
|
143
|
+
function asGraphorinSpan(span) {
|
|
144
|
+
if (typeof span.setAttribute === "function") return span;
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
//#endregion
|
|
149
|
+
export { asGraphorinSpan, createTracer };
|
|
150
|
+
//# sourceMappingURL=tracer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracer.js","names":["DEFAULT_VALIDATION: RedactionValidatorOptions","defaultAttrSensitivity: Sensitivity","validator: RedactionValidatorInstance | null","exporters: TraceExporter[]","out: Record<string, Sensitivity>"],"sources":["../../src/tracer/tracer.ts"],"sourcesContent":["/**\n * `createTracer(...)` — the public entry point for the observability\n * tracer. Wires together the {@link Sampler}, the\n * {@link RedactionValidator}, and the registered {@link TraceExporter}s.\n *\n * @packageDocumentation\n */\n\nimport type {\n AISpan,\n Sensitivity,\n SpanAttributeValue,\n SpanType,\n StartSpanOptions,\n Tracer,\n} from '@graphorin/core';\n\nimport { createConsoleExporter } from '../exporters/console.js';\nimport type { SpanRecord, TraceExporter } from '../exporters/types.js';\nimport { isValidatedExporter, withValidation } from '../exporters/with-validation.js';\nimport { UnvalidatedExporterError } from '../redaction/errors.js';\nimport type { RedactionValidatorInstance, RedactionValidatorOptions } from '../redaction/types.js';\nimport { createRedactionValidator } from '../redaction/validator.js';\n\nimport { newSpanId, newTraceId } from './ids.js';\nimport { createSampler, type SamplingOptions } from './sampling.js';\nimport { createSpan, type GraphorinSpan } from './span.js';\nimport { spanNameFor } from './span-names.js';\n\n/**\n * Configuration shape consumed by {@link createTracer}.\n *\n * @stable\n */\nexport interface TracerOptions {\n /** Logical service name. Embedded in the OTLP `Resource`. */\n readonly serviceName?: string;\n /**\n * Configured exporters. Each exporter MUST be wrapped via\n * `withValidation(...)` before reaching the tracer — pass them\n * through unwrapped to use the tracer-managed validator (set by\n * `validation`) or pass already-wrapped exporters when you want\n * per-exporter policies.\n */\n readonly exporters: ReadonlyArray<TraceExporter>;\n /**\n * Tracer-managed validator policy.\n *\n * - `RedactionValidatorOptions` (default): the tracer auto-wraps any\n * un-wrapped exporter with `withValidation(...)` using these\n * options.\n * - `'off'`: NOT recommended. Skips auto-wrap, and the tracer logs a\n * startup WARN to the supplied {@link warnSink}. Pre-wrapped\n * exporters still flow through their validators.\n *\n * @default `{ minTier: 'public', failOnUnredactedSensitive: false }`\n */\n readonly validation?: RedactionValidatorOptions | 'off';\n /** Sampler configuration. */\n readonly sampling?: SamplingOptions;\n /**\n * Default sensitivity for attributes that omit `setAttribute(_, _, { sensitivity })`.\n * Defaults to `'internal'` per the default-deny non-public posture.\n */\n readonly defaultAttributeSensitivity?: Sensitivity;\n /**\n * Sink used for startup WARNings. Defaults to `console.warn`.\n *\n * @internal\n */\n readonly warnSink?: (line: string) => void;\n /**\n * Override the wall clock used for span timestamps. Returns\n * milliseconds-since-epoch as a `number`. Default: `Date.now`.\n *\n * @internal\n */\n readonly now?: () => number;\n}\n\n/**\n * The {@link createTracer} return value extends the standard\n * {@link Tracer} contract from `@graphorin/core` with introspection\n * helpers (counter snapshots, validator handle).\n *\n * @stable\n */\nexport interface GraphorinTracer extends Tracer {\n /** Service name embedded in the OTLP resource. */\n readonly serviceName: string;\n /**\n * Snapshot of the redaction counters (`droppedTotal`,\n * `droppedByReason`, `matchesByPattern`) maintained by the\n * tracer-managed validator.\n */\n getMetrics(): import('../redaction/types.js').RedactionCounters;\n /** The tracer-managed validator. `null` when `validation: 'off'`. */\n readonly validator: RedactionValidatorInstance | null;\n /** Force-flush every registered exporter. */\n flush(): Promise<void>;\n}\n\nconst DEFAULT_VALIDATION: RedactionValidatorOptions = Object.freeze({\n minTier: 'public',\n failOnUnredactedSensitive: false,\n});\n\n/**\n * Build a {@link GraphorinTracer} from the supplied options. Every\n * exporter passed in must already be wrapped via\n * `withValidation(...)` OR `validation` must be set to a concrete\n * options object so the tracer can auto-wrap on your behalf.\n * Registering a raw exporter while `validation: 'off'` triggers an\n * {@link UnvalidatedExporterError} at startup.\n *\n * @stable\n */\nexport function createTracer(opts: TracerOptions): GraphorinTracer {\n const serviceName = opts.serviceName ?? 'graphorin';\n const sampler = createSampler(opts.sampling ?? {});\n const warnSink = opts.warnSink ?? ((line: string) => console.warn(line));\n const now = opts.now ?? (() => Date.now());\n const defaultAttrSensitivity: Sensitivity = opts.defaultAttributeSensitivity ?? 'internal';\n\n const validationMode = opts.validation ?? DEFAULT_VALIDATION;\n\n let validator: RedactionValidatorInstance | null = null;\n let exporters: TraceExporter[] = [];\n\n if (validationMode === 'off') {\n warnSink(\n \"[graphorin/observability] WARN: validation: 'off' — exporters are NOT \" +\n 'auto-wrapped. Every exporter must call withValidation(...) explicitly. ' +\n 'See ADR-035: OTLP RedactionValidator + default-deny non-public.',\n );\n for (const exporter of opts.exporters) {\n if (!isValidatedExporter(exporter)) {\n throw new UnvalidatedExporterError(exporter.id);\n }\n exporters.push(exporter);\n }\n } else {\n validator = createRedactionValidator(validationMode);\n for (const exporter of opts.exporters) {\n exporters.push(\n isValidatedExporter(exporter) ? exporter : withValidation(exporter, { validator }),\n );\n }\n }\n\n if (exporters.length === 0) {\n // The tracer is still useful (it emits records to /dev/null) but\n // we want to surface that explicitly so consumers don't think the\n // logger is broken.\n const fallback = withValidation(createConsoleExporter({ id: 'console-fallback' }), {\n validator: validator ?? createRedactionValidator(DEFAULT_VALIDATION),\n });\n warnSink(\n '[graphorin/observability] WARN: createTracer() received zero exporters. ' +\n 'Falling back to a sanitized ConsoleExporter so spans are not lost.',\n );\n exporters = [fallback];\n }\n\n // RP-20: sink() exports are fire-and-forget; hold each promise so flush()\n // can await it. Without this a span emitted moments before shutdown is lost\n // when the exporter's closed-guard trips before its export() resolves.\n const inFlight = new Set<Promise<void>>();\n\n function sink(record: SpanRecord): void {\n for (const exporter of exporters) {\n const p = Promise.resolve()\n .then(() => exporter.export(record))\n .catch((err: unknown) => {\n warnSink(\n `[graphorin/observability] WARN: exporter ${exporter.id} failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n });\n inFlight.add(p);\n void p.finally(() => {\n inFlight.delete(p);\n });\n }\n }\n\n function startSpan<T extends SpanType>(spec: StartSpanOptions<T>): AISpan<T> {\n const traceId = spec.parent?.traceId ?? newTraceId();\n const id = newSpanId();\n const parentId = spec.parent?.id;\n // RP-19: a real parent-sampled flag for `'parent-based'` sampling. An\n // unsampled parent is a noop span (no `setAttribute`), so `asGraphorinSpan`\n // returns null — `parentId !== undefined` used to treat that as\n // parent-sampled and record the child as an orphan.\n const parentSampled =\n spec.parent === undefined ? undefined : asGraphorinSpan(spec.parent) !== null;\n const sampled = sampler.shouldSample(spec.type, parentSampled);\n if (!sampled) {\n return noopSpan<T>(spec.type, traceId, id, parentId);\n }\n const sensitivities = readAttrSensitivities(spec.attrs);\n return createSpan<T>({\n type: spec.type,\n name: spanNameFor(spec.type),\n traceId,\n id,\n ...(parentId === undefined ? {} : { parentId }),\n ...(spec.attrs === undefined ? {} : { attrs: spec.attrs }),\n ...(sensitivities === undefined ? {} : { attrSensitivities: sensitivities }),\n sink,\n now,\n recordEvent: (name) => sampler.shouldRecordEvent(name),\n }) as unknown as AISpan<T>;\n }\n\n async function span<T extends SpanType, R>(\n spec: StartSpanOptions<T>,\n fn: (s: AISpan<T>) => R | Promise<R>,\n ): Promise<R> {\n const s = startSpan(spec);\n try {\n const out = await fn(s);\n s.setStatus('ok');\n return out;\n } catch (err) {\n s.recordException(err);\n s.setStatus('error', err instanceof Error ? err.message : String(err));\n throw err;\n } finally {\n s.end();\n }\n }\n\n async function flush(): Promise<void> {\n // RP-20: drain in-flight fire-and-forget exports first, then flush.\n while (inFlight.size > 0) {\n await Promise.all([...inFlight]);\n }\n await Promise.all(exporters.map((e) => e.flush()));\n }\n\n async function shutdown(): Promise<void> {\n await flush();\n await Promise.all(exporters.map((e) => e.shutdown()));\n }\n\n return {\n startSpan,\n span,\n flush,\n shutdown,\n serviceName,\n getMetrics: () =>\n validator?.counters() ?? {\n droppedTotal: 0,\n droppedByReason: Object.freeze({}),\n matchesByPattern: Object.freeze({}),\n },\n validator,\n };\n\n function readAttrSensitivities(\n attrs: undefined | Readonly<Record<string, SpanAttributeValue>>,\n ): Readonly<Record<string, Sensitivity>> | undefined {\n if (attrs === undefined) return undefined;\n // RP-19: initial attrs that omit an explicit `setAttribute(_, _, {\n // sensitivity })` default to `defaultAttributeSensitivity`. Threading it\n // here makes the knob effective — untagged framework attributes carry the\n // configured tier instead of the validator's hardcoded fallback.\n const out: Record<string, Sensitivity> = {};\n for (const key of Object.keys(attrs)) out[key] = defaultAttrSensitivity;\n return Object.freeze(out);\n }\n\n function noopSpan<T extends SpanType>(\n type: T,\n traceId: string,\n id: string,\n parentId?: string,\n ): AISpan<T> {\n const stub: AISpan<T> = {\n type,\n id,\n traceId,\n ...(parentId === undefined ? {} : { parentId }),\n setAttributes() {},\n addEvent() {},\n recordException() {},\n setStatus() {},\n end() {},\n };\n return stub;\n }\n}\n\n/**\n * Returns the underlying {@link GraphorinSpan} when `span` is a Graphorin\n * span. Useful when callers want to reach the per-attribute sensitivity\n * helper from a generic `AISpan<T>`.\n *\n * @stable\n */\nexport function asGraphorinSpan<T extends SpanType>(span: AISpan<T>): GraphorinSpan<T> | null {\n if (typeof (span as Partial<GraphorinSpan<T>>).setAttribute === 'function') {\n return span as GraphorinSpan<T>;\n }\n return null;\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@graphorin/observability",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Observability primitives for the Graphorin framework: typed AISpan tracer over OpenTelemetry, mandatory withValidation wrapper for every exporter, sensitivity-aware RedactionValidator with built-in PII / secret patterns, GenAI Semantic Conventions conformance helpers, OpenInference span-kind emission, structured logger with span correlation, append-only JSONL exporter for replay, sanitized replay primitives, hierarchical CostTracker, and a minimal inline eval runner alongside a zero-default telemetry stub.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Oleksiy Stepurenko",
|
|
7
|
+
"homepage": "https://github.com/o-stepper/graphorin/tree/main/packages/observability",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/o-stepper/graphorin.git",
|
|
11
|
+
"directory": "packages/observability"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/o-stepper/graphorin/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"graphorin",
|
|
18
|
+
"ai",
|
|
19
|
+
"agents",
|
|
20
|
+
"framework",
|
|
21
|
+
"observability",
|
|
22
|
+
"tracing",
|
|
23
|
+
"opentelemetry",
|
|
24
|
+
"otel",
|
|
25
|
+
"gen-ai",
|
|
26
|
+
"openinference",
|
|
27
|
+
"redaction",
|
|
28
|
+
"pii",
|
|
29
|
+
"cost-tracking",
|
|
30
|
+
"replay",
|
|
31
|
+
"evals",
|
|
32
|
+
"logger"
|
|
33
|
+
],
|
|
34
|
+
"type": "module",
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=22.0.0"
|
|
37
|
+
},
|
|
38
|
+
"main": "./dist/index.js",
|
|
39
|
+
"module": "./dist/index.js",
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
41
|
+
"exports": {
|
|
42
|
+
".": {
|
|
43
|
+
"types": "./dist/index.d.ts",
|
|
44
|
+
"import": "./dist/index.js"
|
|
45
|
+
},
|
|
46
|
+
"./tracer": {
|
|
47
|
+
"types": "./dist/tracer/index.d.ts",
|
|
48
|
+
"import": "./dist/tracer/index.js"
|
|
49
|
+
},
|
|
50
|
+
"./redaction": {
|
|
51
|
+
"types": "./dist/redaction/index.d.ts",
|
|
52
|
+
"import": "./dist/redaction/index.js"
|
|
53
|
+
},
|
|
54
|
+
"./redaction/patterns": {
|
|
55
|
+
"types": "./dist/redaction/patterns.d.ts",
|
|
56
|
+
"import": "./dist/redaction/patterns.js"
|
|
57
|
+
},
|
|
58
|
+
"./redaction/imperative-patterns": {
|
|
59
|
+
"types": "./dist/redaction/imperative-patterns.d.ts",
|
|
60
|
+
"import": "./dist/redaction/imperative-patterns.js"
|
|
61
|
+
},
|
|
62
|
+
"./exporters": {
|
|
63
|
+
"types": "./dist/exporters/index.d.ts",
|
|
64
|
+
"import": "./dist/exporters/index.js"
|
|
65
|
+
},
|
|
66
|
+
"./gen-ai": {
|
|
67
|
+
"types": "./dist/gen-ai/index.d.ts",
|
|
68
|
+
"import": "./dist/gen-ai/index.js"
|
|
69
|
+
},
|
|
70
|
+
"./openinference": {
|
|
71
|
+
"types": "./dist/openinference/index.d.ts",
|
|
72
|
+
"import": "./dist/openinference/index.js"
|
|
73
|
+
},
|
|
74
|
+
"./cost": {
|
|
75
|
+
"types": "./dist/cost/index.d.ts",
|
|
76
|
+
"import": "./dist/cost/index.js"
|
|
77
|
+
},
|
|
78
|
+
"./replay": {
|
|
79
|
+
"types": "./dist/replay/index.d.ts",
|
|
80
|
+
"import": "./dist/replay/index.js"
|
|
81
|
+
},
|
|
82
|
+
"./eval": {
|
|
83
|
+
"types": "./dist/eval/index.d.ts",
|
|
84
|
+
"import": "./dist/eval/index.js"
|
|
85
|
+
},
|
|
86
|
+
"./logger": {
|
|
87
|
+
"types": "./dist/logger/index.d.ts",
|
|
88
|
+
"import": "./dist/logger/index.js"
|
|
89
|
+
},
|
|
90
|
+
"./telemetry": {
|
|
91
|
+
"types": "./dist/telemetry/index.d.ts",
|
|
92
|
+
"import": "./dist/telemetry/index.js"
|
|
93
|
+
},
|
|
94
|
+
"./package.json": "./package.json"
|
|
95
|
+
},
|
|
96
|
+
"files": [
|
|
97
|
+
"dist",
|
|
98
|
+
"README.md",
|
|
99
|
+
"CHANGELOG.md",
|
|
100
|
+
"LICENSE"
|
|
101
|
+
],
|
|
102
|
+
"dependencies": {
|
|
103
|
+
"@graphorin/core": "0.5.0"
|
|
104
|
+
},
|
|
105
|
+
"peerDependencies": {
|
|
106
|
+
"@opentelemetry/api": "^1.9.0",
|
|
107
|
+
"@opentelemetry/sdk-node": "^0.217.0",
|
|
108
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.215.0"
|
|
109
|
+
},
|
|
110
|
+
"peerDependenciesMeta": {
|
|
111
|
+
"@opentelemetry/api": {
|
|
112
|
+
"optional": false
|
|
113
|
+
},
|
|
114
|
+
"@opentelemetry/sdk-node": {
|
|
115
|
+
"optional": true
|
|
116
|
+
},
|
|
117
|
+
"@opentelemetry/exporter-trace-otlp-http": {
|
|
118
|
+
"optional": true
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
"publishConfig": {
|
|
122
|
+
"access": "public",
|
|
123
|
+
"provenance": true
|
|
124
|
+
},
|
|
125
|
+
"devDependencies": {
|
|
126
|
+
"@opentelemetry/api": "^1.9.0",
|
|
127
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.215.0",
|
|
128
|
+
"@opentelemetry/sdk-node": "^0.217.0",
|
|
129
|
+
"fast-check": "^3.23.0"
|
|
130
|
+
},
|
|
131
|
+
"scripts": {
|
|
132
|
+
"build": "tsdown",
|
|
133
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.tests.json",
|
|
134
|
+
"test": "vitest run",
|
|
135
|
+
"lint": "biome check .",
|
|
136
|
+
"clean": "rimraf dist .turbo *.tsbuildinfo"
|
|
137
|
+
}
|
|
138
|
+
}
|