@open-mercato/telemetry 0.8.1-develop.7296.1.2111d779db → 0.9.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/README.md +7 -1
- package/dist/init.js +18 -7
- package/dist/init.js.map +2 -2
- package/dist/nextjs.js +5 -0
- package/dist/nextjs.js.map +2 -2
- package/package.json +3 -4
- package/src/__tests__/env-load-order.test.ts +23 -1
- package/src/__tests__/init-otlp-dependency.test.ts +208 -0
- package/src/init.ts +22 -9
- package/src/nextjs.ts +11 -5
package/README.md
CHANGED
|
@@ -170,7 +170,13 @@ export async function register(): Promise<void> {
|
|
|
170
170
|
if (process.env.NEXT_RUNTIME === 'nodejs' && isTelemetryBackendEnabled()) {
|
|
171
171
|
const { registerTelemetryForNextjs } =
|
|
172
172
|
await import('@open-mercato/telemetry/nextjs')
|
|
173
|
-
|
|
173
|
+
try {
|
|
174
|
+
await registerTelemetryForNextjs()
|
|
175
|
+
} catch (err) {
|
|
176
|
+
const nodeProcess = process
|
|
177
|
+
nodeProcess.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`)
|
|
178
|
+
nodeProcess.exit(1)
|
|
179
|
+
}
|
|
174
180
|
}
|
|
175
181
|
}
|
|
176
182
|
```
|
package/dist/init.js
CHANGED
|
@@ -13,12 +13,24 @@ import { registerTelemetryLogger } from "./facade/logger-bridge.js";
|
|
|
13
13
|
import { captureTraceContext, continueTrace } from "./facade/propagation.js";
|
|
14
14
|
import { withSpan } from "./facade/tracer.js";
|
|
15
15
|
import { recordHttpDuration } from "./facade/http.js";
|
|
16
|
+
import { histogram } from "./facade/meter.js";
|
|
16
17
|
import { reportError } from "./facade/report-error.js";
|
|
17
18
|
let initialized = false;
|
|
18
19
|
let activeProvider;
|
|
19
20
|
let disposeLoggerExtension;
|
|
20
21
|
let disposeRuntime;
|
|
21
22
|
const logger = createLogger("telemetry");
|
|
23
|
+
const OTLP_DEPENDENCY_UNAVAILABLE = /* @__PURE__ */ Symbol.for("open-mercato.telemetry.otlp-dependency-unavailable");
|
|
24
|
+
class OtlpDependencyUnavailableError extends Error {
|
|
25
|
+
constructor(backend, cause) {
|
|
26
|
+
super(
|
|
27
|
+
`[internal] OTLP telemetry backend "${backend}" cannot start because its OpenTelemetry runtime dependencies are unavailable. Install optional dependencies, set TELEMETRY_BACKEND=console, or unset TELEMETRY_BACKEND to disable telemetry.`,
|
|
28
|
+
{ cause }
|
|
29
|
+
);
|
|
30
|
+
this.name = "OtlpDependencyUnavailableError";
|
|
31
|
+
Object.defineProperty(this, OTLP_DEPENDENCY_UNAVAILABLE, { value: true });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
22
34
|
async function initTelemetry() {
|
|
23
35
|
if (initialized) return;
|
|
24
36
|
resetTelemetryEnvCache();
|
|
@@ -52,15 +64,13 @@ async function resolveProvider(backend) {
|
|
|
52
64
|
return void 0;
|
|
53
65
|
}
|
|
54
66
|
async function loadOtlpProvider(backend) {
|
|
67
|
+
let mod;
|
|
55
68
|
try {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
logger.warn("OTLP provider unavailable; falling back to console", {
|
|
60
|
-
reason: err instanceof Error ? err.message : String(err)
|
|
61
|
-
});
|
|
62
|
-
return new ConsoleProvider();
|
|
69
|
+
mod = await import("./provider/otlp-provider.js");
|
|
70
|
+
} catch (error) {
|
|
71
|
+
throw new OtlpDependencyUnavailableError(backend, error);
|
|
63
72
|
}
|
|
73
|
+
return new mod.OtlpProvider({}, backend);
|
|
64
74
|
}
|
|
65
75
|
async function shutdownTelemetry() {
|
|
66
76
|
const provider = activeProvider;
|
|
@@ -79,6 +89,7 @@ function createRuntime(provider) {
|
|
|
79
89
|
captureTraceContext,
|
|
80
90
|
continueTrace: (carrier, name, fn, options) => continueTrace(carrier, name, () => fn(), options),
|
|
81
91
|
withSpan: (name, fn, options) => withSpan(name, fn, options),
|
|
92
|
+
recordHistogram: histogram,
|
|
82
93
|
recordHttpDuration,
|
|
83
94
|
reportError,
|
|
84
95
|
shutdown: shutdownTelemetry
|
package/dist/init.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/init.ts"],
|
|
4
|
-
"sourcesContent": ["import type { TelemetryBackendName, TelemetryProvider } from './types'\nimport { isOtlpBackend, readTelemetryEnv, resetTelemetryEnvCache } from './env'\nimport { ConsoleProvider } from './provider/console-provider'\nimport {\n getRegisteredProvider,\n clearActiveProvider,\n setActiveProvider,\n} from './provider/registry'\nimport {\n registerTelemetryRuntime,\n type TelemetryRuntime,\n} from '@open-mercato/shared/lib/telemetry/runtime'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { registerTelemetryLogger } from './facade/logger-bridge'\nimport { captureTraceContext, continueTrace } from './facade/propagation'\nimport { withSpan } from './facade/tracer'\nimport { recordHttpDuration } from './facade/http'\nimport { reportError } from './facade/report-error'\n\nlet initialized = false\nlet activeProvider: TelemetryProvider | undefined\nlet disposeLoggerExtension: (() => void) | undefined\nlet disposeRuntime: (() => void) | undefined\n\nconst logger = createLogger('telemetry')\n\n/**\n * One-shot bootstrap, invoked from `apps/mercato/instrumentation.ts` (web) and,\n * for cross-boundary propagation, from the queue/worker bootstrap. Resolves the\n * backend from `TELEMETRY_BACKEND`, starts it, and installs it as the active\n * provider. Idempotent and safe to call when telemetry is off (defaults to\n * no-op).\n */\nexport async function initTelemetry(): Promise<void> {\n if (initialized) return\n\n // Drop any env snapshot memoized BEFORE the host loaded its `.env` (the CLI\n // binary imports this package before dotenv runs) so init resolves the backend\n // from the actual, fully-loaded environment.\n resetTelemetryEnvCache()\n const env = readTelemetryEnv()\n const configuredBackend = process.env.TELEMETRY_BACKEND?.trim().toLowerCase() ?? ''\n const registeredProvider = configuredBackend && configuredBackend !== 'noop'\n ? getRegisteredProvider(configuredBackend)\n : undefined\n // Explicit off is absolute: do not resolve providers, register hooks, or\n // install process-wide bridges when the backend is unset/noop/unregistered.\n if (!env.enabled && !registeredProvider) return\n\n const provider = registeredProvider ?? await resolveProvider(env.backend)\n if (!provider) {\n logger.warn('Telemetry backend is unsupported; telemetry remains disabled', {\n backend: configuredBackend || env.backend,\n })\n return\n }\n\n // Only flag initialized AFTER start() succeeds \u2014 a throw here must leave\n // telemetry re-initializable, not stuck flagged-on with an unstarted provider.\n await provider.start()\n setActiveProvider(provider)\n activeProvider = provider\n disposeLoggerExtension = registerTelemetryLogger(provider)\n disposeRuntime = registerTelemetryRuntime(createRuntime(provider))\n initialized = true\n\n logger.info('Telemetry initialized', {\n backend: provider.name,\n samplingRatio: env.samplingRatio,\n })\n}\n\nasync function resolveProvider(backend: TelemetryBackendName): Promise<TelemetryProvider | undefined> {\n const registered = getRegisteredProvider(backend)\n if (registered) return registered\n\n if (backend === 'console') return new ConsoleProvider()\n // signoz | newrelic | otlp \u2014 same OTLP provider, vendor differs only by endpoint.\n if (isOtlpBackend(backend)) return await loadOtlpProvider(backend)\n return undefined\n}\n\n/**\n * Dynamically load the OTLP provider so `@opentelemetry/*` (optionalDependencies)\n * is imported only when an OTLP backend is selected.
|
|
5
|
-
"mappings": "AACA,SAAS,eAAe,kBAAkB,8BAA8B;AACxE,SAAS,uBAAuB;AAChC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,OAEK;AACP,SAAS,oBAAoB;AAC7B,SAAS,+BAA+B;AACxC,SAAS,qBAAqB,qBAAqB;AACnD,SAAS,gBAAgB;AACzB,SAAS,0BAA0B;AACnC,SAAS,mBAAmB;AAE5B,IAAI,cAAc;AAClB,IAAI;AACJ,IAAI;AACJ,IAAI;AAEJ,MAAM,SAAS,aAAa,WAAW;
|
|
4
|
+
"sourcesContent": ["import type { TelemetryBackendName, TelemetryProvider } from './types'\nimport { isOtlpBackend, readTelemetryEnv, resetTelemetryEnvCache } from './env'\nimport { ConsoleProvider } from './provider/console-provider'\nimport {\n getRegisteredProvider,\n clearActiveProvider,\n setActiveProvider,\n} from './provider/registry'\nimport {\n registerTelemetryRuntime,\n type TelemetryRuntime,\n} from '@open-mercato/shared/lib/telemetry/runtime'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { registerTelemetryLogger } from './facade/logger-bridge'\nimport { captureTraceContext, continueTrace } from './facade/propagation'\nimport { withSpan } from './facade/tracer'\nimport { recordHttpDuration } from './facade/http'\nimport { histogram } from './facade/meter'\nimport { reportError } from './facade/report-error'\n\nlet initialized = false\nlet activeProvider: TelemetryProvider | undefined\nlet disposeLoggerExtension: (() => void) | undefined\nlet disposeRuntime: (() => void) | undefined\n\nconst logger = createLogger('telemetry')\nconst OTLP_DEPENDENCY_UNAVAILABLE = Symbol.for('open-mercato.telemetry.otlp-dependency-unavailable')\n\nclass OtlpDependencyUnavailableError extends Error {\n constructor(backend: TelemetryBackendName, cause: unknown) {\n super(\n `[internal] OTLP telemetry backend \"${backend}\" cannot start because its OpenTelemetry runtime dependencies are unavailable. Install optional dependencies, set TELEMETRY_BACKEND=console, or unset TELEMETRY_BACKEND to disable telemetry.`,\n { cause },\n )\n this.name = 'OtlpDependencyUnavailableError'\n Object.defineProperty(this, OTLP_DEPENDENCY_UNAVAILABLE, { value: true })\n }\n}\n\n/**\n * One-shot bootstrap, invoked from `apps/mercato/instrumentation.ts` (web) and,\n * for cross-boundary propagation, from the queue/worker bootstrap. Resolves the\n * backend from `TELEMETRY_BACKEND`, starts it, and installs it as the active\n * provider. Idempotent and safe to call when telemetry is off (defaults to\n * no-op).\n */\nexport async function initTelemetry(): Promise<void> {\n if (initialized) return\n\n // Drop any env snapshot memoized BEFORE the host loaded its `.env` (the CLI\n // binary imports this package before dotenv runs) so init resolves the backend\n // from the actual, fully-loaded environment.\n resetTelemetryEnvCache()\n const env = readTelemetryEnv()\n const configuredBackend = process.env.TELEMETRY_BACKEND?.trim().toLowerCase() ?? ''\n const registeredProvider = configuredBackend && configuredBackend !== 'noop'\n ? getRegisteredProvider(configuredBackend)\n : undefined\n // Explicit off is absolute: do not resolve providers, register hooks, or\n // install process-wide bridges when the backend is unset/noop/unregistered.\n if (!env.enabled && !registeredProvider) return\n\n const provider = registeredProvider ?? await resolveProvider(env.backend)\n if (!provider) {\n logger.warn('Telemetry backend is unsupported; telemetry remains disabled', {\n backend: configuredBackend || env.backend,\n })\n return\n }\n\n // Only flag initialized AFTER start() succeeds \u2014 a throw here must leave\n // telemetry re-initializable, not stuck flagged-on with an unstarted provider.\n await provider.start()\n setActiveProvider(provider)\n activeProvider = provider\n disposeLoggerExtension = registerTelemetryLogger(provider)\n disposeRuntime = registerTelemetryRuntime(createRuntime(provider))\n initialized = true\n\n logger.info('Telemetry initialized', {\n backend: provider.name,\n samplingRatio: env.samplingRatio,\n })\n}\n\nasync function resolveProvider(backend: TelemetryBackendName): Promise<TelemetryProvider | undefined> {\n const registered = getRegisteredProvider(backend)\n if (registered) return registered\n\n if (backend === 'console') return new ConsoleProvider()\n // signoz | newrelic | otlp \u2014 same OTLP provider, vendor differs only by endpoint.\n if (isOtlpBackend(backend)) return await loadOtlpProvider(backend)\n return undefined\n}\n\n/**\n * Dynamically load the OTLP provider so `@opentelemetry/*` (optionalDependencies)\n * is imported only when an OTLP backend is selected. An explicit OTLP selection\n * must fail at startup when those packages are unavailable instead of silently\n * switching telemetry semantics.\n */\nasync function loadOtlpProvider(backend: TelemetryBackendName): Promise<TelemetryProvider> {\n let mod: typeof import('./provider/otlp-provider')\n try {\n mod = await import('./provider/otlp-provider')\n } catch (error) {\n throw new OtlpDependencyUnavailableError(backend, error)\n }\n return new mod.OtlpProvider({}, backend)\n}\n\n/** Flush + tear down the active backend (shutdown hook / `after()`). */\nexport async function shutdownTelemetry(): Promise<void> {\n const provider = activeProvider\n activeProvider = undefined\n initialized = false\n disposeLoggerExtension?.()\n disposeLoggerExtension = undefined\n disposeRuntime?.()\n disposeRuntime = undefined\n clearActiveProvider()\n if (provider) await provider.shutdown()\n}\n\nfunction createRuntime(provider: TelemetryProvider): TelemetryRuntime {\n return {\n canUseGlobalTracePropagation: () =>\n isOtlpBackend(provider.name as TelemetryBackendName)\n && readTelemetryEnv().trustInboundTrace,\n captureTraceContext,\n continueTrace: (carrier, name, fn, options) =>\n continueTrace(carrier, name, () => fn(), options),\n withSpan: (name, fn, options) => withSpan(name, fn, options),\n recordHistogram: histogram,\n recordHttpDuration,\n reportError,\n shutdown: shutdownTelemetry,\n }\n}\n\n/** Test-only: allow re-init in jest. */\nexport function resetTelemetryInit(): void {\n initialized = false\n activeProvider = undefined\n disposeLoggerExtension?.()\n disposeLoggerExtension = undefined\n disposeRuntime?.()\n disposeRuntime = undefined\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/nextjs.js
CHANGED
|
@@ -3,6 +3,10 @@ import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
|
3
3
|
import { telemetryServerExternalPackages } from "./nextjs-config.js";
|
|
4
4
|
import { recordHttpDuration } from "./facade/http.js";
|
|
5
5
|
const logger = createLogger("telemetry");
|
|
6
|
+
const OTLP_DEPENDENCY_UNAVAILABLE = /* @__PURE__ */ Symbol.for("open-mercato.telemetry.otlp-dependency-unavailable");
|
|
7
|
+
function isOtlpDependencyUnavailableError(error) {
|
|
8
|
+
return error instanceof Error && Reflect.get(error, OTLP_DEPENDENCY_UNAVAILABLE) === true;
|
|
9
|
+
}
|
|
6
10
|
async function registerTelemetryForNextjs() {
|
|
7
11
|
if (process.env.NEXT_RUNTIME && process.env.NEXT_RUNTIME !== "nodejs") return;
|
|
8
12
|
if (!isTelemetryBackendEnabled()) return;
|
|
@@ -10,6 +14,7 @@ async function registerTelemetryForNextjs() {
|
|
|
10
14
|
try {
|
|
11
15
|
await initTelemetry();
|
|
12
16
|
} catch (error) {
|
|
17
|
+
if (isOtlpDependencyUnavailableError(error)) throw error;
|
|
13
18
|
logger.warn("Init from Next.js instrumentation failed", { err: error });
|
|
14
19
|
return;
|
|
15
20
|
}
|
package/dist/nextjs.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/nextjs.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Next.js wiring helpers for `@open-mercato/telemetry`.\n *\n * Runtime helpers for an enabled Next.js telemetry integration. Build-time\n * config belongs in `@open-mercato/telemetry/nextjs-config`, which has no\n * runtime imports.\n */\nimport { isTelemetryBackendEnabled } from '@open-mercato/shared/lib/telemetry/runtime'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nexport { telemetryServerExternalPackages } from './nextjs-config'\nexport { recordHttpDuration } from './facade/http'\n\nconst logger = createLogger('telemetry')\n\n/**\n * One-line telemetry bootstrap for a Next.js `instrumentation.ts`. Initializes\n * the active backend (no-op unless `TELEMETRY_BACKEND` is set),
|
|
5
|
-
"mappings": "AAOA,SAAS,iCAAiC;AAC1C,SAAS,oBAAoB;AAC7B,SAAS,uCAAuC;AAChD,SAAS,0BAA0B;AAEnC,MAAM,SAAS,aAAa,WAAW;
|
|
4
|
+
"sourcesContent": ["/**\n * Next.js wiring helpers for `@open-mercato/telemetry`.\n *\n * Runtime helpers for an enabled Next.js telemetry integration. Build-time\n * config belongs in `@open-mercato/telemetry/nextjs-config`, which has no\n * runtime imports.\n */\nimport { isTelemetryBackendEnabled } from '@open-mercato/shared/lib/telemetry/runtime'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nexport { telemetryServerExternalPackages } from './nextjs-config'\nexport { recordHttpDuration } from './facade/http'\n\nconst logger = createLogger('telemetry')\nconst OTLP_DEPENDENCY_UNAVAILABLE = Symbol.for('open-mercato.telemetry.otlp-dependency-unavailable')\n\nfunction isOtlpDependencyUnavailableError(error: unknown): boolean {\n return error instanceof Error && Reflect.get(error, OTLP_DEPENDENCY_UNAVAILABLE) === true\n}\n\n/**\n * One-line telemetry bootstrap for a Next.js `instrumentation.ts`. Initializes\n * the active backend (no-op unless `TELEMETRY_BACKEND` is set), propagates an\n * explicit OTLP missing-dependency failure, degrades to no telemetry for other\n * initialization failures, and registers a best-effort flush on\n * `SIGTERM`/`SIGINT`. Skips the edge runtime \u2014 the OTEL NodeSDK is Node-only \u2014\n * so callers may import it unconditionally, though gating the dynamic import on\n * `NEXT_RUNTIME === 'nodejs'` also keeps the SDK off the edge bundle.\n */\nexport async function registerTelemetryForNextjs(): Promise<void> {\n if (process.env.NEXT_RUNTIME && process.env.NEXT_RUNTIME !== 'nodejs') return\n if (!isTelemetryBackendEnabled()) return\n const { initTelemetry, shutdownTelemetry } = await import('./init')\n try {\n await initTelemetry()\n } catch (error) {\n if (isOtlpDependencyUnavailableError(error)) throw error\n logger.warn('Init from Next.js instrumentation failed', { err: error })\n return\n }\n // A signal listener suppresses Node's default termination, so after the\n // best-effort flush the handler re-raises the signal. `once` has already\n // removed this listener by then, so the re-raise falls through to the default\n // terminate (or to another component's own handler, e.g. Next's).\n const flush = (signal: NodeJS.Signals) => {\n void shutdownTelemetry()\n .catch(() => {})\n .finally(() => {\n process.kill(process.pid, signal)\n })\n }\n process.once('SIGTERM', flush)\n process.once('SIGINT', flush)\n}\n"],
|
|
5
|
+
"mappings": "AAOA,SAAS,iCAAiC;AAC1C,SAAS,oBAAoB;AAC7B,SAAS,uCAAuC;AAChD,SAAS,0BAA0B;AAEnC,MAAM,SAAS,aAAa,WAAW;AACvC,MAAM,8BAA8B,uBAAO,IAAI,oDAAoD;AAEnG,SAAS,iCAAiC,OAAyB;AACjE,SAAO,iBAAiB,SAAS,QAAQ,IAAI,OAAO,2BAA2B,MAAM;AACvF;AAWA,eAAsB,6BAA4C;AAChE,MAAI,QAAQ,IAAI,gBAAgB,QAAQ,IAAI,iBAAiB,SAAU;AACvE,MAAI,CAAC,0BAA0B,EAAG;AAClC,QAAM,EAAE,eAAe,kBAAkB,IAAI,MAAM,OAAO,QAAQ;AAClE,MAAI;AACF,UAAM,cAAc;AAAA,EACtB,SAAS,OAAO;AACd,QAAI,iCAAiC,KAAK,EAAG,OAAM;AACnD,WAAO,KAAK,4CAA4C,EAAE,KAAK,MAAM,CAAC;AACtE;AAAA,EACF;AAKA,QAAM,QAAQ,CAAC,WAA2B;AACxC,SAAK,kBAAkB,EACpB,MAAM,MAAM;AAAA,IAAC,CAAC,EACd,QAAQ,MAAM;AACb,cAAQ,KAAK,QAAQ,KAAK,MAAM;AAAA,IAClC,CAAC;AAAA,EACL;AACA,UAAQ,KAAK,WAAW,KAAK;AAC7B,UAAQ,KAAK,UAAU,KAAK;AAC9B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/telemetry",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Vendor-neutral spans, metrics, and error reporting backed by a pluggable OTLP provider. Off by default; the telemetry runtime is loaded only when explicitly enabled.",
|
|
6
6
|
"type": "module",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@open-mercato/shared": "0.
|
|
41
|
+
"@open-mercato/shared": "0.9.0"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"react": ">=18.0.0"
|
|
@@ -89,6 +89,5 @@
|
|
|
89
89
|
"type": "git",
|
|
90
90
|
"url": "https://github.com/open-mercato/open-mercato",
|
|
91
91
|
"directory": "packages/telemetry"
|
|
92
|
-
}
|
|
93
|
-
"stableVersion": "0.8.0"
|
|
92
|
+
}
|
|
94
93
|
}
|
|
@@ -25,7 +25,7 @@ function provider(name = 'noop'): TelemetryProvider {
|
|
|
25
25
|
inject: () => {},
|
|
26
26
|
runInRemoteSpan: (_carrier, _name, _options, fn) => fn(NOOP_SPAN),
|
|
27
27
|
emitLog: () => {},
|
|
28
|
-
recordMetric: ()
|
|
28
|
+
recordMetric: jest.fn(),
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -77,4 +77,26 @@ describe('telemetry explicit opt-in boundary', () => {
|
|
|
77
77
|
expect(getLoggerExtension()).toBeDefined()
|
|
78
78
|
expect(getTelemetryRuntime()).toBeDefined()
|
|
79
79
|
})
|
|
80
|
+
|
|
81
|
+
it('wires shared runtime histograms to the active provider', async () => {
|
|
82
|
+
const customConsole = provider('console')
|
|
83
|
+
registerProvider(customConsole)
|
|
84
|
+
process.env.TELEMETRY_BACKEND = 'console'
|
|
85
|
+
|
|
86
|
+
await initTelemetry()
|
|
87
|
+
getTelemetryRuntime()?.recordHistogram?.(
|
|
88
|
+
'om.enricher.duration',
|
|
89
|
+
0.25,
|
|
90
|
+
{ 'enricher.id': 'customers.people' },
|
|
91
|
+
's',
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
expect(customConsole.recordMetric).toHaveBeenCalledWith({
|
|
95
|
+
kind: 'histogram',
|
|
96
|
+
name: 'om.enricher.duration',
|
|
97
|
+
value: 0.25,
|
|
98
|
+
labels: { 'enricher.id': 'customers.people' },
|
|
99
|
+
unit: 's',
|
|
100
|
+
})
|
|
101
|
+
})
|
|
80
102
|
})
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Attributes,
|
|
3
|
+
LogRecord,
|
|
4
|
+
MetricPoint,
|
|
5
|
+
Span,
|
|
6
|
+
SpanOptions,
|
|
7
|
+
TelemetryProvider,
|
|
8
|
+
TraceCarrier,
|
|
9
|
+
TraceContext,
|
|
10
|
+
} from '../types'
|
|
11
|
+
|
|
12
|
+
function createSpan(): Span {
|
|
13
|
+
return {
|
|
14
|
+
setAttribute(_key: string, _value: string | number | boolean): void {},
|
|
15
|
+
setAttributes(_attributes: Attributes): void {},
|
|
16
|
+
recordException(_error: unknown): void {},
|
|
17
|
+
setStatus(_status: 'ok' | 'error', _message?: string): void {},
|
|
18
|
+
end(): void {},
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function createProvider(name = 'otlp'): TelemetryProvider {
|
|
23
|
+
const span = createSpan()
|
|
24
|
+
return {
|
|
25
|
+
name,
|
|
26
|
+
supports: ['traces', 'metrics', 'logs', 'errors'],
|
|
27
|
+
start: jest.fn(async () => {}),
|
|
28
|
+
shutdown: jest.fn(async () => {}),
|
|
29
|
+
runInSpan<T>(_name: string, _options: SpanOptions, operation: (activeSpan: Span) => T): T {
|
|
30
|
+
return operation(span)
|
|
31
|
+
},
|
|
32
|
+
activeSpan(): Span | undefined {
|
|
33
|
+
return span
|
|
34
|
+
},
|
|
35
|
+
activeTraceContext(): TraceContext | undefined {
|
|
36
|
+
return undefined
|
|
37
|
+
},
|
|
38
|
+
inject(_carrier: TraceCarrier): void {},
|
|
39
|
+
runInRemoteSpan<T>(
|
|
40
|
+
_carrier: TraceCarrier,
|
|
41
|
+
_name: string,
|
|
42
|
+
_options: SpanOptions,
|
|
43
|
+
operation: (activeSpan: Span) => T,
|
|
44
|
+
): T {
|
|
45
|
+
return operation(span)
|
|
46
|
+
},
|
|
47
|
+
emitLog(_record: LogRecord): void {},
|
|
48
|
+
recordMetric(_point: MetricPoint): void {},
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('OTLP dependency bootstrap', () => {
|
|
53
|
+
const originalBackend = process.env.TELEMETRY_BACKEND
|
|
54
|
+
let shutdownTelemetry: (() => Promise<void>) | undefined
|
|
55
|
+
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
jest.resetModules()
|
|
58
|
+
jest.restoreAllMocks()
|
|
59
|
+
shutdownTelemetry = undefined
|
|
60
|
+
delete process.env.TELEMETRY_BACKEND
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
afterEach(async () => {
|
|
64
|
+
await shutdownTelemetry?.()
|
|
65
|
+
if (originalBackend === undefined) delete process.env.TELEMETRY_BACKEND
|
|
66
|
+
else process.env.TELEMETRY_BACKEND = originalBackend
|
|
67
|
+
jest.dontMock('../provider/console-provider')
|
|
68
|
+
jest.dontMock('../provider/otlp-provider')
|
|
69
|
+
jest.dontMock('../init')
|
|
70
|
+
jest.resetModules()
|
|
71
|
+
jest.restoreAllMocks()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it.each(['otlp', 'signoz', 'newrelic'])('fails clearly when %s dependencies cannot load', async (backend) => {
|
|
75
|
+
const importCause = new Error('simulated missing package at /private/runtime with token=secret-value')
|
|
76
|
+
const consoleProvider = jest.fn()
|
|
77
|
+
jest.doMock('../provider/console-provider', () => ({ ConsoleProvider: consoleProvider }))
|
|
78
|
+
jest.doMock('../provider/otlp-provider', () => {
|
|
79
|
+
throw importCause
|
|
80
|
+
})
|
|
81
|
+
const telemetry = await import('../init')
|
|
82
|
+
shutdownTelemetry = telemetry.shutdownTelemetry
|
|
83
|
+
process.env.TELEMETRY_BACKEND = backend
|
|
84
|
+
|
|
85
|
+
const failure: unknown = await telemetry.initTelemetry().catch((error: unknown) => error)
|
|
86
|
+
|
|
87
|
+
expect(failure).toBeInstanceOf(Error)
|
|
88
|
+
expect((failure as Error).message).toContain(`OTLP telemetry backend "${backend}" cannot start`)
|
|
89
|
+
expect((failure as Error).message).toContain('Install optional dependencies')
|
|
90
|
+
expect((failure as Error).message).not.toContain('/private/runtime')
|
|
91
|
+
expect((failure as Error).message).not.toContain('secret-value')
|
|
92
|
+
expect((failure as Error).cause).toBe(importCause)
|
|
93
|
+
expect(consoleProvider).not.toHaveBeenCalled()
|
|
94
|
+
const [{ getActiveProvider }, { getLoggerExtension }, { getTelemetryRuntime }] = await Promise.all([
|
|
95
|
+
import('../provider/registry'),
|
|
96
|
+
import('@open-mercato/shared/lib/logger'),
|
|
97
|
+
import('@open-mercato/shared/lib/telemetry/runtime'),
|
|
98
|
+
])
|
|
99
|
+
expect(getActiveProvider().name).toBe('noop')
|
|
100
|
+
expect(getLoggerExtension()).toBeUndefined()
|
|
101
|
+
expect(getTelemetryRuntime()).toBeUndefined()
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('starts the dynamically loaded provider when OTLP dependencies are available', async () => {
|
|
105
|
+
const provider = createProvider()
|
|
106
|
+
const OtlpProvider = jest.fn(() => provider)
|
|
107
|
+
jest.doMock('../provider/otlp-provider', () => ({ OtlpProvider }))
|
|
108
|
+
const telemetry = await import('../init')
|
|
109
|
+
shutdownTelemetry = telemetry.shutdownTelemetry
|
|
110
|
+
process.env.TELEMETRY_BACKEND = 'otlp'
|
|
111
|
+
|
|
112
|
+
await telemetry.initTelemetry()
|
|
113
|
+
|
|
114
|
+
expect(OtlpProvider).toHaveBeenCalledWith({}, 'otlp')
|
|
115
|
+
expect(provider.start).toHaveBeenCalledTimes(1)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it.each([undefined, 'noop'])('does not resolve OTLP dependencies when telemetry is %s', async (backend) => {
|
|
119
|
+
const loadOtlpModule = jest.fn(() => ({ OtlpProvider: jest.fn() }))
|
|
120
|
+
jest.doMock('../provider/otlp-provider', loadOtlpModule)
|
|
121
|
+
const telemetry = await import('../init')
|
|
122
|
+
shutdownTelemetry = telemetry.shutdownTelemetry
|
|
123
|
+
if (backend === undefined) delete process.env.TELEMETRY_BACKEND
|
|
124
|
+
else process.env.TELEMETRY_BACKEND = backend
|
|
125
|
+
|
|
126
|
+
await telemetry.initTelemetry()
|
|
127
|
+
|
|
128
|
+
expect(loadOtlpModule).not.toHaveBeenCalled()
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('can retry initialization after a dependency load failure', async () => {
|
|
132
|
+
const provider = createProvider()
|
|
133
|
+
const importCause = new Error('simulated first-load failure')
|
|
134
|
+
let loadAttempts = 0
|
|
135
|
+
jest.doMock('../provider/otlp-provider', () => {
|
|
136
|
+
loadAttempts += 1
|
|
137
|
+
if (loadAttempts === 1) throw importCause
|
|
138
|
+
return { OtlpProvider: jest.fn(() => provider) }
|
|
139
|
+
})
|
|
140
|
+
const telemetry = await import('../init')
|
|
141
|
+
shutdownTelemetry = telemetry.shutdownTelemetry
|
|
142
|
+
process.env.TELEMETRY_BACKEND = 'otlp'
|
|
143
|
+
|
|
144
|
+
await expect(telemetry.initTelemetry()).rejects.toHaveProperty('cause', importCause)
|
|
145
|
+
await telemetry.initTelemetry()
|
|
146
|
+
|
|
147
|
+
expect(loadAttempts).toBe(2)
|
|
148
|
+
expect(provider.start).toHaveBeenCalledTimes(1)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('propagates a dependency load failure through the standard Next.js bootstrap', async () => {
|
|
152
|
+
const importCause = new Error('simulated missing package')
|
|
153
|
+
jest.doMock('../provider/otlp-provider', () => {
|
|
154
|
+
throw importCause
|
|
155
|
+
})
|
|
156
|
+
const telemetry = await import('../init')
|
|
157
|
+
shutdownTelemetry = telemetry.shutdownTelemetry
|
|
158
|
+
const { registerTelemetryForNextjs } = await import('../nextjs')
|
|
159
|
+
process.env.TELEMETRY_BACKEND = 'otlp'
|
|
160
|
+
|
|
161
|
+
await expect(registerTelemetryForNextjs()).rejects.toMatchObject({
|
|
162
|
+
name: 'OtlpDependencyUnavailableError',
|
|
163
|
+
cause: importCause,
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('keeps unrelated provider initialization failures best effort in Next.js', async () => {
|
|
168
|
+
const startupFailure = new Error('collector configuration rejected')
|
|
169
|
+
const initTelemetry = jest.fn(async () => {
|
|
170
|
+
throw startupFailure
|
|
171
|
+
})
|
|
172
|
+
jest.doMock('../init', () => ({
|
|
173
|
+
initTelemetry,
|
|
174
|
+
shutdownTelemetry: jest.fn(async () => {}),
|
|
175
|
+
}))
|
|
176
|
+
const { registerTelemetryForNextjs } = await import('../nextjs')
|
|
177
|
+
process.env.TELEMETRY_BACKEND = 'otlp'
|
|
178
|
+
|
|
179
|
+
await expect(registerTelemetryForNextjs()).resolves.toBeUndefined()
|
|
180
|
+
expect(initTelemetry).toHaveBeenCalledTimes(1)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('preserves a provider startup failure and can retry it without misclassifying dependencies', async () => {
|
|
184
|
+
const provider = createProvider()
|
|
185
|
+
const startupFailure = new Error('collector configuration rejected')
|
|
186
|
+
jest.mocked(provider.start)
|
|
187
|
+
.mockRejectedValueOnce(startupFailure)
|
|
188
|
+
.mockResolvedValueOnce(undefined)
|
|
189
|
+
jest.doMock('../provider/otlp-provider', () => ({
|
|
190
|
+
OtlpProvider: jest.fn(() => provider),
|
|
191
|
+
}))
|
|
192
|
+
const telemetry = await import('../init')
|
|
193
|
+
shutdownTelemetry = telemetry.shutdownTelemetry
|
|
194
|
+
process.env.TELEMETRY_BACKEND = 'otlp'
|
|
195
|
+
|
|
196
|
+
await expect(telemetry.initTelemetry()).rejects.toBe(startupFailure)
|
|
197
|
+
const [{ getActiveProvider }, { getTelemetryRuntime }] = await Promise.all([
|
|
198
|
+
import('../provider/registry'),
|
|
199
|
+
import('@open-mercato/shared/lib/telemetry/runtime'),
|
|
200
|
+
])
|
|
201
|
+
expect(getActiveProvider().name).toBe('noop')
|
|
202
|
+
expect(getTelemetryRuntime()).toBeUndefined()
|
|
203
|
+
|
|
204
|
+
await telemetry.initTelemetry()
|
|
205
|
+
|
|
206
|
+
expect(provider.start).toHaveBeenCalledTimes(2)
|
|
207
|
+
})
|
|
208
|
+
})
|
package/src/init.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { registerTelemetryLogger } from './facade/logger-bridge'
|
|
|
15
15
|
import { captureTraceContext, continueTrace } from './facade/propagation'
|
|
16
16
|
import { withSpan } from './facade/tracer'
|
|
17
17
|
import { recordHttpDuration } from './facade/http'
|
|
18
|
+
import { histogram } from './facade/meter'
|
|
18
19
|
import { reportError } from './facade/report-error'
|
|
19
20
|
|
|
20
21
|
let initialized = false
|
|
@@ -23,6 +24,18 @@ let disposeLoggerExtension: (() => void) | undefined
|
|
|
23
24
|
let disposeRuntime: (() => void) | undefined
|
|
24
25
|
|
|
25
26
|
const logger = createLogger('telemetry')
|
|
27
|
+
const OTLP_DEPENDENCY_UNAVAILABLE = Symbol.for('open-mercato.telemetry.otlp-dependency-unavailable')
|
|
28
|
+
|
|
29
|
+
class OtlpDependencyUnavailableError extends Error {
|
|
30
|
+
constructor(backend: TelemetryBackendName, cause: unknown) {
|
|
31
|
+
super(
|
|
32
|
+
`[internal] OTLP telemetry backend "${backend}" cannot start because its OpenTelemetry runtime dependencies are unavailable. Install optional dependencies, set TELEMETRY_BACKEND=console, or unset TELEMETRY_BACKEND to disable telemetry.`,
|
|
33
|
+
{ cause },
|
|
34
|
+
)
|
|
35
|
+
this.name = 'OtlpDependencyUnavailableError'
|
|
36
|
+
Object.defineProperty(this, OTLP_DEPENDENCY_UNAVAILABLE, { value: true })
|
|
37
|
+
}
|
|
38
|
+
}
|
|
26
39
|
|
|
27
40
|
/**
|
|
28
41
|
* One-shot bootstrap, invoked from `apps/mercato/instrumentation.ts` (web) and,
|
|
@@ -82,19 +95,18 @@ async function resolveProvider(backend: TelemetryBackendName): Promise<Telemetry
|
|
|
82
95
|
|
|
83
96
|
/**
|
|
84
97
|
* Dynamically load the OTLP provider so `@opentelemetry/*` (optionalDependencies)
|
|
85
|
-
* is imported only when an OTLP backend is selected.
|
|
86
|
-
*
|
|
98
|
+
* is imported only when an OTLP backend is selected. An explicit OTLP selection
|
|
99
|
+
* must fail at startup when those packages are unavailable instead of silently
|
|
100
|
+
* switching telemetry semantics.
|
|
87
101
|
*/
|
|
88
102
|
async function loadOtlpProvider(backend: TelemetryBackendName): Promise<TelemetryProvider> {
|
|
103
|
+
let mod: typeof import('./provider/otlp-provider')
|
|
89
104
|
try {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
logger.warn('OTLP provider unavailable; falling back to console', {
|
|
94
|
-
reason: err instanceof Error ? err.message : String(err),
|
|
95
|
-
})
|
|
96
|
-
return new ConsoleProvider()
|
|
105
|
+
mod = await import('./provider/otlp-provider')
|
|
106
|
+
} catch (error) {
|
|
107
|
+
throw new OtlpDependencyUnavailableError(backend, error)
|
|
97
108
|
}
|
|
109
|
+
return new mod.OtlpProvider({}, backend)
|
|
98
110
|
}
|
|
99
111
|
|
|
100
112
|
/** Flush + tear down the active backend (shutdown hook / `after()`). */
|
|
@@ -119,6 +131,7 @@ function createRuntime(provider: TelemetryProvider): TelemetryRuntime {
|
|
|
119
131
|
continueTrace: (carrier, name, fn, options) =>
|
|
120
132
|
continueTrace(carrier, name, () => fn(), options),
|
|
121
133
|
withSpan: (name, fn, options) => withSpan(name, fn, options),
|
|
134
|
+
recordHistogram: histogram,
|
|
122
135
|
recordHttpDuration,
|
|
123
136
|
reportError,
|
|
124
137
|
shutdown: shutdownTelemetry,
|
package/src/nextjs.ts
CHANGED
|
@@ -11,14 +11,19 @@ export { telemetryServerExternalPackages } from './nextjs-config'
|
|
|
11
11
|
export { recordHttpDuration } from './facade/http'
|
|
12
12
|
|
|
13
13
|
const logger = createLogger('telemetry')
|
|
14
|
+
const OTLP_DEPENDENCY_UNAVAILABLE = Symbol.for('open-mercato.telemetry.otlp-dependency-unavailable')
|
|
15
|
+
|
|
16
|
+
function isOtlpDependencyUnavailableError(error: unknown): boolean {
|
|
17
|
+
return error instanceof Error && Reflect.get(error, OTLP_DEPENDENCY_UNAVAILABLE) === true
|
|
18
|
+
}
|
|
14
19
|
|
|
15
20
|
/**
|
|
16
21
|
* One-line telemetry bootstrap for a Next.js `instrumentation.ts`. Initializes
|
|
17
|
-
* the active backend (no-op unless `TELEMETRY_BACKEND` is set),
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* the edge runtime — the OTEL NodeSDK is Node-only —
|
|
21
|
-
* unconditionally, though gating the dynamic import on
|
|
22
|
+
* the active backend (no-op unless `TELEMETRY_BACKEND` is set), propagates an
|
|
23
|
+
* explicit OTLP missing-dependency failure, degrades to no telemetry for other
|
|
24
|
+
* initialization failures, and registers a best-effort flush on
|
|
25
|
+
* `SIGTERM`/`SIGINT`. Skips the edge runtime — the OTEL NodeSDK is Node-only —
|
|
26
|
+
* so callers may import it unconditionally, though gating the dynamic import on
|
|
22
27
|
* `NEXT_RUNTIME === 'nodejs'` also keeps the SDK off the edge bundle.
|
|
23
28
|
*/
|
|
24
29
|
export async function registerTelemetryForNextjs(): Promise<void> {
|
|
@@ -28,6 +33,7 @@ export async function registerTelemetryForNextjs(): Promise<void> {
|
|
|
28
33
|
try {
|
|
29
34
|
await initTelemetry()
|
|
30
35
|
} catch (error) {
|
|
36
|
+
if (isOtlpDependencyUnavailableError(error)) throw error
|
|
31
37
|
logger.warn('Init from Next.js instrumentation failed', { err: error })
|
|
32
38
|
return
|
|
33
39
|
}
|