@modainteract/moda-interact-shared 0.3.0 → 0.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/README.md CHANGED
@@ -4,8 +4,8 @@ Shared TypeScript contracts and reusable platform primitives used by multiple
4
4
  Moda Interact services.
5
5
 
6
6
  This package is a **library**, not a deployable service. It does not own
7
- transport, persistence, background workers, provider SDK integration, or
8
- application business workflows.
7
+ business transports, persistence, background workers, provider SDK integration,
8
+ or application business workflows.
9
9
 
10
10
  ## Install
11
11
 
@@ -28,10 +28,117 @@ including:
28
28
  - canonical queue/job-name constants where multiple repositories must agree;
29
29
  - deterministic correlation/job identifier helpers;
30
30
  - small pure utilities used by more than one service;
31
- - reusable structured application logging primitives.
31
+ - reusable structured application logging primitives;
32
+ - the reusable base Node OpenTelemetry runtime, including tracing and metrics
33
+ providers, exporters, sampling, HTTP/Undici instrumentation, lifecycle, and
34
+ generic observability helpers.
32
35
 
33
36
  The package deliberately keeps framework and runtime dependencies small.
34
37
 
38
+ ## Node observability runtime
39
+
40
+ The package provides a browser-safe semantic observability entry and a
41
+ Node-only runtime entry:
42
+
43
+ ```ts
44
+ import {
45
+ getActiveTraceId,
46
+ withObservedSpan,
47
+ } from "@modainteract/moda-interact-shared/observability";
48
+
49
+ import {
50
+ initNodeObservability,
51
+ } from "@modainteract/moda-interact-shared/observability/node";
52
+ ```
53
+
54
+ Import `./observability/node` only from a process preload. It installs tracing,
55
+ metrics, HTTP/HTTPS and Undici/fetch instrumentation before framework or worker
56
+ modules load:
57
+
58
+ ```text
59
+ node --import ./observability.mjs <framework-or-worker-entrypoint>
60
+ ```
61
+
62
+ ```js
63
+ // observability.mjs
64
+ import { initNodeObservability } from
65
+ "@modainteract/moda-interact-shared/observability/node";
66
+
67
+ initNodeObservability({
68
+ serviceName: "moda-interact-messaging",
69
+ environment: process.env.DEPLOYMENT_ENVIRONMENT_NAME,
70
+ instrument: { http: true, fetch: true, prisma: false },
71
+ });
72
+ ```
73
+
74
+ Set `prisma: true` only for processes that load Prisma, and keep the preload
75
+ before the first Prisma application import.
76
+
77
+ The runtime uses the canonical `service.namespace`, `service.name`, and
78
+ `deployment.environment.name` resource attributes. It honors
79
+ `OTEL_TRACES_SAMPLER`, ratio arguments for ratio samplers, signal-specific or
80
+ generic OTLP endpoints, and bounded batch/export settings. With no trace or
81
+ metric endpoint those pipelines are no-ops while structured stdout logging
82
+ continues.
83
+
84
+ `forceFlush()` covers traces, metrics, OpenTelemetry Logs, and direct Loki.
85
+ Initialization, export, flush, and shutdown failures are best-effort and do not
86
+ become application startup or business-operation failures.
87
+
88
+ ### BullMQ telemetry
89
+
90
+ Create the shared BullMQ telemetry adapter once per Queue or Worker options
91
+ object and pass it through BullMQ's native `telemetry` option:
92
+
93
+ ```ts
94
+ import { createBullMQTelemetry } from
95
+ "@modainteract/moda-interact-shared/observability/bullmq";
96
+
97
+ const telemetry = createBullMQTelemetry({
98
+ serviceName: "moda-messaging-worker",
99
+ enableMetrics: true,
100
+ });
101
+
102
+ new Worker(queueName, processor, { connection, telemetry });
103
+ ```
104
+
105
+ The adapter uses the global trace and meter providers installed by the Node
106
+ runtime. BullMQ owns propagation metadata in its job options; applications must
107
+ not copy or mutate queue payloads solely to carry trace context. BullMQ metrics
108
+ use queue name, bounded job name, and job state dimensions, never Moda job,
109
+ shop, checkout, conversation, or message IDs.
110
+
111
+ ### GenAI active spans
112
+
113
+ Use the GenAI helpers to model one inbound conversation turn as an independent
114
+ trace with active agent, tool, and automatically instrumented child spans:
115
+
116
+ ```ts
117
+ import {
118
+ observeAgentInvocation,
119
+ observeAgentTool,
120
+ observeConversationTurn,
121
+ } from "@modainteract/moda-interact-shared/observability/genai";
122
+
123
+ await observeConversationTurn("whatsapp", () =>
124
+ observeAgentInvocation({ agentName: "commerce-agent" }, () =>
125
+ observeAgentTool("lookup-products", executeTool),
126
+ ),
127
+ );
128
+ ```
129
+
130
+ The helpers use only the global tracer and meter providers installed by the
131
+ Node runtime. They create no SDK, provider, exporter, or network request, and
132
+ remain lightweight no-ops when providers are absent. Six module-singleton
133
+ instruments record turn, agent, and tool duration and operation outcomes.
134
+ Metric attributes are limited to `outcome=success|error`; turn metrics also use
135
+ the closed `channel=whatsapp|other` vocabulary. Arbitrary agent, provider,
136
+ model, tool, and Moda business identifiers never become metric dimensions.
137
+
138
+ Agent, provider, model, and tool names are trimmed and bounded on spans only.
139
+ Prompt, completion, message, and tool payload bodies are never accepted or
140
+ captured by default.
141
+
35
142
  ## What this package does not own
