cachegate 1.3.1 → 1.4.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/tracing.js ADDED
@@ -0,0 +1,97 @@
1
+ // model-router/tracing.js
2
+ //
3
+ // OpenTelemetry export (roadmap step 36.2). Builds on the request-scoped
4
+ // `trace_id` that server.js generates (step 36.1): that id is set as a span
5
+ // attribute so a trace in an OTel backend and a metrics.js row for the same
6
+ // request can be cross-referenced by the same value even though they are two
7
+ // different systems.
8
+ //
9
+ // Gated off by default (OTEL_ENABLED). The @opentelemetry/api no-op tracer
10
+ // is always present (it is the API surface and costs nothing when the SDK is
11
+ // not initialized), but the heavy SDK + exporter are lazily required and only
12
+ // started when BOTH OTEL_ENABLED=true AND OTEL_EXPORTER_OTLP_ENDPOINT is set -
13
+ // "never initializes at all" when unused, not "initializes and exports
14
+ // nowhere". Spans here are MANUAL ONLY: auto-instrumentation is explicitly out
15
+ // of scope (spec 220), so no registerInstrumentations() call.
16
+
17
+ const { trace, context } = require('@opentelemetry/api');
18
+
19
+ const TRACER_NAME = 'cachegate';
20
+
21
+ let initialized = false;
22
+
23
+ /**
24
+ * Initialize the OTel SDK exactly once. A no-op unless both gates are open.
25
+ * Any SDK failure is logged and swallowed (fail-open): tracing must never be
26
+ * the reason a real request fails.
27
+ */
28
+ function initTracing() {
29
+ if (initialized) return;
30
+ initialized = true;
31
+ if (process.env.OTEL_ENABLED !== 'true') return;
32
+ const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
33
+ if (!endpoint) return;
34
+ try {
35
+ // Lazy-require the SDK + exporter only on the enabled path, so a
36
+ // deployment that never opts in never loads them.
37
+ const { NodeSDK } = require('@opentelemetry/sdk-node');
38
+ const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
39
+ const sdk = new NodeSDK({
40
+ traceExporter: new OTLPTraceExporter({ url: endpoint })
41
+ // No instrumentations: manual spans only (spec 220, out of scope).
42
+ });
43
+ sdk.start();
44
+ process.on('SIGTERM', () => {
45
+ sdk.shutdown().finally(() => process.exit(0));
46
+ });
47
+ } catch (err) {
48
+ console.warn('⚠️ OTel SDK failed to initialize, tracing disabled:', err.message);
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Start a span as a child of whatever span is currently active. With a no-op
54
+ * tracer (SDK off) this is a no-op object whose methods are all safe no-ops.
55
+ */
56
+ function startSpan(name, attributes = {}) {
57
+ return trace.getTracer(TRACER_NAME).startSpan(name, { attributes });
58
+ }
59
+
60
+ /**
61
+ * Run `fn` inside a span named `name`, ending it when `fn` settles. The span
62
+ * is a child of the active span (the request's root span, set via
63
+ * withRootSpan). Does NOT make the new span itself active - none of the
64
+ * wrapped operations start their own nested spans, so there is nothing to
65
+ * nest. Re-throws on failure after recording the exception on the span.
66
+ */
67
+ async function withSpan(name, attributes, fn) {
68
+ const span = startSpan(name, attributes);
69
+ try {
70
+ return await fn();
71
+ } catch (err) {
72
+ span.recordException(err);
73
+ throw err;
74
+ } finally {
75
+ span.end();
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Run `fn` inside a NEW root span (the one span per request), making it the
81
+ * active context so every `withSpan`/`startSpan` inside nests under it.
82
+ * `trace_id` is carried as a span attribute so this trace can be joined to
83
+ * metrics rows for the same request.
84
+ */
85
+ async function withRootSpan(name, attributes, fn) {
86
+ const span = startSpan(name, attributes);
87
+ try {
88
+ return await context.with(trace.setSpan(context.active(), span), fn);
89
+ } catch (err) {
90
+ span.recordException(err);
91
+ throw err;
92
+ } finally {
93
+ span.end();
94
+ }
95
+ }
96
+
97
+ module.exports = { initTracing, startSpan, withSpan, withRootSpan };