@open-mercato/telemetry 0.6.7-develop.6834.1.e76f4b8cbc

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.
Files changed (82) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/AGENTS.md +71 -0
  3. package/README.md +164 -0
  4. package/build.mjs +10 -0
  5. package/dist/env.js +44 -0
  6. package/dist/env.js.map +7 -0
  7. package/dist/facade/context.js +14 -0
  8. package/dist/facade/context.js.map +7 -0
  9. package/dist/facade/http.js +18 -0
  10. package/dist/facade/http.js.map +7 -0
  11. package/dist/facade/logger-bridge.js +40 -0
  12. package/dist/facade/logger-bridge.js.map +7 -0
  13. package/dist/facade/meter.js +16 -0
  14. package/dist/facade/meter.js.map +7 -0
  15. package/dist/facade/propagation.js +20 -0
  16. package/dist/facade/propagation.js.map +7 -0
  17. package/dist/facade/redact.js +21 -0
  18. package/dist/facade/redact.js.map +7 -0
  19. package/dist/facade/report-error.js +26 -0
  20. package/dist/facade/report-error.js.map +7 -0
  21. package/dist/facade/serialize.js +35 -0
  22. package/dist/facade/serialize.js.map +7 -0
  23. package/dist/facade/tracer.js +21 -0
  24. package/dist/facade/tracer.js.map +7 -0
  25. package/dist/index.js +21 -0
  26. package/dist/index.js.map +7 -0
  27. package/dist/init.js +98 -0
  28. package/dist/init.js.map +7 -0
  29. package/dist/nextjs-config.js +21 -0
  30. package/dist/nextjs-config.js.map +7 -0
  31. package/dist/nextjs.js +30 -0
  32. package/dist/nextjs.js.map +7 -0
  33. package/dist/provider/console-provider.js +70 -0
  34. package/dist/provider/console-provider.js.map +7 -0
  35. package/dist/provider/noop-provider.js +45 -0
  36. package/dist/provider/noop-provider.js.map +7 -0
  37. package/dist/provider/otlp-provider.js +212 -0
  38. package/dist/provider/otlp-provider.js.map +7 -0
  39. package/dist/provider/registry.js +40 -0
  40. package/dist/provider/registry.js.map +7 -0
  41. package/dist/provider/run-span.js +34 -0
  42. package/dist/provider/run-span.js.map +7 -0
  43. package/dist/types.js +1 -0
  44. package/dist/types.js.map +7 -0
  45. package/jest.config.cjs +27 -0
  46. package/package.json +68 -0
  47. package/src/__tests__/default-unloaded.test.ts +58 -0
  48. package/src/__tests__/env-load-order.test.ts +80 -0
  49. package/src/__tests__/env.test.ts +72 -0
  50. package/src/__tests__/logger-level.test.ts +64 -0
  51. package/src/__tests__/nextjs-shutdown.test.ts +71 -0
  52. package/src/__tests__/nextjs.test.ts +122 -0
  53. package/src/__tests__/otlp-integration.test.ts +264 -0
  54. package/src/__tests__/pg-instrumentation.test.ts +88 -0
  55. package/src/__tests__/pg-pii-config.test.ts +14 -0
  56. package/src/__tests__/redact.test.ts +92 -0
  57. package/src/__tests__/registry.test.ts +77 -0
  58. package/src/__tests__/run-span.test.ts +56 -0
  59. package/src/__tests__/serialize.test.ts +47 -0
  60. package/src/__tests__/telemetry.test.ts +172 -0
  61. package/src/env.ts +71 -0
  62. package/src/facade/context.ts +17 -0
  63. package/src/facade/http.ts +25 -0
  64. package/src/facade/logger-bridge.ts +50 -0
  65. package/src/facade/meter.ts +20 -0
  66. package/src/facade/propagation.ts +31 -0
  67. package/src/facade/redact.ts +54 -0
  68. package/src/facade/report-error.ts +41 -0
  69. package/src/facade/serialize.ts +46 -0
  70. package/src/facade/tracer.ts +25 -0
  71. package/src/index.ts +30 -0
  72. package/src/init.ts +134 -0
  73. package/src/nextjs-config.ts +22 -0
  74. package/src/nextjs.ts +47 -0
  75. package/src/provider/console-provider.ts +87 -0
  76. package/src/provider/noop-provider.ts +53 -0
  77. package/src/provider/otlp-provider.ts +316 -0
  78. package/src/provider/registry.ts +60 -0
  79. package/src/provider/run-span.ts +45 -0
  80. package/src/types.ts +108 -0
  81. package/tsconfig.json +9 -0
  82. package/watch.mjs +6 -0