36
143
 
37
144
  This package does **not** own:
@@ -42,13 +149,13 @@ This package does **not** own:
42
149
  - Shopify or Meta webhook HTTP handlers;
43
150
  - Shopify or Meta SDK integration;
44
151
  - application recovery/business logic;
45
- - OpenTelemetry spans or application-specific metrics;
46
- - OpenTelemetry tracing/metrics SDK bootstraps;
152
+ - service/domain-specific span names, business attributes, or application
153
+ metrics;
47
154
  - deployment OTLP endpoint/credential wiring or observability backends;
48
155
  - Render deployment configuration.
49
156
 
50
- Those capabilities remain in the repository that owns the corresponding
51
- runtime concern.
157
+ Those service-specific semantics and deployment concerns remain in the
158
+ repository that owns the corresponding runtime.
52
159
 
53
160
  ---
54
161
 
@@ -503,7 +610,7 @@ not recursively written back through the shared logger.
503
610
 
504
611
  ### Loki labels and cardinality
505
612
 
506
- The only default Loki labels are the canonical low-cardinality identity:
613
+ Moda explicitly configures exactly three canonical low-cardinality labels:
507
614
 
508
615
  ```text
509
616
  service_namespace
@@ -511,9 +618,15 @@ service_name
511
618
  environment
512
619
  ```
513
620
 
621
+ `winston-loki@6.1.7` additionally adds its own low-cardinality `level` label
622
+ to every pushed stream, so a stream carries the three Moda labels plus
623
+ `level`. This is transport-library behaviour and does not change Moda's label
624
+ configuration.
625
+
514
626
  Dynamic/high-cardinality identifiers must **not** be promoted to Loki stream
515
627
  labels. The implementation explicitly retains
516
- `useWinstonMetaAsLabels: false`. In particular, do not label by:
628
+ `useWinstonMetaAsLabels: false`, so winston metadata is never promoted to
629
+ labels. In particular, do not label by:
517
630
 
518
631
  ```text
519
632
  traceId
@@ -573,12 +686,14 @@ choice.
573
686
  - stdout and OpenTelemetry emission are isolated and best-effort: a failure in
574
687
  either must not change application/business correctness.
575
688
 
576
- ### Tracing/metrics remain service-specific
689
+ ### Tracing/metrics ownership
577
690
 
578
691
  The shared logger does **not** create spans, counters or histograms.
579
692
 
580
- Service-specific OpenTelemetry tracing and metrics remain in the owning runtime
581
- repository, for example:
693
+ The shared observability runtime owns the generic Node SDK, tracing and metrics
694
+ provider mechanics, exporters, sampling, HTTP/Undici instrumentation, generic
695
+ helpers, and lifecycle. Service repositories own their span names, business
696
+ attributes, application-specific metrics, and domain semantics, for example:
582
697
 
583
698
  ```text
584
699
  Shopify operation
@@ -615,6 +730,19 @@ The public package exposes independent entry points.
615
730
  Node-only logging bootstrap
616
731
  (OpenTelemetry LoggerProvider, batching, OTLP HTTP exporter;
617
732
  Winston + winston-loki direct transport)
