@modainteract/moda-interact-shared 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,152 @@ 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
+ Pass `recordMetrics: false` to activate a helper's span without recording its
131
+ GenAI metrics. The option applies only to that helper invocation, so pass it to
132
+ each nested helper whose metrics should be suppressed:
133
+
134
+ ```ts
135
+ const spansOnly = { recordMetrics: false } as const;
136
+
137
+ await observeConversationTurn("whatsapp", () =>
138
+ observeAgentInvocation({ agentName: "commerce-agent" }, () =>
139
+ observeAgentTool("lookup-products", executeTool, spansOnly),
140
+ spansOnly,
141
+ ),
142
+ spansOnly,
143
+ );
144
+ ```
145
+
146
+ An optional `mapException` callback can replace the application failure with a
147
+ bounded telemetry-safe representation for `span.recordException`. The original
148
+ thrown value is still rethrown unchanged. If the mapper throws or returns no
149
+ usable fields, the exception event is omitted and the span remains `ERROR`;
150
+ the original value is never recorded as fallback. Apply the mapper to every
151
+ nested helper that may observe the same rethrown failure:
152
+
153
+ ```ts
154
+ const safeObservation = {
155
+ recordMetrics: false,
156
+ mapException: () => ({
157
+ name: "ProviderError",
158
+ message: "Provider operation failed",
159
+ }),
160
+ } as const;
161
+
162
+ await observeAgentTool("lookup-products", executeTool, safeObservation);
163
+ ```
164
+
165
+ The helpers use only the global tracer and meter providers installed by the
166
+ Node runtime. They create no SDK, provider, exporter, or network request, and
167
+ remain lightweight no-ops when providers are absent. Six module-singleton
168
+ instruments record turn, agent, and tool duration and operation outcomes.
169
+ Metric attributes are limited to `outcome=success|error`; turn metrics also use
170
+ the closed `channel=whatsapp|other` vocabulary. Arbitrary agent, provider,
171
+ model, tool, and Moda business identifiers never become metric dimensions.
172
+
173
+ Agent, provider, model, and tool names are trimmed and bounded on spans only.
174
+ Prompt, completion, message, and tool payload bodies are never accepted or
175
+ captured by default.
176
+
35
177
  ## What this package does not own
36
178
 
37
179
  This package does **not** own:
@@ -42,13 +184,13 @@ This package does **not** own:
42
184
  - Shopify or Meta webhook HTTP handlers;
43
185
  - Shopify or Meta SDK integration;
44
186
  - application recovery/business logic;
45
- - OpenTelemetry spans or application-specific metrics;
46
- - OpenTelemetry tracing/metrics SDK bootstraps;
187
+ - service/domain-specific span names, business attributes, or application
188
+ metrics;
47
189
  - deployment OTLP endpoint/credential wiring or observability backends;
48
190
  - Render deployment configuration.
49
191
 
50
- Those capabilities remain in the repository that owns the corresponding
51
- runtime concern.
192
+ Those service-specific semantics and deployment concerns remain in the
193
+ repository that owns the corresponding runtime.
52
194
 
53
195
  ---
54
196
 
@@ -503,7 +645,7 @@ not recursively written back through the shared logger.
503
645
 
504
646
  ### Loki labels and cardinality
505
647
 
506
- The only default Loki labels are the canonical low-cardinality identity:
648
+ Moda explicitly configures exactly three canonical low-cardinality labels:
507
649
 
508
650
  ```text
509
651
  service_namespace
@@ -511,9 +653,15 @@ service_name
511
653
  environment
512
654
  ```
513
655
 
656
+ `winston-loki@6.1.7` additionally adds its own low-cardinality `level` label
657
+ to every pushed stream, so a stream carries the three Moda labels plus
658
+ `level`. This is transport-library behaviour and does not change Moda's label
659
+ configuration.
660
+
514
661
  Dynamic/high-cardinality identifiers must **not** be promoted to Loki stream