@@ -0,0 +1,2 @@
1
+ [build:telemetry] found 20 entry points
2
+ [build:telemetry] built successfully
package/AGENTS.md ADDED
@@ -0,0 +1,71 @@
1
+ # Telemetry Package — Agent Rules
2
+
3
+ `@open-mercato/telemetry` supplies vendor-neutral spans, metrics, error
4
+ reporting, and an optional remote sink for the canonical shared logger. It is
5
+ off by default. Spec:
6
+ `.ai/specs/2026-04-29-telemetry-and-otel.md`.
7
+
8
+ ## Always
9
+
10
+ - Use `createLogger(namespace)` from
11
+ `@open-mercato/shared/lib/logger` for operational logging. Telemetry must
12
+ extend that logger, never introduce another logger or stdout/stderr path.
13
+ - Keep host integration default-unloaded: check
14
+ `isTelemetryBackendEnabled()` from shared code before dynamically importing
15
+ this package.
16
+ - Treat an unset, `noop`, or unregistered backend as absolute off. A custom
17
+ provider activates only after an explicit bootstrap registers the exact
18
+ configured name and calls `initTelemetry()`.
19
+ - Name spans `module.entity.action` (lowercase, dot-separated).
20
+ - Use semantic-convention metric/attribute names when available.
21
+ - Keep metric labels low-cardinality. Tenant, organization, and user IDs belong
22
+ on span attributes, never metric labels.
23
+ - Apply redaction at the provider boundary as well as at facade call sites.
24
+
25
+ ## Ask First
26
+
27
+ - Ask before adding a built-in metric, auto-instrumentation, production
28
+ dependency, or new global hook.
29
+ - OpenTelemetry packages must stay optional and may only be imported by
30
+ `provider/otlp-provider.ts`.
31
+ - Ask before changing the `pg` `enhancedDatabaseReporting: false` guard or
32
+ broadening the accepted inbound-trace trust model.
33
+
34
+ ## Never
35
+
36
+ - Never emit PII, credentials, record content, SQL parameters, request bodies,
37
+ or arbitrary thrown-object properties.
38
+ - Never trust `traceparent` or `x-original-traceparent` at an inbound/global
39
+ boundary unless `TELEMETRY_TRUST_INBOUND_TRACE=true`.
40
+ - Never store provider, shared-logger extension, or runtime bridge state only in
41
+ a module local; cross-bundle state uses `globalThis` symbol registries.
42
+ - Never replace provider-owned span delegation with a finished-span sink.
43
+ - Never import this package from a client component.
44
+
45
+ ## Architecture
46
+
47
+ ```
48
+ @open-mercato/shared/lib/logger ── local output (always)
49
+ │
50
+ └─ process-wide extension (only after telemetry init) ── remote logs
51
+
52
+ host/queue shared runtime bridge ── absent while off
53
+ │
54
+ └─ registered provider: console | OTLP
55
+ ```
56
+
57
+ - `src/facade/*`: spans, metrics, propagation, error funnel, redaction, and
58
+ shared-logger adapter.
59
+ - `src/provider/*`: noop/console/OTLP providers and global provider registry.
60
+ - `src/init.ts`: explicit-enabled initialization and process-wide bridge
61
+ registration.
62
+ - `src/nextjs-config.ts`: build-time constants only; no runtime imports.
63
+ - `src/nextjs.ts`: enabled runtime helper.
64
+
65
+ ## Validation
66
+
67
+ ```bash
68
+ yarn workspace @open-mercato/telemetry build
69
+ yarn workspace @open-mercato/telemetry test
70
+ yarn typecheck
71
+ ```
package/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # @open-mercato/telemetry
2
+
3
+ Vendor-neutral tracing, metrics, log export, and error reporting for Open
4
+ Mercato, backed by a pluggable provider with OpenTelemetry (OTLP) as the default
5
+ transport.
6
+
7
+ Telemetry is explicitly opt-in. The platform checks `TELEMETRY_BACKEND` from
8
+ shared code before importing this package, so the telemetry runtime is not
9
+ evaluated when the variable is unset, `noop`, or unknown. OpenTelemetry packages
10
+ are optional dependencies and are dynamically imported only for an OTLP backend.
11
+ Package managers may still install optional dependencies; the guarantee is zero
12
+ disabled-path runtime loading, hook registration, or export traffic.
13
+
14
+ See [the telemetry spec](../../.ai/specs/2026-04-29-telemetry-and-otel.md).
15
+
16
+ ## Usage
17
+
18
+ The canonical application logger remains
19
+ `@open-mercato/shared/lib/logger`. Telemetry extends it after successful
20
+ initialization with trace correlation and one remote sink; it does not create a
21
+ second logger or local output path.
22
+
23
+ ```ts
24
+ import { createLogger } from '@open-mercato/shared/lib/logger'
25
+ import { withSpan, counter, reportError } from '@open-mercato/telemetry'
26
+
27
+ const log = createLogger('orders').child({ module: 'sales' })
28
+ log.info('Order placed', { orderId })
29
+
30
+ await withSpan('orders.checkout', async (span) => {
31
+ span.setAttribute('om.tenant_id', tenantId)
32
+ // pg/undici auto-spans nest here when OTLP is enabled
33
+ })
34
+
35
+ counter('om.errors', 1, { module: 'orders' })
36
+
37
+ try {
38
+ // ...
39
+ } catch (error) {
40
+ reportError(error, { module: 'orders' })
41
+ throw error
42
+ }
43
+ ```
44
+
45
+ Use `OM_LOG_LEVEL`, `OM_LOG_PRETTY`, and `OM_LOG_DESTINATION` for both the
46
+ normal local logger and telemetry-backed log export. Remote records follow the
47
+ same shared level gate as local records.
48
+
49
+ ## Enabling
50
+
51
+ Leave `TELEMETRY_BACKEND` unset (or set it to `noop`) to keep telemetry off.
52
+ Unknown values also resolve to off.
53
+
54
+ ```dotenv
55
+ TELEMETRY_BACKEND=otlp
56
+ TELEMETRY_SAMPLING_RATIO=0.1
57
+ OTEL_EXPORTER_OTLP_ENDPOINT=https://<your-collector>:4318
58
+ OTEL_EXPORTER_OTLP_HEADERS=<auth-header>=<key>
59
+ OTEL_SERVICE_NAME=open-mercato
60
+ OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production
61
+ ```
62
+
63
+ `otlp`, `signoz`, and `newrelic` select the same OTLP provider and differ
64
+ only by endpoint and headers. `console` is an explicit local span/metric
65
+ diagnostic backend.
66
+
67
+ `TELEMETRY_TRUST_INBOUND_TRACE` defaults to false. In that mode both
68
+ `traceparent` and `x-original-traceparent` from inbound/global carriers are
69
+ ignored because either header is caller-controlled. Set it to true only behind a
70
+ trusted upstream when global W3C continuation is required. The queue package's
71
+ dedicated `metadata._trace` carrier does not require this flag. The richer
72
+ `bullmq-otel` queue spans do use the process-global propagator, so they stay
73
+ disabled while this flag is false; async queues then use the dedicated carrier.
74
+
75
+ Custom providers remain supported through `registerProvider()`. Because the
76
+ default host guard deliberately imports telemetry only for built-in backend
77
+ names, a custom-provider bootstrap must import `@open-mercato/telemetry`,
78
+ register a provider whose `name` matches `TELEMETRY_BACKEND`, and call
79
+ `initTelemetry()` directly. Unregistered names remain a hard no-op.
80
+
81
+ ## Existing apps
82
+
83
+ Fresh create-app scaffolds are already wired. Older apps can run:
84
+
85
+ ```bash
86
+ yarn mercato telemetry init
87
+ ```
88
+
89
+ The command is idempotent and supports `--dry-run`. The equivalent manual
90
+ wiring is:
91
+
92
+ ```ts
93
+ // src/instrumentation.ts
94
+ import { isTelemetryBackendEnabled } from '@open-mercato/shared/lib/telemetry/runtime'
95
+
96
+ export async function register(): Promise<void> {
97
+ if (process.env.NEXT_RUNTIME === 'nodejs' && isTelemetryBackendEnabled()) {
98
+ const { registerTelemetryForNextjs } =
99
+ await import('@open-mercato/telemetry/nextjs')
100
+ await registerTelemetryForNextjs()
101
+ }
102
+ }
103
+ ```
104
+
105
+ ```ts
106
+ // next.config.ts — config-only entrypoint, no telemetry runtime imports
107
+ import { telemetryServerExternalPackages } from '@open-mercato/telemetry/nextjs-config'
108
+ ```
109
+
110
+ ```ts
111
+ // shared API dispatcher
112
+ import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
113
+
114
+ getTelemetryRuntime()?.recordHttpDuration(method, route.path, status, startedAt)
115
+ getTelemetryRuntime()?.reportError(error, {
116
+ attributes: { 'http.route': route.path },
117
+ })
118
+ ```
119
+
120
+ The CLI and queue worker use the same explicit-backend check before their dynamic
121
+ telemetry import. Queue enqueue/dispatch code talks only to the shared runtime
122
+ bridge, so the package is not loaded on the disabled path.
123
+
124
+ ## Public API
125
+
126
+ | Export | Description |
127
+ | --- | --- |
128
+ | `withSpan(name, fn, opts?)` | Run `fn` in a provider-owned span |
129
+ | `currentSpan()` / `setAttributes(attrs)` | Active span access |
130
+ | `counter` / `histogram` / `gauge` | Metric helpers |
131
+ | `reportError(err, ctx?)` | Span exception + shared error log + `om.errors` |
132
+ | `captureTraceContext()` / `continueTrace(...)` | Dedicated cross-boundary propagation |
133
+ | `initTelemetry()` / `shutdownTelemetry()` | Opt-in bootstrap and flush |
134
+ | `registerProvider(provider)` | Register a custom provider for an enabled backend name |
135
+
136
+ `@open-mercato/telemetry/nextjs` exports the runtime
137
+ `registerTelemetryForNextjs()` and `recordHttpDuration()` helpers.
138
+ `@open-mercato/telemetry/nextjs-config` separately exports only
139
+ `telemetryServerExternalPackages` for build configuration.
140
+
141
+ ## Security and privacy
142
+
143
+ - Explicit off is absolute: custom providers cannot override an unset/noop
144
+ backend, and no process-wide logger/runtime hooks are registered.
145
+ - Secret-looking attribute keys (including exact `token`) are masked, while
146
+ benign keys such as `token_count` remain intact.
147
+ - Error serialization includes only name, message, and stack. Arbitrary thrown
148
+ objects are never JSON-stringified.
149
+ - Redaction runs again at the OTLP provider boundary for log bodies, error
150
+ fields, span attributes, and metric labels.
151
+ - Postgres parameter-value capture is locked off with
152
+ `enhancedDatabaseReporting: false`.
153
+ - Metric labels must remain low-cardinality and must never contain tenant,
154
+ organization, or user IDs.
155
+
156
+ The package is server-only because span context uses `node:async_hooks`.
157
+
158
+ ## Validation
159
+
160
+ ```bash
161
+ yarn workspace @open-mercato/telemetry build
162
+ yarn workspace @open-mercato/telemetry test
163
+ yarn typecheck
164
+ ```
package/build.mjs ADDED
@@ -0,0 +1,10 @@
1
+ import { dirname } from 'node:path'
2
+ import { fileURLToPath } from 'node:url'
3
+ import { buildPackage } from '../../scripts/build-package.mjs'
4
+
5
+ const packageDir = dirname(fileURLToPath(import.meta.url))
6
+
7
+ await buildPackage(packageDir, {
8
+ name: 'telemetry',
9
+ entryPoints: 'src/**/*.ts',
10
+ })
package/dist/env.js ADDED
@@ -0,0 +1,44 @@
1
+ import { isTelemetryBackendEnabled } from "@open-mercato/shared/lib/telemetry/runtime";
2
+ const OTLP_BACKENDS = ["signoz", "newrelic", "otlp"];
3
+ function isOtlpBackend(name) {
4
+ return OTLP_BACKENDS.includes(name);
5
+ }
6
+ function parseBackend(raw) {
7
+ const v = (raw ?? "").trim().toLowerCase();
8
+ return isTelemetryBackendEnabled(v) ? v : "noop";
9
+ }
10
+ function parseSampling(raw, isProd) {
11
+ const n = Number.parseFloat((raw ?? "").trim());
12
+ if (Number.isFinite(n) && n >= 0 && n <= 1) return n;
13
+ return isProd ? 0.1 : 1;
14
+ }
15
+ function parseBool(raw, fallback) {
16
+ const v = (raw ?? "").trim().toLowerCase();
17
+ if (v === "true" || v === "1") return true;
18
+ if (v === "false" || v === "0") return false;
19
+ return fallback;
20
+ }
21
+ let cached;
22
+ function readTelemetryEnv() {
23
+ if (cached) return cached;
24
+ const isProd = process.env.NODE_ENV === "production";
25
+ const backend = parseBackend(process.env.TELEMETRY_BACKEND);
26
+ cached = {
27
+ backend,
28
+ enabled: backend !== "noop",
29
+ samplingRatio: parseSampling(process.env.TELEMETRY_SAMPLING_RATIO, isProd),
30
+ trustInboundTrace: parseBool(process.env.TELEMETRY_TRUST_INBOUND_TRACE, false),
31
+ serviceName: process.env.OTEL_SERVICE_NAME?.trim() || "open-mercato",
32
+ otlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT?.trim() || void 0
33
+ };
34
+ return cached;
35
+ }
36
+ function resetTelemetryEnvCache() {
37
+ cached = void 0;
38
+ }
39
+ export {
40
+ isOtlpBackend,
41
+ readTelemetryEnv,
42
+ resetTelemetryEnvCache
43
+ };
44
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/env.ts"],
4
+ "sourcesContent": ["import type { TelemetryBackendName } from './types'\nimport { isTelemetryBackendEnabled } from '@open-mercato/shared/lib/telemetry/runtime'\n\n/** Backend names that resolve to the OTLP provider (vendor differs only by endpoint). */\nconst OTLP_BACKENDS: readonly TelemetryBackendName[] = ['signoz', 'newrelic', 'otlp']\nexport function isOtlpBackend(name: TelemetryBackendName): boolean {\n return (OTLP_BACKENDS as readonly string[]).includes(name)\n}\n\nexport type TelemetryEnv = {\n /** Active backend. Unset/unknown \u2192 'noop' (off). */\n backend: TelemetryBackendName\n /** True unless backend is 'noop'. */\n enabled: boolean\n /** 0.0\u20131.0 trace sampling ratio. */\n samplingRatio: number\n /**\n * Continue the inbound HTTP trace (standard W3C extract) instead of rooting\n * per request. Default false. Set true only when embedded behind a trusted\n * upstream whose trace should continue.\n */\n trustInboundTrace: boolean\n serviceName: string\n /** OTLP endpoint (provider reads standard OTEL vars; surfaced here for diagnostics). */\n otlpEndpoint?: string\n}\n\nfunction parseBackend(raw: string | undefined): TelemetryBackendName {\n const v = (raw ?? '').trim().toLowerCase()\n return isTelemetryBackendEnabled(v) ? (v as TelemetryBackendName) : 'noop'\n}\n\nfunction parseSampling(raw: string | undefined, isProd: boolean): number {\n const n = Number.parseFloat((raw ?? '').trim())\n if (Number.isFinite(n) && n >= 0 && n <= 1) return n\n return isProd ? 0.1 : 1.0\n}\n\n/** Parse a boolean env flag; unset/unknown \u2192 `fallback`. */\nfunction parseBool(raw: string | undefined, fallback: boolean): boolean {\n const v = (raw ?? '').trim().toLowerCase()\n if (v === 'true' || v === '1') return true\n if (v === 'false' || v === '0') return false\n return fallback\n}\n\nlet cached: TelemetryEnv | undefined\n\nexport function readTelemetryEnv(): TelemetryEnv {\n if (cached) return cached\n const isProd = process.env.NODE_ENV === 'production'\n const backend = parseBackend(process.env.TELEMETRY_BACKEND)\n cached = {\n backend,\n enabled: backend !== 'noop',\n samplingRatio: parseSampling(process.env.TELEMETRY_SAMPLING_RATIO, isProd),\n trustInboundTrace: parseBool(process.env.TELEMETRY_TRUST_INBOUND_TRACE, false),\n serviceName: process.env.OTEL_SERVICE_NAME?.trim() || 'open-mercato',\n otlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT?.trim() || undefined,\n }\n return cached\n}\n\n/**\n * Reset the memoized env snapshot. Called by `initTelemetry()` so init always\n * resolves from the fully-loaded environment (a host may import this package\n * before its `.env` is loaded \u2014 the CLI binary does), and by jest between tests.\n */\nexport function resetTelemetryEnvCache(): void {\n cached = undefined\n}\n"],
5
+ "mappings": "AACA,SAAS,iCAAiC;AAG1C,MAAM,gBAAiD,CAAC,UAAU,YAAY,MAAM;AAC7E,SAAS,cAAc,MAAqC;AACjE,SAAQ,cAAoC,SAAS,IAAI;AAC3D;AAoBA,SAAS,aAAa,KAA+C;AACnE,QAAM,KAAK,OAAO,IAAI,KAAK,EAAE,YAAY;AACzC,SAAO,0BAA0B,CAAC,IAAK,IAA6B;AACtE;AAEA,SAAS,cAAc,KAAyB,QAAyB;AACvE,QAAM,IAAI,OAAO,YAAY,OAAO,IAAI,KAAK,CAAC;AAC9C,MAAI,OAAO,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,EAAG,QAAO;AACnD,SAAO,SAAS,MAAM;AACxB;AAGA,SAAS,UAAU,KAAyB,UAA4B;AACtE,QAAM,KAAK,OAAO,IAAI,KAAK,EAAE,YAAY;AACzC,MAAI,MAAM,UAAU,MAAM,IAAK,QAAO;AACtC,MAAI,MAAM,WAAW,MAAM,IAAK,QAAO;AACvC,SAAO;AACT;AAEA,IAAI;AAEG,SAAS,mBAAiC;AAC/C,MAAI,OAAQ,QAAO;AACnB,QAAM,SAAS,QAAQ,IAAI,aAAa;AACxC,QAAM,UAAU,aAAa,QAAQ,IAAI,iBAAiB;AAC1D,WAAS;AAAA,IACP;AAAA,IACA,SAAS,YAAY;AAAA,IACrB,eAAe,cAAc,QAAQ,IAAI,0BAA0B,MAAM;AAAA,IACzE,mBAAmB,UAAU,QAAQ,IAAI,+BAA+B,KAAK;AAAA,IAC7E,aAAa,QAAQ,IAAI,mBAAmB,KAAK,KAAK;AAAA,IACtD,cAAc,QAAQ,IAAI,6BAA6B,KAAK,KAAK;AAAA,EACnE;AACA,SAAO;AACT;AAOO,SAAS,yBAA+B;AAC7C,WAAS;AACX;",
6
+ "names": []
7
+ }
@@ -0,0 +1,14 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ const spanStore = new AsyncLocalStorage();
3
+ function runWithSpan(span, fn) {
4
+ return spanStore.run(span, fn);
5
+ }
6
+ function spanFromStore() {
7
+ return spanStore.getStore();
8
+ }
9
+ export {
10
+ runWithSpan,
11
+ spanFromStore,
12
+ spanStore
13
+ };
14
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/facade/context.ts"],
4
+ "sourcesContent": ["import { AsyncLocalStorage } from 'node:async_hooks'\nimport type { Span } from '../types'\n\n/**\n * Carries the facade's active span across `await` boundaries for spans created\n * via `withSpan`. (OTEL auto-instrumentation spans live in OTEL's own context;\n * the OTLP provider's `activeSpan()` bridges to those \u2014 see `currentSpan`.)\n */\nexport const spanStore = new AsyncLocalStorage<Span>()\n\nexport function runWithSpan<T>(span: Span, fn: () => T): T {\n return spanStore.run(span, fn)\n}\n\nexport function spanFromStore(): Span | undefined {\n return spanStore.getStore()\n}\n"],
5
+ "mappings": "AAAA,SAAS,yBAAyB;AAQ3B,MAAM,YAAY,IAAI,kBAAwB;AAE9C,SAAS,YAAe,MAAY,IAAgB;AACzD,SAAO,UAAU,IAAI,MAAM,EAAE;AAC/B;AAEO,SAAS,gBAAkC;AAChD,SAAO,UAAU,SAAS;AAC5B;",
6
+ "names": []
7
+ }
@@ -0,0 +1,18 @@
1
+ import { histogram } from "./meter.js";
2
+ function recordHttpDuration(method, route, status, startedAt) {
3
+ histogram(
4
+ "http.server.request.duration",
5
+ (Date.now() - startedAt) / 1e3,
6
+ {
7
+ "http.request.method": method,
8
+ "http.route": route,
9
+ "http.response.status_code": status,
10
+ "error.type": status >= 500 ? String(status) : void 0
11
+ },
12
+ "s"
13
+ );
14
+ }
15
+ export {
16
+ recordHttpDuration
17
+ };
18
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/facade/http.ts"],
4
+ "sourcesContent": ["import { histogram } from './meter'\n\n/**\n * Emit the OpenTelemetry-standard HTTP server metric\n * `http.server.request.duration` (histogram, seconds) with semconv attributes.\n * `route` MUST be a low-cardinality route template, never a resolved pathname.\n */\nexport function recordHttpDuration(\n method: string,\n route: string,\n status: number,\n startedAt: number,\n): void {\n histogram(\n 'http.server.request.duration',\n (Date.now() - startedAt) / 1000,\n {\n 'http.request.method': method,\n 'http.route': route,\n 'http.response.status_code': status,\n 'error.type': status >= 500 ? String(status) : undefined,\n },\n 's',\n )\n}\n"],
5
+ "mappings": "AAAA,SAAS,iBAAiB;AAOnB,SAAS,mBACd,QACA,OACA,QACA,WACM;AACN;AAAA,IACE;AAAA,KACC,KAAK,IAAI,IAAI,aAAa;AAAA,IAC3B;AAAA,MACE,uBAAuB;AAAA,MACvB,cAAc;AAAA,MACd,6BAA6B;AAAA,MAC7B,cAAc,UAAU,MAAM,OAAO,MAAM,IAAI;AAAA,IACjD;AAAA,IACA;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,40 @@
1
+ import {
2
+ registerLoggerExtension
3
+ } from "@open-mercato/shared/lib/logger";
4
+ import { redactAttributes } from "./redact.js";
5
+ import { serializeError } from "./serialize.js";
6
+ function toAttributes(record) {
7
+ const attributes = {
8
+ "logger.name": record.namespace
9
+ };
10
+ for (const [key, value] of Object.entries(record.fields)) {
11
+ if (key === "err" || value === void 0) continue;
12
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
13
+ attributes[key] = value;
14
+ }
15
+ }
16
+ return redactAttributes(attributes);
17
+ }
18
+ function toLogRecord(record) {
19
+ const error = record.fields.err === void 0 ? void 0 : serializeError(record.fields.err);
20
+ return {
21
+ level: record.level,
22
+ message: record.message,
23
+ attributes: toAttributes(record),
24
+ error,
25
+ time: record.time
26
+ };
27
+ }
28
+ function registerTelemetryLogger(provider) {
29
+ return registerLoggerExtension({
30
+ enrich: () => {
31
+ const context = provider.activeTraceContext();
32
+ return context ? { trace_id: context.traceId, span_id: context.spanId } : void 0;
33
+ },
34
+ emit: (record) => provider.emitLog(toLogRecord(record))
35
+ });
36
+ }
37
+ export {
38
+ registerTelemetryLogger
39
+ };
40
+ //# sourceMappingURL=logger-bridge.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/facade/logger-bridge.ts"],
4
+ "sourcesContent": ["import {\n registerLoggerExtension,\n type LoggerExtensionRecord,\n} from '@open-mercato/shared/lib/logger'\nimport type { Attributes, LogRecord, TelemetryProvider } from '../types'\nimport { redactAttributes } from './redact'\nimport { serializeError } from './serialize'\n\nfunction toAttributes(record: LoggerExtensionRecord): Attributes {\n const attributes: Attributes = {\n 'logger.name': record.namespace,\n }\n for (const [key, value] of Object.entries(record.fields)) {\n if (key === 'err' || value === undefined) continue\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n attributes[key] = value\n }\n }\n return redactAttributes(attributes)\n}\n\nfunction toLogRecord(record: LoggerExtensionRecord): LogRecord {\n const error = record.fields.err === undefined\n ? undefined\n : serializeError(record.fields.err)\n return {\n level: record.level,\n message: record.message,\n attributes: toAttributes(record),\n error,\n time: record.time,\n }\n}\n\n/**\n * Extend the canonical shared logger with trace correlation + remote export.\n * The shared logger remains the sole application-facing logger and sole local\n * output path; telemetry observes the already-normalized record exactly once.\n */\nexport function registerTelemetryLogger(provider: TelemetryProvider): () => void {\n return registerLoggerExtension({\n enrich: () => {\n const context = provider.activeTraceContext()\n return context\n ? { trace_id: context.traceId, span_id: context.spanId }\n : undefined\n },\n emit: (record) => provider.emitLog(toLogRecord(record)),\n })\n}\n"],
5
+ "mappings": "AAAA;AAAA,EACE;AAAA,OAEK;AAEP,SAAS,wBAAwB;AACjC,SAAS,sBAAsB;AAE/B,SAAS,aAAa,QAA2C;AAC/D,QAAM,aAAyB;AAAA,IAC7B,eAAe,OAAO;AAAA,EACxB;AACA,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,MAAM,GAAG;AACxD,QAAI,QAAQ,SAAS,UAAU,OAAW;AAC1C,QAAI,OAAO,UAAU,YAAY,OAAO,UAAU,YAAY,OAAO,UAAU,WAAW;AACxF,iBAAW,GAAG,IAAI;AAAA,IACpB;AAAA,EACF;AACA,SAAO,iBAAiB,UAAU;AACpC;AAEA,SAAS,YAAY,QAA0C;AAC7D,QAAM,QAAQ,OAAO,OAAO,QAAQ,SAChC,SACA,eAAe,OAAO,OAAO,GAAG;AACpC,SAAO;AAAA,IACL,OAAO,OAAO;AAAA,IACd,SAAS,OAAO;AAAA,IAChB,YAAY,aAAa,MAAM;AAAA,IAC/B;AAAA,IACA,MAAM,OAAO;AAAA,EACf;AACF;AAOO,SAAS,wBAAwB,UAAyC;AAC/E,SAAO,wBAAwB;AAAA,IAC7B,QAAQ,MAAM;AACZ,YAAM,UAAU,SAAS,mBAAmB;AAC5C,aAAO,UACH,EAAE,UAAU,QAAQ,SAAS,SAAS,QAAQ,OAAO,IACrD;AAAA,IACN;AAAA,IACA,MAAM,CAAC,WAAW,SAAS,QAAQ,YAAY,MAAM,CAAC;AAAA,EACxD,CAAC;AACH;",
6
+ "names": []
7
+ }
@@ -0,0 +1,16 @@
1
+ import { getActiveProvider } from "../provider/registry.js";
2
+ function counter(name, value = 1, labels, unit) {
3
+ getActiveProvider().recordMetric({ kind: "counter", name, value, labels, unit });
4
+ }
5
+ function histogram(name, value, labels, unit) {
6
+ getActiveProvider().recordMetric({ kind: "histogram", name, value, labels, unit });
7
+ }
8
+ function gauge(name, value, labels, unit) {
9
+ getActiveProvider().recordMetric({ kind: "gauge", name, value, labels, unit });
10
+ }
11
+ export {
12
+ counter,
13
+ gauge,
14
+ histogram
15
+ };
16
+ //# sourceMappingURL=meter.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/facade/meter.ts"],
4
+ "sourcesContent": ["import type { Attributes } from '../types'\nimport { getActiveProvider } from '../provider/registry'\n\n/**\n * Metric helpers. Labels MUST be low-cardinality (never tenant/org/user IDs \u2014\n * those belong on span attributes, per the telemetry spec's R4). The active\n * provider caches the underlying instrument by name; these are no-ops when\n * metrics are off.\n */\nexport function counter(name: string, value = 1, labels?: Attributes, unit?: string): void {\n getActiveProvider().recordMetric({ kind: 'counter', name, value, labels, unit })\n}\n\nexport function histogram(name: string, value: number, labels?: Attributes, unit?: string): void {\n getActiveProvider().recordMetric({ kind: 'histogram', name, value, labels, unit })\n}\n\nexport function gauge(name: string, value: number, labels?: Attributes, unit?: string): void {\n getActiveProvider().recordMetric({ kind: 'gauge', name, value, labels, unit })\n}\n"],
5
+ "mappings": "AACA,SAAS,yBAAyB;AAQ3B,SAAS,QAAQ,MAAc,QAAQ,GAAG,QAAqB,MAAqB;AACzF,oBAAkB,EAAE,aAAa,EAAE,MAAM,WAAW,MAAM,OAAO,QAAQ,KAAK,CAAC;AACjF;AAEO,SAAS,UAAU,MAAc,OAAe,QAAqB,MAAqB;AAC/F,oBAAkB,EAAE,aAAa,EAAE,MAAM,aAAa,MAAM,OAAO,QAAQ,KAAK,CAAC;AACnF;AAEO,SAAS,MAAM,MAAc,OAAe,QAAqB,MAAqB;AAC3F,oBAAkB,EAAE,aAAa,EAAE,MAAM,SAAS,MAAM,OAAO,QAAQ,KAAK,CAAC;AAC/E;",
6
+ "names": []
7
+ }
@@ -0,0 +1,20 @@
1
+ import { getActiveProvider } from "../provider/registry.js";
2
+ import { runWithSpan } from "./context.js";
3
+ function captureTraceContext() {
4
+ const carrier = {};
5
+ getActiveProvider().inject(carrier);
6
+ return carrier;
7
+ }
8
+ function continueTrace(carrier, name, fn, options) {
9
+ return getActiveProvider().runInRemoteSpan(
10
+ carrier ?? {},
11
+ name,
12
+ options ?? {},
13
+ (span) => runWithSpan(span, () => fn(span))
14
+ );
15
+ }
16
+ export {
17
+ captureTraceContext,
18
+ continueTrace
19
+ };
20
+ //# sourceMappingURL=propagation.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/facade/propagation.ts"],
4
+ "sourcesContent": ["import type { Span, SpanOptions, TraceCarrier } from '../types'\nimport { getActiveProvider } from '../provider/registry'\nimport { runWithSpan } from './context'\n\n/**\n * Capture the active trace context into a fresh carrier, to embed in a queue job\n * or event payload (the queue/event bus expose no metadata channel \u2014 see the\n * telemetry spec's S5). Returns `{}` when telemetry is off.\n */\nexport function captureTraceContext(): TraceCarrier {\n const carrier: TraceCarrier = {}\n getActiveProvider().inject(carrier)\n return carrier\n}\n\n/**\n * Continue a trace on the consumer side (worker / subscriber): run `fn` inside a\n * new active span named `name`, parented to the context in `carrier`. The job's\n * spans (pg, etc.) then nest under the producer's trace as one waterfall. If\n * `carrier` is empty/undefined the span simply starts a fresh root.\n */\nexport function continueTrace<T>(\n carrier: TraceCarrier | undefined,\n name: string,\n fn: (span: Span) => T,\n options?: SpanOptions,\n): T {\n return getActiveProvider().runInRemoteSpan(carrier ?? {}, name, options ?? {}, (span) =>\n runWithSpan(span, () => fn(span)),\n )\n}\n"],
5
+ "mappings": "AACA,SAAS,yBAAyB;AAClC,SAAS,mBAAmB;AAOrB,SAAS,sBAAoC;AAClD,QAAM,UAAwB,CAAC;AAC/B,oBAAkB,EAAE,OAAO,OAAO;AAClC,SAAO;AACT;AAQO,SAAS,cACd,SACA,MACA,IACA,SACG;AACH,SAAO,kBAAkB,EAAE;AAAA,IAAgB,WAAW,CAAC;AAAA,IAAG;AAAA,IAAM,WAAW,CAAC;AAAA,IAAG,CAAC,SAC9E,YAAY,MAAM,MAAM,GAAG,IAAI,CAAC;AAAA,EAClC;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,21 @@
1
+ const EMAIL_RE = /[A-Z0-9._%+-]{1,64}@[A-Z0-9.-]{1,253}\.[A-Z]{2,63}/gi;
2
+ const SECRET_HEADER_RE = /\b(authorization|proxy-authorization|cookie|set-cookie)(\s*[:=]\s*)([^\n;,}]+)/gi;
3
+ const AUTH_SCHEME_RE = /\b(Bearer|Basic|ApiKey)\s+[A-Za-z0-9._~+/=-]+/gi;
4
+ function redactPii(text) {
5
+ return text.replace(EMAIL_RE, "[redacted-email]").replace(SECRET_HEADER_RE, (_match, name, separator) => `${name}${separator}[redacted]`).replace(AUTH_SCHEME_RE, (_match, scheme) => `${scheme} [redacted]`);
6
+ }
7
+ const SECRET_KEY_RE = /(authorization|cookie|password|passwd|\bpwd\b|secret|api[-_]?key|access[-_]?token|refresh[-_]?token|id[-_]?token|auth[-_]?token|^token$|\bbearer\b|private[-_]?key|credential)/i;
8
+ function redactAttributes(attributes) {
9
+ const result = {};
10
+ for (const [key, value] of Object.entries(attributes)) {
11
+ if (SECRET_KEY_RE.test(key)) result[key] = "[redacted]";
12
+ else if (typeof value === "string") result[key] = redactPii(value);
13
+ else result[key] = value;
14
+ }
15
+ return result;
16
+ }
17
+ export {
18
+ redactAttributes,
19
+ redactPii
20
+ };
21
+ //# sourceMappingURL=redact.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/facade/redact.ts"],
4
+ "sourcesContent": ["import type { Attributes } from '../types'\n\n/**\n * Active redaction backstop for telemetry (Privacy section of the telemetry spec).\n *\n * Defense-in-depth over the don't-emit posture: callers are expected not to put\n * PII or secrets into errors/logs, but error messages and stack frames can still\n * pick them up a layer down (e.g. `Error: no user for jan.kowalski@example.com`,\n * or a dumped `Authorization: Bearer \u2026` header). This scrubs the highest-signal\n * identifiers \u2014 email addresses and auth tokens \u2014 from any text that ships to the\n * backend, without touching opaque UUIDs/ids (which we keep).\n *\n * Deliberately conservative (emails + auth tokens) to preserve debuggability;\n * extend the pattern set here if a new leak vector is found.\n */\n// Bound every component both to match the practical limits from RFC 5321 and\n// to keep scanning time linear for hostile strings with long runs of email\n// punctuation (CodeQL js/polynomial-redos).\nconst EMAIL_RE = /[A-Z0-9._%+-]{1,64}@[A-Z0-9.-]{1,253}\\.[A-Z]{2,63}/gi\n// `authorization: <value>` / `cookie: <value>` header dumps embedded in free text.\n// The value runs to the next separator (`;`/`,`/`}`) or newline, so it swallows a\n// `Bearer <token>` value in one pass (before the standalone-scheme rule below).\nconst SECRET_HEADER_RE = /\\b(authorization|proxy-authorization|cookie|set-cookie)(\\s*[:=]\\s*)([^\\n;,}]+)/gi\n// `Bearer <token>` / `Basic <base64>` / `ApiKey <key>` standing alone in free text (message or stack).\nconst AUTH_SCHEME_RE = /\\b(Bearer|Basic|ApiKey)\\s+[A-Za-z0-9._~+/=-]+/gi\n\nexport function redactPii(text: string): string {\n return text\n .replace(EMAIL_RE, '[redacted-email]')\n .replace(SECRET_HEADER_RE, (_match, name: string, separator: string) => `${name}${separator}[redacted]`)\n .replace(AUTH_SCHEME_RE, (_match, scheme: string) => `${scheme} [redacted]`)\n}\n\n// Attribute KEYS whose value is a secret regardless of content. Intentionally\n// specific enough not to clobber benign fields like `token_count`, while\n// still masking the exact key `token`.\nconst SECRET_KEY_RE =\n /(authorization|cookie|password|passwd|\\bpwd\\b|secret|api[-_]?key|access[-_]?token|refresh[-_]?token|id[-_]?token|auth[-_]?token|^token$|\\bbearer\\b|private[-_]?key|credential)/i\n\n/**\n * Redact a telemetry attribute bag before it ships: a value under a secret-looking\n * KEY (`authorization`, `set-cookie`, `client_secret`, `x-api-key`, \u2026) is masked\n * wholesale; other string values still pass through `redactPii` so an inline email\n * or auth token is caught too. Non-string values are left as-is.\n */\nexport function redactAttributes(attributes: Attributes): Attributes {\n const result: Attributes = {}\n for (const [key, value] of Object.entries(attributes)) {\n if (SECRET_KEY_RE.test(key)) result[key] = '[redacted]'\n else if (typeof value === 'string') result[key] = redactPii(value)\n else result[key] = value\n }\n return result\n}\n"],
5
+ "mappings": "AAkBA,MAAM,WAAW;AAIjB,MAAM,mBAAmB;AAEzB,MAAM,iBAAiB;AAEhB,SAAS,UAAU,MAAsB;AAC9C,SAAO,KACJ,QAAQ,UAAU,kBAAkB,EACpC,QAAQ,kBAAkB,CAAC,QAAQ,MAAc,cAAsB,GAAG,IAAI,GAAG,SAAS,YAAY,EACtG,QAAQ,gBAAgB,CAAC,QAAQ,WAAmB,GAAG,MAAM,aAAa;AAC/E;AAKA,MAAM,gBACJ;AAQK,SAAS,iBAAiB,YAAoC;AACnE,QAAM,SAAqB,CAAC;AAC5B,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,UAAU,GAAG;AACrD,QAAI,cAAc,KAAK,GAAG,EAAG,QAAO,GAAG,IAAI;AAAA,aAClC,OAAO,UAAU,SAAU,QAAO,GAAG,IAAI,UAAU,KAAK;AAAA,QAC5D,QAAO,GAAG,IAAI;AAAA,EACrB;AACA,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -0,0 +1,26 @@
1
+ import { createLogger } from "@open-mercato/shared/lib/logger";
2
+ import { currentSpan } from "./tracer.js";
3
+ import { serializeError } from "./serialize.js";
4
+ import { counter } from "./meter.js";
5
+ import { redactAttributes } from "./redact.js";
6
+ const logger = createLogger("telemetry");
7
+ function reportError(error, ctx) {
8
+ const span = currentSpan();
9
+ if (span) {
10
+ span.recordException(error);
11
+ span.setStatus("error");
12
+ }
13
+ const serialized = serializeError(error);
14
+ const attributes = { ...ctx?.attributes ?? {} };
15
+ if (ctx?.module) attributes.module = ctx.module;
16
+ const safeAttributes = redactAttributes(attributes);
17
+ const safeError = new Error(serialized.message);
18
+ safeError.name = serialized.name;
19
+ safeError.stack = serialized.stack;
20
+ logger.error("Application error reported", { ...safeAttributes, err: safeError });
21
+ counter("om.errors", 1, ctx?.module ? { module: ctx.module } : void 0);
22
+ }
23
+ export {
24
+ reportError
25
+ };
26
+ //# sourceMappingURL=report-error.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/facade/report-error.ts"],
4
+ "sourcesContent": ["import type { Attributes } from '../types'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { currentSpan } from './tracer'\nimport { serializeError } from './serialize'\nimport { counter } from './meter'\nimport { redactAttributes } from './redact'\n\nconst logger = createLogger('telemetry')\n\nexport type ReportErrorContext = {\n /** Owning module, e.g. 'orders'. Used as the only metric label. */\n module?: string\n /** Low-cardinality, NO-PII attributes (ids ok, never names/content). */\n attributes?: Attributes\n}\n\n/**\n * The error funnel. Additive \u2014 existing `console.error` calls stay valid; this\n * is the path that reaches the active backend. It:\n * 1. records the exception on the active span (errors-as-span-events),\n * 2. emits a structured error log (stack only \u2014 no PII payloads),\n * 3. increments the `om.errors` counter, labeled by `module` only.\n */\nexport function reportError(error: unknown, ctx?: ReportErrorContext): void {\n const span = currentSpan()\n if (span) {\n span.recordException(error)\n span.setStatus('error')\n }\n\n const serialized = serializeError(error)\n const attributes: Attributes = { ...(ctx?.attributes ?? {}) }\n if (ctx?.module) attributes.module = ctx.module\n const safeAttributes = redactAttributes(attributes)\n\n const safeError = new Error(serialized.message)\n safeError.name = serialized.name\n safeError.stack = serialized.stack\n logger.error('Application error reported', { ...safeAttributes, err: safeError })\n counter('om.errors', 1, ctx?.module ? { module: ctx.module } : undefined)\n}\n"],
5
+ "mappings": "AACA,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAC5B,SAAS,sBAAsB;AAC/B,SAAS,eAAe;AACxB,SAAS,wBAAwB;AAEjC,MAAM,SAAS,aAAa,WAAW;AAgBhC,SAAS,YAAY,OAAgB,KAAgC;AAC1E,QAAM,OAAO,YAAY;AACzB,MAAI,MAAM;AACR,SAAK,gBAAgB,KAAK;AAC1B,SAAK,UAAU,OAAO;AAAA,EACxB;AAEA,QAAM,aAAa,eAAe,KAAK;AACvC,QAAM,aAAyB,EAAE,GAAI,KAAK,cAAc,CAAC,EAAG;AAC5D,MAAI,KAAK,OAAQ,YAAW,SAAS,IAAI;AACzC,QAAM,iBAAiB,iBAAiB,UAAU;AAElD,QAAM,YAAY,IAAI,MAAM,WAAW,OAAO;AAC9C,YAAU,OAAO,WAAW;AAC5B,YAAU,QAAQ,WAAW;AAC7B,SAAO,MAAM,8BAA8B,EAAE,GAAG,gBAAgB,KAAK,UAAU,CAAC;AAChF,UAAQ,aAAa,GAAG,KAAK,SAAS,EAAE,QAAQ,IAAI,OAAO,IAAI,MAAS;AAC1E;",
6
+ "names": []
7
+ }
@@ -0,0 +1,35 @@
1
+ import { redactPii } from "./redact.js";
2
+ function serializeError(error) {
3
+ if (error instanceof Error) {
4
+ return {
5
+ name: redactPii(error.name || "Error"),
6
+ message: redactPii(foldCause(error)),
7
+ stack: error.stack ? redactPii(error.stack) : void 0
8
+ };
9
+ }
10
+ return { name: "NonError", message: redactPii(safeString(error)) };
11
+ }
12
+ function foldCause(error, seen = /* @__PURE__ */ new WeakSet()) {
13
+ if (seen.has(error)) return error.message;
14
+ seen.add(error);
15
+ const cause = error.cause;
16
+ if (cause instanceof Error) {
17
+ return `${error.message} \u2014 caused by ${cause.name || "Error"}: ${foldCause(cause, seen)}`;
18
+ }
19
+ return error.message;
20
+ }
21
+ function safeString(value) {
22
+ if (typeof value === "string") return value;
23
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
24
+ return String(value);
25
+ }
26
+ if (typeof value === "symbol") return value.description ? `Symbol(${value.description})` : "Symbol";
27
+ if (typeof value === "function") return "[function]";
28
+ if (value === null) return "null";
29
+ if (value === void 0) return "undefined";
30
+ return "[non-error object]";
31
+ }
32
+ export {
33
+ serializeError
34
+ };
35
+ //# sourceMappingURL=serialize.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/facade/serialize.ts"],
4
+ "sourcesContent": ["import type { LogRecord } from '../types'\nimport { redactPii } from './redact'\n\n/**\n * Serialize an error for telemetry: name, message, and stack only.\n *\n * Deliberately does NOT include arbitrary error properties (request bodies,\n * `cause` payloads, query parameters) \u2014 those can carry PII. The cause CHAIN is\n * folded into the message text (names/messages only), never its data. Message +\n * stack are run through `redactPii` so a leaked email never reaches the backend\n * (active-redaction backstop over the don't-emit posture).\n */\nexport function serializeError(error: unknown): NonNullable<LogRecord['error']> {\n if (error instanceof Error) {\n return {\n name: redactPii(error.name || 'Error'),\n message: redactPii(foldCause(error)),\n stack: error.stack ? redactPii(error.stack) : undefined,\n }\n }\n return { name: 'NonError', message: redactPii(safeString(error)) }\n}\n\nfunction foldCause(error: Error, seen = new WeakSet<object>()): string {\n if (seen.has(error)) return error.message\n seen.add(error)\n const cause = (error as { cause?: unknown }).cause\n if (cause instanceof Error) {\n return `${error.message} \u2014 caused by ${cause.name || 'Error'}: ${foldCause(cause, seen)}`\n }\n return error.message\n}\n\nfunction safeString(value: unknown): string {\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {\n return String(value)\n }\n if (typeof value === 'symbol') return value.description ? `Symbol(${value.description})` : 'Symbol'\n if (typeof value === 'function') return '[function]'\n if (value === null) return 'null'\n if (value === undefined) return 'undefined'\n // Never stringify arbitrary objects: request bodies, provider errors, and\n // thrown payloads commonly contain credentials or personal data.\n return '[non-error object]'\n}\n"],
5
+ "mappings": "AACA,SAAS,iBAAiB;AAWnB,SAAS,eAAe,OAAiD;AAC9E,MAAI,iBAAiB,OAAO;AAC1B,WAAO;AAAA,MACL,MAAM,UAAU,MAAM,QAAQ,OAAO;AAAA,MACrC,SAAS,UAAU,UAAU,KAAK,CAAC;AAAA,MACnC,OAAO,MAAM,QAAQ,UAAU,MAAM,KAAK,IAAI;AAAA,IAChD;AAAA,EACF;AACA,SAAO,EAAE,MAAM,YAAY,SAAS,UAAU,WAAW,KAAK,CAAC,EAAE;AACnE;AAEA,SAAS,UAAU,OAAc,OAAO,oBAAI,QAAgB,GAAW;AACrE,MAAI,KAAK,IAAI,KAAK,EAAG,QAAO,MAAM;AAClC,OAAK,IAAI,KAAK;AACd,QAAM,QAAS,MAA8B;AAC7C,MAAI,iBAAiB,OAAO;AAC1B,WAAO,GAAG,MAAM,OAAO,qBAAgB,MAAM,QAAQ,OAAO,KAAK,UAAU,OAAO,IAAI,CAAC;AAAA,EACzF;AACA,SAAO,MAAM;AACf;AAEA,SAAS,WAAW,OAAwB;AAC1C,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,YAAY,OAAO,UAAU,aAAa,OAAO,UAAU,UAAU;AACxF,WAAO,OAAO,KAAK;AAAA,EACrB;AACA,MAAI,OAAO,UAAU,SAAU,QAAO,MAAM,cAAc,UAAU,MAAM,WAAW,MAAM;AAC3F,MAAI,OAAO,UAAU,WAAY,QAAO;AACxC,MAAI,UAAU,KAAM,QAAO;AAC3B,MAAI,UAAU,OAAW,QAAO;AAGhC,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -0,0 +1,21 @@
1
+ import { getActiveProvider } from "../provider/registry.js";
2
+ import { runWithSpan, spanFromStore } from "./context.js";
3
+ function withSpan(name, fn, options) {
4
+ return getActiveProvider().runInSpan(
5
+ name,
6
+ options ?? {},
7
+ (span) => runWithSpan(span, () => fn(span))
8
+ );
9
+ }
10
+ function currentSpan() {
11
+ return spanFromStore() ?? getActiveProvider().activeSpan();
12
+ }
13
+ function setAttributes(attributes) {
14
+ currentSpan()?.setAttributes(attributes);
15
+ }
16
+ export {
17
+ currentSpan,
18
+ setAttributes,
19
+ withSpan
20
+ };
21
+ //# sourceMappingURL=tracer.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/facade/tracer.ts"],
4
+ "sourcesContent": ["import type { Attributes, Span, SpanOptions } from '../types'\nimport { getActiveProvider } from '../provider/registry'\nimport { runWithSpan, spanFromStore } from './context'\n\n/**\n * Run `fn` inside a new span named `name`. The active provider owns span\n * creation + context, so any OTEL auto-instrumentation (pg/http) inside `fn`\n * nests under this span in the same trace. Exceptions and duration are recorded\n * automatically by the provider. No-op-cheap when telemetry is off.\n */\nexport function withSpan<T>(name: string, fn: (span: Span) => T, options?: SpanOptions): T {\n return getActiveProvider().runInSpan(name, options ?? {}, (span) =>\n runWithSpan(span, () => fn(span)),\n )\n}\n\n/** The active span (facade-created or OTEL auto-instrumented), if any. */\nexport function currentSpan(): Span | undefined {\n return spanFromStore() ?? getActiveProvider().activeSpan()\n}\n\n/** Set attributes on the active span, if any. */\nexport function setAttributes(attributes: Attributes): void {\n currentSpan()?.setAttributes(attributes)\n}\n"],
5
+ "mappings": "AACA,SAAS,yBAAyB;AAClC,SAAS,aAAa,qBAAqB;AAQpC,SAAS,SAAY,MAAc,IAAuB,SAA0B;AACzF,SAAO,kBAAkB,EAAE;AAAA,IAAU;AAAA,IAAM,WAAW,CAAC;AAAA,IAAG,CAAC,SACzD,YAAY,MAAM,MAAM,GAAG,IAAI,CAAC;AAAA,EAClC;AACF;AAGO,SAAS,cAAgC;AAC9C,SAAO,cAAc,KAAK,kBAAkB,EAAE,WAAW;AAC3D;AAGO,SAAS,cAAc,YAA8B;AAC1D,cAAY,GAAG,cAAc,UAAU;AACzC;",
6
+ "names": []
7
+ }