733
+
734
+ @modainteract/moda-interact-shared/observability
735
+ browser-safe generic observability helpers
736
+
737
+ @modainteract/moda-interact-shared/observability/node
738
+ reusable Node OpenTelemetry SDK, tracing/metrics providers, exporters,
739
+ sampling, HTTP/Undici instrumentation, and lifecycle
740
+
741
+ @modainteract/moda-interact-shared/observability/bullmq
742
+ Node-only BullMQ native telemetry adapter
743
+
744
+ @modainteract/moda-interact-shared/observability/genai
745
+ Node/runtime GenAI conversation-turn, agent, and tool active-span helpers
618
746
  ```
619
747
 
620
748
  Consumers should prefer the narrowest appropriate subpath.
@@ -631,27 +759,31 @@ Examples of repository ownership:
631
759
  - normalizes/validates Shopify events;
632
760
  - publishes the appropriate queue jobs;
633
761
  - uses the shared structured logger for generic log mechanics;
634
- - owns Shopify-specific observability semantics and OpenTelemetry.
762
+ - uses the shared observability runtime for generic OpenTelemetry mechanics;
763
+ - owns Shopify-specific observability semantics and application metrics.
635
764
 
636
765
  **moda-interact-background**
637
766
 
638
767
  - parses shared cross-service contracts before acting;
639
768
  - runs BullMQ workers and recovery workflows;
640
769
  - uses the shared logger for generic structured logging;
641
- - owns worker/recovery OpenTelemetry.
770
+ - uses the shared observability runtime for generic OpenTelemetry mechanics;
771
+ - owns worker/recovery observability semantics and application metrics.
642
772
 
643
773
  **moda-interact-messaging**
644
774
 
645
775
  - owns Meta/WhatsApp ingress;
646
776
  - uses the shared logger for generic structured logging;
647
- - owns Meta/WhatsApp-specific observability and OpenTelemetry.
777
+ - uses the shared observability runtime for generic OpenTelemetry mechanics;
778
+ - owns Meta/WhatsApp-specific observability semantics and application metrics.
648
779
 
649
780
  **moda-interact-admin**
650
781
 
651
782
  - owns the internal admin application;
652
783
  - may use the same shared logger where server-side operational logging is
653
784
  required;
654
- - owns admin-specific application telemetry.
785
+ - uses the shared observability runtime for generic OpenTelemetry mechanics;
786
+ - owns admin-specific observability semantics and application metrics.
655
787
 
656
788
  **moda-interact-database**
657
789
 
@@ -0,0 +1,374 @@
1
+ import {
2
+ setLokiLogEmitter
3
+ } from "./chunk-XU4XX7SD.js";
4
+
5
+ // src/logging/node.ts
6
+ import { logs } from "@opentelemetry/api-logs";
7
+ import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
8
+ import { resourceFromAttributes } from "@opentelemetry/resources";
9
+ import {
10
+ BatchLogRecordProcessor,
11
+ LoggerProvider
12
+ } from "@opentelemetry/sdk-logs";
13
+
14
+ // src/logging/node/loki.ts
15
+ import {
16
+ createLogger as createWinstonLogger,
17
+ format
18
+ } from "winston";
19
+ import LokiTransport from "winston-loki";
20
+ var DEFAULT_SERVICE_NAMESPACE = "moda-interact";
21
+ var runtime = null;
22
+ function requireNonEmpty(value, field) {
23
+ const normalized = value.trim();
24
+ if (!normalized) {
25
+ throw new Error(`${field} must be a non-empty string`);
26
+ }
27
+ return normalized;
28
+ }
29
+ function initNodeLokiLogging(options) {
30
+ if (runtime) {
31
+ return runtime;
32
+ }
33
+ const serviceName = requireNonEmpty(options.serviceName, "serviceName");
34
+ const environment = requireNonEmpty(options.environment, "environment");
35
+ const serviceNamespace = requireNonEmpty(
36
+ options.serviceNamespace ?? DEFAULT_SERVICE_NAMESPACE,
37
+ "serviceNamespace"
38
+ );
39
+ const host = nonEmpty(options.host) ?? envString("LOKI_URL");
40
+ const enabled = options.forceEnable === true || envBool("LOKI_ENABLED") !== false && host !== void 0;
41
+ const labels = Object.freeze({
42
+ service_namespace: serviceNamespace,
43
+ service_name: serviceName,
44
+ environment
45
+ });
46
+ if (!enabled || !host) {
47
+ setLokiLogEmitter(null);
48
+ runtime = {
49
+ enabled: false,
50
+ host: null,
51
+ labels,
52
+ forceFlush: async () => {
53
+ },
54
+ shutdown: async () => {
55
+ }
56
+ };
57
+ return runtime;
58
+ }
59
+ const basicAuth = nonEmpty(options.basicAuth) ?? buildBasicAuth(
60
+ envString("LOKI_USERNAME"),
61
+ envString("LOKI_PASSWORD")
62
+ );
63
+ const transport = new LokiTransport({
64
+ host,
65
+ json: true,
66
+ batching: options.batching ?? envBool("LOKI_BATCHING") ?? true,
67
+ interval: options.intervalSeconds ?? envInt("LOKI_BATCH_INTERVAL_SECONDS", 5),
68
+ timeout: options.timeoutMs ?? envInt("LOKI_TIMEOUT_MS", 1e4),
69
+ // Prefer bounded loss to unbounded memory growth when the logging backend
70
+ // is unavailable. Loki must never become an application dependency.
71
+ clearOnError: options.clearOnError ?? envBool("LOKI_CLEAR_ON_ERROR") ?? true,
72
+ // Current winston-loki guidance recommends keeping timestamp replacement
73
+ // enabled unless there is a specific reason not to.
74
+ replaceTimestamp: true,
75
+ // Moda owns flush/shutdown explicitly from the process bootstrap.
76
+ gracefulShutdown: false,
77
+ // Critical cardinality rule: only these architecture-approved static
78
+ // labels may be indexed. Never promote Winston meta/data to labels.
79
+ labels,
80
+ useWinstonMetaAsLabels: false,
81
+ ...basicAuth ? { basicAuth } : {},
82
+ ...options.headers ? { headers: options.headers } : {},
83
+ // Do not recursively log Loki connection errors through the shared logger.
84
+ // Doing so could create a failure loop while Loki is unavailable.
85
+ onConnectionError: () => {
86
+ }
87
+ });
88
+ const lokiLogger = createWinstonLogger({
89
+ level: "debug",
90
+ format: format.json(),
91
+ transports: [transport],
92
+ exitOnError: false
93
+ });
94
+ setLokiLogEmitter((record) => {
95
+ writeCanonicalRecord(lokiLogger, record);
96
+ });
97
+ runtime = {
98
+ enabled: true,
99
+ host,
100
+ labels,
101
+ forceFlush: async () => {
102
+ await safeFlush(transport);
103
+ },
104
+ shutdown: async () => {
105
+ setLokiLogEmitter(null);
106
+ await safeFlush(transport);
107
+ await safeCloseTransport(transport);
108
+ try {
109
+ lokiLogger.close();
110
+ } catch {
111
+ }
112
+ }
113
+ };
114
+ return runtime;
115
+ }
116
+ function getNodeLokiLoggingRuntime() {
117
+ return runtime;
118
+ }
119
+ function writeCanonicalRecord(logger, record) {
120
+ try {
121
+ logger.log({
122
+ ...record,
123
+ level: record.level,
124
+ message: record.event
125
+ });
126
+ } catch {
127
+ }
128
+ }
129
+ async function safeFlush(transport) {
130
+ try {
131
+ await transport.flush();
132
+ } catch {
133
+ }
134
+ }
135
+ async function safeCloseTransport(transport) {
136
+ try {
137
+ const closeable = transport;
138
+ if (typeof closeable.close === "function") {
139
+ await Promise.resolve(closeable.close());
140
+ }
141
+ } catch {
142
+ }
143
+ }
144
+ function buildBasicAuth(username, password) {
145
+ if (!username || !password) {
146
+ return void 0;
147
+ }
148
+ return `${username}:${password}`;
149
+ }
150
+ function envString(name) {
151
+ return nonEmpty(process.env[name]);
152
+ }
153
+ function nonEmpty(value) {
154
+ if (value === void 0) {
155
+ return void 0;
156
+ }
157
+ const normalized = value.trim();
158
+ return normalized.length > 0 ? normalized : void 0;
159
+ }
160
+ function envBool(name) {
161
+ const value = envString(name);
162
+ if (value === void 0) {
163
+ return void 0;
164
+ }
165
+ const normalized = value.toLowerCase();
166
+ if (["1", "true", "yes", "on"].includes(normalized)) {
167
+ return true;
168
+ }
169
+ if (["0", "false", "no", "off"].includes(normalized)) {
170
+ return false;
171
+ }
172
+ return void 0;
173
+ }
174
+ function envInt(name, fallback) {
175
+ const value = envString(name);
176
+ if (value === void 0) {
177
+ return fallback;
178
+ }
179
+ const parsed = Number(value);
180
+ return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
181
+ }
182
+
183
+ // src/logging/node.ts
184
+ var DEFAULT_SERVICE_NAMESPACE2 = "moda-interact";
185
+ var runtime2 = null;
186
+ function requireNonEmpty2(value, field) {
187
+ const normalized = value.trim();
188
+ if (!normalized) {
189
+ throw new Error(`${field} must be a non-empty string`);
190
+ }
191
+ return normalized;
192
+ }
193
+ function initNodeOpenTelemetryLogging(options) {
194
+ if (runtime2) {
195
+ return runtime2;
196
+ }
197
+ const serviceName = requireNonEmpty2(options.serviceName, "serviceName");
198
+ const environment = requireNonEmpty2(options.environment, "environment");
199
+ const serviceNamespace = requireNonEmpty2(
200
+ options.serviceNamespace ?? DEFAULT_SERVICE_NAMESPACE2,
201
+ "serviceNamespace"
202
+ );
203
+ const config = resolveLogsConfig(options);
204
+ if (!config.enabled || !config.endpoint) {
205
+ runtime2 = {
206
+ enabled: false,
207
+ endpoint: null,
208
+ forceFlush: async () => {
209
+ },
210
+ shutdown: async () => {
211
+ }
212
+ };
213
+ return runtime2;
214
+ }
215
+ const exporter = new OTLPLogExporter({
216
+ url: config.endpoint,
217
+ headers: config.headers,
218
+ concurrencyLimit: envInt2("OTEL_LOG_EXPORT_CONCURRENCY_LIMIT", 2)
219
+ });
220
+ const processor = new BatchLogRecordProcessor({
221
+ exporter,
222
+ maxQueueSize: envInt2("OTEL_BLRP_MAX_QUEUE_SIZE", 2048),
223
+ maxExportBatchSize: envInt2("OTEL_BLRP_MAX_EXPORT_BATCH_SIZE", 512),
224
+ scheduledDelayMillis: envInt2("OTEL_BLRP_SCHEDULE_DELAY", 1e3),
225
+ exportTimeoutMillis: envInt2("OTEL_BLRP_EXPORT_TIMEOUT", 3e4)
226
+ });
227
+ const provider = new LoggerProvider({
228
+ resource: resourceFromAttributes({
229
+ "service.namespace": serviceNamespace,
230
+ "service.name": serviceName,
231
+ "deployment.environment.name": environment
232
+ }),
233
+ processors: [processor],
234
+ logRecordLimits: {
235
+ attributeCountLimit: 64,
236
+ attributeValueLengthLimit: 4096
237
+ }
238
+ });
239
+ logs.setGlobalLoggerProvider(provider);
240
+ runtime2 = {
241
+ enabled: true,
242
+ endpoint: config.endpoint,
243
+ forceFlush: async () => {
244
+ try {
245
+ await provider.forceFlush();
246
+ } catch {
247
+ }
248
+ },
249
+ shutdown: async () => {
250
+ try {
251
+ await provider.shutdown();
252
+ } catch {
253
+ }
254
+ }
255
+ };
256
+ return runtime2;
257
+ }
258
+ function getNodeOpenTelemetryLoggingRuntime() {
259
+ return runtime2;
260
+ }
261
+ function resolveLogsEndpoint(explicitEndpoint) {
262
+ const explicit = nonEmpty2(explicitEndpoint);
263
+ if (explicit) {
264
+ return explicit;
265
+ }
266
+ const signalEndpoint = envString2("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT");
267
+ if (signalEndpoint) {
268
+ return signalEndpoint;
269
+ }
270
+ const genericEndpoint = envString2("OTEL_EXPORTER_OTLP_ENDPOINT");
271
+ if (!genericEndpoint) {
272
+ return void 0;
273
+ }
274
+ const normalized = genericEndpoint.replace(/\/+$/, "");
275
+ if (normalized.endsWith("/v1/logs")) {
276
+ return normalized;
277
+ }
278
+ return `${normalized}/v1/logs`;
279
+ }
280
+ function parseOtlpHeaders(value) {
281
+ const raw = nonEmpty2(value);
282
+ if (!raw) {
283
+ return void 0;
284
+ }
285
+ const headers = {};
286
+ for (const part of raw.split(",")) {
287
+ const separator = part.indexOf("=");
288
+ if (separator <= 0) {
289
+ continue;
290
+ }
291
+ const rawKey = part.slice(0, separator).trim();
292
+ const rawValue = part.slice(separator + 1).trim();
293
+ if (!rawKey) {
294
+ continue;
295
+ }
296
+ headers[safeDecodeURIComponent(rawKey)] = safeDecodeURIComponent(rawValue);
297
+ }
298
+ return Object.keys(headers).length > 0 ? headers : void 0;
299
+ }
300
+ function resolveLogsConfig(options) {
301
+ if (!options.forceEnable && envBool2("OTEL_SDK_DISABLED") === true) {
302
+ return {
303
+ enabled: false,
304
+ endpoint: void 0,
305
+ headers: void 0
306
+ };
307
+ }
308
+ const exporterName = envString2("OTEL_LOGS_EXPORTER") ?? "otlp";
309
+ if (!options.forceEnable && exporterName !== "otlp") {
310
+ return {
311
+ enabled: false,
312
+ endpoint: void 0,
313
+ headers: void 0
314
+ };
315
+ }
316
+ const endpoint = resolveLogsEndpoint(options.endpoint);
317
+ if (!endpoint) {
318
+ return {
319
+ enabled: false,
320
+ endpoint: void 0,
321
+ headers: void 0
322
+ };
323
+ }
324
+ const headers = options.headers ?? parseOtlpHeaders(
325
+ envString2("OTEL_EXPORTER_OTLP_LOGS_HEADERS") ?? envString2("OTEL_EXPORTER_OTLP_HEADERS")
326
+ );
327
+ return {
328
+ enabled: true,
329
+ endpoint,
330
+ headers
331
+ };
332
+ }
333
+ function envString2(name) {
334
+ return nonEmpty2(process.env[name]);
335
+ }
336
+ function nonEmpty2(value) {
337
+ if (value === void 0) {
338
+ return void 0;
339
+ }
340
+ const trimmed = value.trim();
341
+ return trimmed.length > 0 ? trimmed : void 0;
342
+ }
343
+ function envBool2(name) {
344
+ const value = envString2(name);
345
+ if (value === void 0) {
346
+ return void 0;
347
+ }
348
+ return ["1", "true", "yes", "on"].includes(value.toLowerCase());
349
+ }
350
+ function envInt2(name, fallback) {
351
+ const value = envString2(name);
352
+ if (value === void 0) {
353
+ return fallback;
354
+ }
355
+ const parsed = Number(value);
356
+ return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
357
+ }
358
+ function safeDecodeURIComponent(value) {
359
+ try {
360
+ return decodeURIComponent(value);
361
+ } catch {
362
+ return value;
363
+ }
364
+ }
365
+
366
+ export {
367
+ initNodeLokiLogging,
368
+ getNodeLokiLoggingRuntime,
369
+ initNodeOpenTelemetryLogging,
370
+ getNodeOpenTelemetryLoggingRuntime,
371
+ resolveLogsEndpoint,
372
+ parseOtlpHeaders
373
+ };
374
+ //# sourceMappingURL=chunk-5JNEZEKV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/logging/node.ts","../src/logging/node/loki.ts"],"sourcesContent":["import { logs } from \"@opentelemetry/api-logs\";\nimport { OTLPLogExporter } from \"@opentelemetry/exporter-logs-otlp-http\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport {\n BatchLogRecordProcessor,\n LoggerProvider,\n} from \"@opentelemetry/sdk-logs\";\n\nexport {\n getNodeLokiLoggingRuntime,\n initNodeLokiLogging,\n} from \"./node/loki.js\";\nexport type {\n NodeLokiLoggingOptions,\n NodeLokiLoggingRuntime,\n} from \"./node/loki.js\";\n\nconst DEFAULT_SERVICE_NAMESPACE = \"moda-interact\";\n\nexport type NodeOpenTelemetryLoggingOptions = {\n serviceName: string;\n environment: string;\n serviceNamespace?: string;\n\n /**\n * Explicit endpoint override. Intended mainly for tests.\n *\n * Production normally uses:\n * OTEL_EXPORTER_OTLP_LOGS_ENDPOINT\n * or:\n * OTEL_EXPORTER_OTLP_ENDPOINT\n */\n endpoint?: string;\n\n /** Optional explicit OTLP headers override. */\n headers?: Record<string, string>;\n\n /** Test-only switch. Production should rely on environment configuration. */\n forceEnable?: boolean;\n};\n\nexport type NodeOpenTelemetryLoggingRuntime = {\n enabled: boolean;\n endpoint: string | null;\n forceFlush: () => Promise<void>;\n shutdown: () => Promise<void>;\n};\n\nlet runtime: NodeOpenTelemetryLoggingRuntime | null = null;\n\nfunction requireNonEmpty(value: string, field: string): string {\n const normalized = value.trim();\n if (!normalized) {\n throw new Error(`${field} must be a non-empty string`);\n }\n return normalized;\n}\n\n/**\n * Installs the Node OpenTelemetry LoggerProvider exactly once for this process.\n *\n * IMPORTANT:\n * This module is Node-only. Import and initialize it from the process bootstrap\n * before framework/application modules are loaded. Do not import this module\n * through Vite/React/browser bundles.\n *\n * Application modules should import only:\n *\n * @modainteract/moda-interact-shared/logging\n *\n * Once this provider is installed, createLogger(...) automatically fans safe\n * canonical log records out to stdout and the OpenTelemetry Logs API.\n *\n * Direct Loki delivery, when desired, is independently enabled with:\n *\n * initNodeLokiLogging(...)\n *\n * from this same Node-only package entry.\n */\nexport function initNodeOpenTelemetryLogging(\n options: NodeOpenTelemetryLoggingOptions,\n): NodeOpenTelemetryLoggingRuntime {\n if (runtime) {\n return runtime;\n }\n\n const serviceName = requireNonEmpty(options.serviceName, \"serviceName\");\n const environment = requireNonEmpty(options.environment, \"environment\");\n const serviceNamespace = requireNonEmpty(\n options.serviceNamespace ?? DEFAULT_SERVICE_NAMESPACE,\n \"serviceNamespace\",\n );\n\n const config = resolveLogsConfig(options);\n\n if (!config.enabled || !config.endpoint) {\n runtime = {\n enabled: false,\n endpoint: null,\n forceFlush: async () => {},\n shutdown: async () => {},\n };\n return runtime;\n }\n\n const exporter = new OTLPLogExporter({\n url: config.endpoint,\n headers: config.headers,\n concurrencyLimit: envInt(\"OTEL_LOG_EXPORT_CONCURRENCY_LIMIT\", 2),\n });\n\n const processor = new BatchLogRecordProcessor({\n exporter,\n maxQueueSize: envInt(\"OTEL_BLRP_MAX_QUEUE_SIZE\", 2048),\n maxExportBatchSize: envInt(\"OTEL_BLRP_MAX_EXPORT_BATCH_SIZE\", 512),\n scheduledDelayMillis: envInt(\"OTEL_BLRP_SCHEDULE_DELAY\", 1_000),\n exportTimeoutMillis: envInt(\"OTEL_BLRP_EXPORT_TIMEOUT\", 30_000),\n });\n\n const provider = new LoggerProvider({\n resource: resourceFromAttributes({\n \"service.namespace\": serviceNamespace,\n \"service.name\": serviceName,\n \"deployment.environment.name\": environment,\n }),\n processors: [processor],\n logRecordLimits: {\n attributeCountLimit: 64,\n attributeValueLengthLimit: 4096,\n },\n });\n\n logs.setGlobalLoggerProvider(provider);\n\n runtime = {\n enabled: true,\n endpoint: config.endpoint,\n forceFlush: async () => {\n try {\n await provider.forceFlush();\n } catch {\n // Telemetry delivery is best-effort.\n }\n },\n shutdown: async () => {\n try {\n await provider.shutdown();\n } catch {\n // Telemetry shutdown is best-effort.\n }\n },\n };\n\n return runtime;\n}\n\nexport function getNodeOpenTelemetryLoggingRuntime():\n | NodeOpenTelemetryLoggingRuntime\n | null {\n return runtime;\n}\n\nexport function resolveLogsEndpoint(\n explicitEndpoint?: string,\n): string | undefined {\n const explicit = nonEmpty(explicitEndpoint);\n if (explicit) {\n return explicit;\n }\n\n const signalEndpoint = envString(\"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT\");\n if (signalEndpoint) {\n return signalEndpoint;\n }\n\n const genericEndpoint = envString(\"OTEL_EXPORTER_OTLP_ENDPOINT\");\n if (!genericEndpoint) {\n return undefined;\n }\n\n const normalized = genericEndpoint.replace(/\\/+$/, \"\");\n if (normalized.endsWith(\"/v1/logs\")) {\n return normalized;\n }\n return `${normalized}/v1/logs`;\n}\n\nexport function parseOtlpHeaders(value: string | undefined):\n | Record<string, string>\n | undefined {\n const raw = nonEmpty(value);\n if (!raw) {\n return undefined;\n }\n\n const headers: Record<string, string> = {};\n\n for (const part of raw.split(\",\")) {\n const separator = part.indexOf(\"=\");\n if (separator <= 0) {\n continue;\n }\n\n const rawKey = part.slice(0, separator).trim();\n const rawValue = part.slice(separator + 1).trim();\n\n if (!rawKey) {\n continue;\n }\n\n headers[safeDecodeURIComponent(rawKey)] =\n safeDecodeURIComponent(rawValue);\n }\n\n return Object.keys(headers).length > 0 ? headers : undefined;\n}\n\nfunction resolveLogsConfig(options: NodeOpenTelemetryLoggingOptions) {\n if (!options.forceEnable && envBool(\"OTEL_SDK_DISABLED\") === true) {\n return {\n enabled: false,\n endpoint: undefined,\n headers: undefined,\n };\n }\n\n const exporterName = envString(\"OTEL_LOGS_EXPORTER\") ?? \"otlp\";\n\n if (!options.forceEnable && exporterName !== \"otlp\") {\n return {\n enabled: false,\n endpoint: undefined,\n headers: undefined,\n };\n }\n\n const endpoint = resolveLogsEndpoint(options.endpoint);\n\n if (!endpoint) {\n return {\n enabled: false,\n endpoint: undefined,\n headers: undefined,\n };\n }\n\n const headers =\n options.headers ??\n parseOtlpHeaders(\n envString(\"OTEL_EXPORTER_OTLP_LOGS_HEADERS\") ??\n envString(\"OTEL_EXPORTER_OTLP_HEADERS\"),\n );\n\n return {\n enabled: true,\n endpoint,\n headers,\n };\n}\n\nfunction envString(name: string): string | undefined {\n return nonEmpty(process.env[name]);\n}\n\nfunction nonEmpty(value: string | undefined): string | undefined {\n if (value === undefined) {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction envBool(name: string): boolean | undefined {\n const value = envString(name);\n if (value === undefined) {\n return undefined;\n }\n return [\"1\", \"true\", \"yes\", \"on\"].includes(value.toLowerCase());\n}\n\nfunction envInt(name: string, fallback: number): number {\n const value = envString(name);\n if (value === undefined) {\n return fallback;\n }\n\n const parsed = Number(value);\n return Number.isFinite(parsed) && parsed > 0\n ? Math.floor(parsed)\n : fallback;\n}\n\nfunction safeDecodeURIComponent(value: string): string {\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n}\n","import {\n createLogger as createWinstonLogger,\n format,\n type Logger as WinstonLogger,\n} from \"winston\";\nimport LokiTransport from \"winston-loki\";\nimport { setLokiLogEmitter } from \"../loki.js\";\nimport type { LogRecord } from \"../types.js\";\n\nconst DEFAULT_SERVICE_NAMESPACE = \"moda-interact\";\n\nexport type NodeLokiLoggingOptions = {\n serviceName: string;\n environment: string;\n serviceNamespace?: string;\n\n /**\n * Explicit Loki base URL.\n *\n * Production normally supplies LOKI_URL.\n */\n host?: string;\n\n /**\n * Optional Basic Auth override in `username:password` format.\n *\n * Prefer separate LOKI_USERNAME/LOKI_PASSWORD environment variables in\n * deployed services so the composed value never needs to be committed.\n */\n basicAuth?: string;\n\n /**\n * Optional additional static headers.\n *\n * Never include these in log payloads or error messages.\n */\n headers?: Record<string, string>;\n\n batching?: boolean;\n intervalSeconds?: number;\n timeoutMs?: number;\n clearOnError?: boolean;\n\n /**\n * Test-only override. In normal service bootstrap, the presence of a Loki\n * host is sufficient to enable the transport.\n */\n forceEnable?: boolean;\n};\n\nexport type NodeLokiLoggingRuntime = {\n enabled: boolean;\n host: string | null;\n labels: Readonly<Record<string, string>>;\n forceFlush: () => Promise<void>;\n shutdown: () => Promise<void>;\n};\n\nlet runtime: NodeLokiLoggingRuntime | null = null;\n\nfunction requireNonEmpty(value: string, field: string): string {\n const normalized = value.trim();\n if (!normalized) {\n throw new Error(`${field} must be a non-empty string`);\n }\n return normalized;\n}\n\n/**\n * Installs the direct Grafana Loki destination exactly once per Node process.\n *\n * IMPORTANT:\n * - This module is Node-only.\n * - It is reached through @modainteract/moda-interact-shared/logging/node.\n * - Do not import it from Vite/browser/application module graphs.\n * - The normal ./logging entry contains no Winston/winston-loki imports.\n */\nexport function initNodeLokiLogging(\n options: NodeLokiLoggingOptions,\n): NodeLokiLoggingRuntime {\n if (runtime) {\n return runtime;\n }\n\n const serviceName = requireNonEmpty(options.serviceName, \"serviceName\");\n const environment = requireNonEmpty(options.environment, \"environment\");\n const serviceNamespace = requireNonEmpty(\n options.serviceNamespace ?? DEFAULT_SERVICE_NAMESPACE,\n \"serviceNamespace\",\n );\n\n const host = nonEmpty(options.host) ?? envString(\"LOKI_URL\");\n const enabled =\n options.forceEnable === true ||\n (envBool(\"LOKI_ENABLED\") !== false && host !== undefined);\n\n const labels = Object.freeze({\n service_namespace: serviceNamespace,\n service_name: serviceName,\n environment,\n });\n\n if (!enabled || !host) {\n setLokiLogEmitter(null);\n\n runtime = {\n enabled: false,\n host: null,\n labels,\n forceFlush: async () => {},\n shutdown: async () => {},\n };\n\n return runtime;\n }\n\n const basicAuth =\n nonEmpty(options.basicAuth) ??\n buildBasicAuth(\n envString(\"LOKI_USERNAME\"),\n envString(\"LOKI_PASSWORD\"),\n );\n\n const transport = new LokiTransport({\n host,\n json: true,\n batching: options.batching ?? envBool(\"LOKI_BATCHING\") ?? true,\n interval:\n options.intervalSeconds ??\n envInt(\"LOKI_BATCH_INTERVAL_SECONDS\", 5),\n timeout:\n options.timeoutMs ??\n envInt(\"LOKI_TIMEOUT_MS\", 10_000),\n\n // Prefer bounded loss to unbounded memory growth when the logging backend\n // is unavailable. Loki must never become an application dependency.\n clearOnError:\n options.clearOnError ??\n envBool(\"LOKI_CLEAR_ON_ERROR\") ??\n true,\n\n // Current winston-loki guidance recommends keeping timestamp replacement\n // enabled unless there is a specific reason not to.\n replaceTimestamp: true,\n\n // Moda owns flush/shutdown explicitly from the process bootstrap.\n gracefulShutdown: false,\n\n // Critical cardinality rule: only these architecture-approved static\n // labels may be indexed. Never promote Winston meta/data to labels.\n labels,\n useWinstonMetaAsLabels: false,\n\n ...(basicAuth ? { basicAuth } : {}),\n ...(options.headers ? { headers: options.headers } : {}),\n\n // Do not recursively log Loki connection errors through the shared logger.\n // Doing so could create a failure loop while Loki is unavailable.\n onConnectionError: () => {},\n });\n\n const lokiLogger = createWinstonLogger({\n level: \"debug\",\n format: format.json(),\n transports: [transport],\n exitOnError: false,\n });\n\n setLokiLogEmitter((record) => {\n writeCanonicalRecord(lokiLogger, record);\n });\n\n runtime = {\n enabled: true,\n host,\n labels,\n forceFlush: async () => {\n await safeFlush(transport);\n },\n shutdown: async () => {\n setLokiLogEmitter(null);\n await safeFlush(transport);\n await safeCloseTransport(transport);\n\n try {\n lokiLogger.close();\n } catch {\n // best-effort\n }\n },\n };\n\n return runtime;\n}\n\nexport function getNodeLokiLoggingRuntime():\n | NodeLokiLoggingRuntime\n | null {\n return runtime;\n}\n\nfunction writeCanonicalRecord(\n logger: WinstonLogger,\n record: LogRecord,\n): void {\n try {\n // Winston requires a `message`; use the canonical event name.\n //\n // Every other value comes from the already-sanitized immutable Moda\n // LogRecord. Because useWinstonMetaAsLabels=false, these values remain in\n // the JSON log line rather than becoming Loki stream labels.\n logger.log({\n ...record,\n level: record.level,\n message: record.event,\n });\n } catch {\n // Direct Loki emission must never escape to business code.\n }\n}\n\nasync function safeFlush(transport: LokiTransport): Promise<void> {\n try {\n await transport.flush();\n } catch {\n // Logs may be dropped. Business processing must continue.\n }\n}\n\nasync function safeCloseTransport(\n transport: LokiTransport,\n): Promise<void> {\n try {\n const closeable = transport as LokiTransport & {\n close?: () => unknown | Promise<unknown>;\n };\n\n if (typeof closeable.close === \"function\") {\n await Promise.resolve(closeable.close());\n }\n } catch {\n // best-effort shutdown\n }\n}\n\nfunction buildBasicAuth(\n username: string | undefined,\n password: string | undefined,\n): string | undefined {\n if (!username || !password) {\n return undefined;\n }\n\n return `${username}:${password}`;\n}\n\nfunction envString(name: string): string | undefined {\n return nonEmpty(process.env[name]);\n}\n\nfunction nonEmpty(value: string | undefined): string | undefined {\n if (value === undefined) {\n return undefined;\n }\n\n const normalized = value.trim();\n return normalized.length > 0 ? normalized : undefined;\n}\n\nfunction envBool(name: string): boolean | undefined {\n const value = envString(name);\n\n if (value === undefined) {\n return undefined;\n }\n\n const normalized = value.toLowerCase();\n\n if ([\"1\", \"true\", \"yes\", \"on\"].includes(normalized)) {\n return true;\n }\n\n if ([\"0\", \"false\", \"no\", \"off\"].includes(normalized)) {\n return false;\n }\n\n return undefined;\n}\n\nfunction envInt(name: string, fallback: number): number {\n const value = envString(name);\n\n if (value === undefined) {\n return fallback;\n }\n\n const parsed = Number(value);\n\n return Number.isFinite(parsed) && parsed > 0\n ? Math.floor(parsed)\n : fallback;\n}\n"],"mappings":"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","names":["DEFAULT_SERVICE_NAMESPACE","runtime","requireNonEmpty","envInt","nonEmpty","envString","envBool"]}