515
662
  labels. The implementation explicitly retains
516
- `useWinstonMetaAsLabels: false`. In particular, do not label by:
663
+ `useWinstonMetaAsLabels: false`, so winston metadata is never promoted to
664
+ labels. In particular, do not label by:
517
665
 
518
666
  ```text
519
667
  traceId
@@ -573,12 +721,14 @@ choice.
573
721
  - stdout and OpenTelemetry emission are isolated and best-effort: a failure in
574
722
  either must not change application/business correctness.
575
723
 
576
- ### Tracing/metrics remain service-specific
724
+ ### Tracing/metrics ownership
577
725
 
578
726
  The shared logger does **not** create spans, counters or histograms.
579
727
 
580
- Service-specific OpenTelemetry tracing and metrics remain in the owning runtime
581
- repository, for example:
728
+ The shared observability runtime owns the generic Node SDK, tracing and metrics
729
+ provider mechanics, exporters, sampling, HTTP/Undici instrumentation, generic
730
+ helpers, and lifecycle. Service repositories own their span names, business
731
+ attributes, application-specific metrics, and domain semantics, for example:
582
732
 
583
733
  ```text
584
734
  Shopify operation
@@ -615,6 +765,19 @@ The public package exposes independent entry points.
615
765
  Node-only logging bootstrap
616
766
  (OpenTelemetry LoggerProvider, batching, OTLP HTTP exporter;
617
767
  Winston + winston-loki direct transport)
768
+
769
+ @modainteract/moda-interact-shared/observability
770
+ browser-safe generic observability helpers
771
+
772
+ @modainteract/moda-interact-shared/observability/node
773
+ reusable Node OpenTelemetry SDK, tracing/metrics providers, exporters,
774
+ sampling, HTTP/Undici instrumentation, and lifecycle
775
+
776
+ @modainteract/moda-interact-shared/observability/bullmq
777
+ Node-only BullMQ native telemetry adapter
778
+
779
+ @modainteract/moda-interact-shared/observability/genai
780
+ Node/runtime GenAI conversation-turn, agent, and tool active-span helpers
618
781
  ```
619
782
 
620
783
  Consumers should prefer the narrowest appropriate subpath.
@@ -631,27 +794,31 @@ Examples of repository ownership:
631
794
  - normalizes/validates Shopify events;
632
795
  - publishes the appropriate queue jobs;
633
796
  - uses the shared structured logger for generic log mechanics;
634
- - owns Shopify-specific observability semantics and OpenTelemetry.
797
+ - uses the shared observability runtime for generic OpenTelemetry mechanics;
798
+ - owns Shopify-specific observability semantics and application metrics.
635
799
 
636
800
  **moda-interact-background**
637
801
 
638
802
  - parses shared cross-service contracts before acting;
639
803
  - runs BullMQ workers and recovery workflows;
640
804
  - uses the shared logger for generic structured logging;
641
- - owns worker/recovery OpenTelemetry.
805
+ - uses the shared observability runtime for generic OpenTelemetry mechanics;
806
+ - owns worker/recovery observability semantics and application metrics.
642
807
 
643
808
  **moda-interact-messaging**
644
809
 
645
810
  - owns Meta/WhatsApp ingress;
646
811
  - uses the shared logger for generic structured logging;
647
- - owns Meta/WhatsApp-specific observability and OpenTelemetry.
812
+ - uses the shared observability runtime for generic OpenTelemetry mechanics;
813
+ - owns Meta/WhatsApp-specific observability semantics and application metrics.
648
814
 
649
815
  **moda-interact-admin**
650
816
 
651
817
  - owns the internal admin application;
652
818
  - may use the same shared logger where server-side operational logging is
653
819
  required;
654
- - owns admin-specific application telemetry.
820
+ - uses the shared observability runtime for generic OpenTelemetry mechanics;
821
+ - owns admin-specific observability semantics and application metrics.
655
822
 
656
823
  **moda-interact-database**
657
824
 
@@ -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