@modainteract/moda-interact-shared 0.1.0 → 0.3.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.
@@ -0,0 +1,241 @@
1
+ import {
2
+ emitLokiLog
3
+ } from "../chunk-XU4XX7SD.js";
4
+
5
+ // src/logging/otel.ts
6
+ import { logs, SeverityNumber } from "@opentelemetry/api-logs";
7
+ var OTEL_LOGGER_SCOPE_NAME = "@modainteract/moda-interact-shared/logging";
8
+ var SEVERITY_BY_LEVEL = Object.freeze({
9
+ debug: SeverityNumber.DEBUG,
10
+ info: SeverityNumber.INFO,
11
+ warn: SeverityNumber.WARN,
12
+ error: SeverityNumber.ERROR
13
+ });
14
+ function emitOpenTelemetryLog(record) {
15
+ try {
16
+ const logger = logs.getLogger(OTEL_LOGGER_SCOPE_NAME);
17
+ logger.emit({
18
+ eventName: record.event,
19
+ timestamp: new Date(record.timestamp),
20
+ severityNumber: SEVERITY_BY_LEVEL[record.level],
21
+ severityText: record.level.toUpperCase(),
22
+ // Keep the OTLP body identical to the structured stdout record.
23
+ body: JSON.stringify(record),
24
+ attributes: {
25
+ "event.name": record.event,
26
+ "log.level": record.level,
27
+ "service.namespace": record["service.namespace"],
28
+ "service.name": record["service.name"],
29
+ "deployment.environment.name": record["deployment.environment.name"]
30
+ }
31
+ });
32
+ } catch {
33
+ }
34
+ }
35
+
36
+ // src/logging/redaction.ts
37
+ var REDACTED = "[REDACTED]";
38
+ var CIRCULAR = "[Circular]";
39
+ var MAX_DEPTH_REACHED = "[MaxDepth]";
40
+ var TRUNCATED = "[Truncated]";
41
+ var LOG_VALUE_LIMITS = Object.freeze({
42
+ maxDepth: 6,
43
+ maxArrayItems: 50,
44
+ maxObjectKeys: 100,
45
+ maxStringLength: 4096
46
+ });
47
+ var SENSITIVE_EXACT_KEYS = /* @__PURE__ */ new Set([
48
+ "authorization",
49
+ "cookie",
50
+ "setcookie",
51
+ "password",
52
+ "passwd",
53
+ "apikey",
54
+ "privatekey",
55
+ "cvv",
56
+ "cvc",
57
+ "cardnumber",
58
+ "creditcard",
59
+ "firstname",
60
+ "lastname"
61
+ ]);
62
+ function normalizeKey(key) {
63
+ return key.toLowerCase().replace(/[^a-z0-9]/g, "");
64
+ }
65
+ function isSensitiveLogKey(key) {
66
+ const normalized = normalizeKey(key);
67
+ return SENSITIVE_EXACT_KEYS.has(normalized) || normalized.includes("secret") || normalized.endsWith("token") || normalized.includes("email") || normalized.includes("phone") || normalized.endsWith("address");
68
+ }
69
+ function sanitizeString(value) {
70
+ const scrubbed = value.replace(/\bBearer\s+[^\s,;]+/gi, `Bearer ${REDACTED}`).replace(
71
+ /([?&](?:access_token|refresh_token|verify_token|client_secret|api_key|token|password)=)[^&#\s]*/gi,
72
+ `$1${REDACTED}`
73
+ );
74
+ if (scrubbed.length <= LOG_VALUE_LIMITS.maxStringLength) {
75
+ return scrubbed;
76
+ }
77
+ return `${scrubbed.slice(0, LOG_VALUE_LIMITS.maxStringLength)}\u2026${TRUNCATED}`;
78
+ }
79
+ function sanitizeError(error) {
80
+ return {
81
+ name: sanitizeString(error.name),
82
+ message: sanitizeString(error.message)
83
+ };
84
+ }
85
+ function sanitizeValue(value, depth, activeObjects) {
86
+ if (value === null || value === void 0) {
87
+ return value ?? null;
88
+ }
89
+ if (typeof value === "string") {
90
+ return sanitizeString(value);
91
+ }
92
+ if (typeof value === "number") {
93
+ return Number.isFinite(value) ? value : String(value);
94
+ }
95
+ if (typeof value === "boolean") {
96
+ return value;
97
+ }
98
+ if (typeof value === "bigint") {
99
+ return value.toString();
100
+ }
101
+ if (typeof value === "symbol" || typeof value === "function") {
102
+ return `[${typeof value}]`;
103
+ }
104
+ if (value instanceof Date) {
105
+ return Number.isNaN(value.getTime()) ? "Invalid Date" : value.toISOString();
106
+ }
107
+ if (value instanceof Error) {
108
+ return sanitizeError(value);
109
+ }
110
+ if (depth >= LOG_VALUE_LIMITS.maxDepth) {
111
+ return MAX_DEPTH_REACHED;
112
+ }
113
+ if (typeof value !== "object") {
114
+ return String(value);
115
+ }
116
+ if (activeObjects.has(value)) {
117
+ return CIRCULAR;
118
+ }
119
+ activeObjects.add(value);
120
+ try {
121
+ if (Array.isArray(value)) {
122
+ const result2 = value.slice(0, LOG_VALUE_LIMITS.maxArrayItems).map((item) => sanitizeValue(item, depth + 1, activeObjects));
123
+ if (value.length > LOG_VALUE_LIMITS.maxArrayItems) {
124
+ result2.push(TRUNCATED);
125
+ }
126
+ return result2;
127
+ }
128
+ const result = {};
129
+ const entries = Object.entries(value).slice(0, LOG_VALUE_LIMITS.maxObjectKeys);
130
+ for (const [key, item] of entries) {
131
+ result[key] = isSensitiveLogKey(key) ? REDACTED : sanitizeValue(item, depth + 1, activeObjects);
132
+ }
133
+ if (Object.keys(value).length > LOG_VALUE_LIMITS.maxObjectKeys) {
134
+ result.__truncated__ = true;
135
+ }
136
+ return result;
137
+ } finally {
138
+ activeObjects.delete(value);
139
+ }
140
+ }
141
+ function sanitizeLogFields(fields) {
142
+ return sanitizeValue(fields, 0, /* @__PURE__ */ new WeakSet());
143
+ }
144
+
145
+ // src/logging/logger.ts
146
+ var DEFAULT_SERVICE_NAMESPACE = "moda-interact";
147
+ function requireNonEmpty(value, field) {
148
+ const normalized = value.trim();
149
+ if (!normalized) {
150
+ throw new Error(`${field} must be a non-empty string`);
151
+ }
152
+ return normalized;
153
+ }
154
+ function defaultSink(record) {
155
+ const line = JSON.stringify(record);
156
+ switch (record.level) {
157
+ case "debug":
158
+ console.debug(line);
159
+ break;
160
+ case "warn":
161
+ console.warn(line);
162
+ break;
163
+ case "error":
164
+ console.error(line);
165
+ break;
166
+ case "info":
167
+ default:
168
+ console.info(line);
169
+ break;
170
+ }
171
+ }
172
+ function safeTimestamp(now) {
173
+ try {
174
+ const value = now();
175
+ if (value instanceof Date && !Number.isNaN(value.getTime())) {
176
+ return value.toISOString();
177
+ }
178
+ } catch {
179
+ }
180
+ return (/* @__PURE__ */ new Date()).toISOString();
181
+ }
182
+ function createLogger(options) {
183
+ const serviceName = requireNonEmpty(options.serviceName, "serviceName");
184
+ const environment = requireNonEmpty(options.environment, "environment");
185
+ const serviceNamespace = requireNonEmpty(
186
+ options.serviceNamespace ?? DEFAULT_SERVICE_NAMESPACE,
187
+ "serviceNamespace"
188
+ );
189
+ const sink = options.sink ?? defaultSink;
190
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
191
+ const initialBaseFields = { ...options.baseFields ?? {} };
192
+ function buildLogger(baseFields) {
193
+ function emit(level, event, fields = {}) {
194
+ let record;
195
+ try {
196
+ const eventName = event.trim() || "unknown";
197
+ const sanitizedData = sanitizeLogFields({ ...baseFields, ...fields });
198
+ record = Object.freeze({
199
+ timestamp: safeTimestamp(now),
200
+ level,
201
+ event: eventName,
202
+ "service.namespace": serviceNamespace,
203
+ "service.name": serviceName,
204
+ "deployment.environment.name": environment,
205
+ ...Object.keys(sanitizedData).length > 0 ? { data: sanitizedData } : {}
206
+ });
207
+ } catch {
208
+ return;
209
+ }
210
+ try {
211
+ sink(record);
212
+ } catch {
213
+ }
214
+ emitOpenTelemetryLog(record);
215
+ emitLokiLog(record);
216
+ }
217
+ return Object.freeze({
218
+ debug: (event, fields) => emit("debug", event, fields),
219
+ info: (event, fields) => emit("info", event, fields),
220
+ warn: (event, fields) => emit("warn", event, fields),
221
+ error: (event, fields) => emit("error", event, fields),
222
+ child: (fields) => buildLogger({ ...baseFields, ...fields })
223
+ });
224
+ }
225
+ return buildLogger(initialBaseFields);
226
+ }
227
+
228
+ // src/logging/types.ts
229
+ var LOG_LEVELS = ["debug", "info", "warn", "error"];
230
+ export {
231
+ CIRCULAR,
232
+ LOG_LEVELS,
233
+ LOG_VALUE_LIMITS,
234
+ MAX_DEPTH_REACHED,
235
+ REDACTED,
236
+ TRUNCATED,
237
+ createLogger,
238
+ isSensitiveLogKey,
239
+ sanitizeLogFields
240
+ };
241
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/logging/otel.ts","../../src/logging/redaction.ts","../../src/logging/logger.ts","../../src/logging/types.ts"],"sourcesContent":["import { logs, SeverityNumber } from \"@opentelemetry/api-logs\";\nimport type { LogLevel, LogRecord } from \"./types.js\";\n\nconst OTEL_LOGGER_SCOPE_NAME = \"@modainteract/moda-interact-shared/logging\";\n\nconst SEVERITY_BY_LEVEL: Readonly<Record<LogLevel, SeverityNumber>> = Object.freeze({\n debug: SeverityNumber.DEBUG,\n info: SeverityNumber.INFO,\n warn: SeverityNumber.WARN,\n error: SeverityNumber.ERROR,\n});\n\n/**\n * Emits the already-sanitized canonical Moda log record through the global\n * OpenTelemetry Logs API.\n *\n * If no LoggerProvider has been installed, the OpenTelemetry Logs API is a\n * no-op. This is intentional: stdout logging remains available in local/test\n * environments without an OTLP backend.\n *\n * The OpenTelemetry SDK automatically associates emitted records with the\n * active Context when a tracing context manager/provider is present. This is\n * how logs become trace/span-correlated without the shared logger depending on\n * a service-specific tracing SDK.\n */\nexport function emitOpenTelemetryLog(record: LogRecord): void {\n try {\n const logger = logs.getLogger(OTEL_LOGGER_SCOPE_NAME);\n\n logger.emit({\n eventName: record.event,\n timestamp: new Date(record.timestamp),\n severityNumber: SEVERITY_BY_LEVEL[record.level],\n severityText: record.level.toUpperCase(),\n // Keep the OTLP body identical to the structured stdout record.\n body: JSON.stringify(record),\n attributes: {\n \"event.name\": record.event,\n \"log.level\": record.level,\n \"service.namespace\": record[\"service.namespace\"],\n \"service.name\": record[\"service.name\"],\n \"deployment.environment.name\":\n record[\"deployment.environment.name\"],\n },\n });\n } catch {\n // OpenTelemetry is best-effort. It must never change business correctness.\n }\n}\n","export const REDACTED = \"[REDACTED]\";\nexport const CIRCULAR = \"[Circular]\";\nexport const MAX_DEPTH_REACHED = \"[MaxDepth]\";\nexport const TRUNCATED = \"[Truncated]\";\n\nexport const LOG_VALUE_LIMITS = Object.freeze({\n maxDepth: 6,\n maxArrayItems: 50,\n maxObjectKeys: 100,\n maxStringLength: 4096,\n});\n\nconst SENSITIVE_EXACT_KEYS = new Set([\n \"authorization\",\n \"cookie\",\n \"setcookie\",\n \"password\",\n \"passwd\",\n \"apikey\",\n \"privatekey\",\n \"cvv\",\n \"cvc\",\n \"cardnumber\",\n \"creditcard\",\n \"firstname\",\n \"lastname\",\n]);\n\nfunction normalizeKey(key: string): string {\n return key.toLowerCase().replace(/[^a-z0-9]/g, \"\");\n}\n\nexport function isSensitiveLogKey(key: string): boolean {\n const normalized = normalizeKey(key);\n\n return (\n SENSITIVE_EXACT_KEYS.has(normalized) ||\n normalized.includes(\"secret\") ||\n normalized.endsWith(\"token\") ||\n normalized.includes(\"email\") ||\n normalized.includes(\"phone\") ||\n normalized.endsWith(\"address\")\n );\n}\n\nfunction sanitizeString(value: string): string {\n const scrubbed = value\n .replace(/\\bBearer\\s+[^\\s,;]+/gi, `Bearer ${REDACTED}`)\n .replace(\n /([?&](?:access_token|refresh_token|verify_token|client_secret|api_key|token|password)=)[^&#\\s]*/gi,\n `$1${REDACTED}`,\n );\n\n if (scrubbed.length <= LOG_VALUE_LIMITS.maxStringLength) {\n return scrubbed;\n }\n\n return `${scrubbed.slice(0, LOG_VALUE_LIMITS.maxStringLength)}…${TRUNCATED}`;\n}\n\nfunction sanitizeError(error: Error): Record<string, unknown> {\n return {\n name: sanitizeString(error.name),\n message: sanitizeString(error.message),\n };\n}\n\nfunction sanitizeValue(\n value: unknown,\n depth: number,\n activeObjects: WeakSet<object>,\n): unknown {\n if (value === null || value === undefined) {\n return value ?? null;\n }\n\n if (typeof value === \"string\") {\n return sanitizeString(value);\n }\n\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : String(value);\n }\n\n if (typeof value === \"boolean\") {\n return value;\n }\n\n if (typeof value === \"bigint\") {\n return value.toString();\n }\n\n if (typeof value === \"symbol\" || typeof value === \"function\") {\n return `[${typeof value}]`;\n }\n\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? \"Invalid Date\" : value.toISOString();\n }\n\n if (value instanceof Error) {\n return sanitizeError(value);\n }\n\n if (depth >= LOG_VALUE_LIMITS.maxDepth) {\n return MAX_DEPTH_REACHED;\n }\n\n if (typeof value !== \"object\") {\n return String(value);\n }\n\n if (activeObjects.has(value)) {\n return CIRCULAR;\n }\n\n activeObjects.add(value);\n\n try {\n if (Array.isArray(value)) {\n const result = value\n .slice(0, LOG_VALUE_LIMITS.maxArrayItems)\n .map((item) => sanitizeValue(item, depth + 1, activeObjects));\n\n if (value.length > LOG_VALUE_LIMITS.maxArrayItems) {\n result.push(TRUNCATED);\n }\n\n return result;\n }\n\n const result: Record<string, unknown> = {};\n const entries = Object.entries(value).slice(0, LOG_VALUE_LIMITS.maxObjectKeys);\n\n for (const [key, item] of entries) {\n result[key] = isSensitiveLogKey(key)\n ? REDACTED\n : sanitizeValue(item, depth + 1, activeObjects);\n }\n\n if (Object.keys(value).length > LOG_VALUE_LIMITS.maxObjectKeys) {\n result.__truncated__ = true;\n }\n\n return result;\n } finally {\n activeObjects.delete(value);\n }\n}\n\nexport function sanitizeLogFields(\n fields: Readonly<Record<string, unknown>>,\n): Record<string, unknown> {\n return sanitizeValue(fields, 0, new WeakSet<object>()) as Record<string, unknown>;\n}\n","import { emitLokiLog } from \"./loki.js\";\nimport { emitOpenTelemetryLog } from \"./otel.js\";\nimport { sanitizeLogFields } from \"./redaction.js\";\nimport type {\n LogFields,\n LogLevel,\n LogRecord,\n LogSink,\n LoggerOptions,\n StructuredLogger,\n} from \"./types.js\";\n\nconst DEFAULT_SERVICE_NAMESPACE = \"moda-interact\";\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\nfunction defaultSink(record: LogRecord): void {\n const line = JSON.stringify(record);\n\n switch (record.level) {\n case \"debug\":\n console.debug(line);\n break;\n case \"warn\":\n console.warn(line);\n break;\n case \"error\":\n console.error(line);\n break;\n case \"info\":\n default:\n console.info(line);\n break;\n }\n}\n\nfunction safeTimestamp(now: () => Date): string {\n try {\n const value = now();\n if (value instanceof Date && !Number.isNaN(value.getTime())) {\n return value.toISOString();\n }\n } catch {\n // Logging must not become a correctness dependency.\n }\n\n return new Date().toISOString();\n}\n\nexport function createLogger(options: LoggerOptions): StructuredLogger {\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 const sink: LogSink = options.sink ?? defaultSink;\n const now = options.now ?? (() => new Date());\n const initialBaseFields = { ...(options.baseFields ?? {}) };\n\n function buildLogger(baseFields: Record<string, unknown>): StructuredLogger {\n function emit(level: LogLevel, event: string, fields: LogFields = {}): void {\n let record: LogRecord;\n\n try {\n const eventName = event.trim() || \"unknown\";\n const sanitizedData = sanitizeLogFields({ ...baseFields, ...fields });\n\n record = Object.freeze({\n timestamp: safeTimestamp(now),\n level,\n event: eventName,\n \"service.namespace\": serviceNamespace,\n \"service.name\": serviceName,\n \"deployment.environment.name\": environment,\n ...(Object.keys(sanitizedData).length > 0\n ? { data: sanitizedData }\n : {}),\n });\n } catch {\n // Sanitization/record construction must never affect business logic.\n return;\n }\n\n // Each destination is deliberately isolated. Failure of one destination\n // must not suppress another destination and must never escape to business\n // code.\n try {\n sink(record);\n } catch {\n // best-effort stdout/custom sink\n }\n\n emitOpenTelemetryLog(record);\n emitLokiLog(record);\n }\n\n return Object.freeze({\n debug: (event: string, fields?: LogFields) => emit(\"debug\", event, fields),\n info: (event: string, fields?: LogFields) => emit(\"info\", event, fields),\n warn: (event: string, fields?: LogFields) => emit(\"warn\", event, fields),\n error: (event: string, fields?: LogFields) => emit(\"error\", event, fields),\n child: (fields: LogFields) => buildLogger({ ...baseFields, ...fields }),\n });\n }\n\n return buildLogger(initialBaseFields);\n}\n","export const LOG_LEVELS = [\"debug\", \"info\", \"warn\", \"error\"] as const;\n\nexport type LogLevel = (typeof LOG_LEVELS)[number];\n\nexport type LogFields = Readonly<Record<string, unknown>>;\n\nexport type LogRecord = Readonly<{\n timestamp: string;\n level: LogLevel;\n event: string;\n \"service.namespace\": string;\n \"service.name\": string;\n \"deployment.environment.name\": string;\n data?: Readonly<Record<string, unknown>>;\n}>;\n\nexport type LogSink = (record: LogRecord) => void;\n\nexport type LoggerOptions = {\n serviceName: string;\n environment: string;\n serviceNamespace?: string;\n baseFields?: LogFields;\n sink?: LogSink;\n now?: () => Date;\n};\n\nexport interface StructuredLogger {\n debug(event: string, fields?: LogFields): void;\n info(event: string, fields?: LogFields): void;\n warn(event: string, fields?: LogFields): void;\n error(event: string, fields?: LogFields): void;\n child(fields: LogFields): StructuredLogger;\n}\n"],"mappings":"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","names":["result"]}
@@ -0,0 +1,103 @@
1
+ type NodeLokiLoggingOptions = {
2
+ serviceName: string;
3
+ environment: string;
4
+ serviceNamespace?: string;
5
+ /**
6
+ * Explicit Loki base URL.
7
+ *
8
+ * Production normally supplies LOKI_URL.
9
+ */
10
+ host?: string;
11
+ /**
12
+ * Optional Basic Auth override in `username:password` format.
13
+ *
14
+ * Prefer separate LOKI_USERNAME/LOKI_PASSWORD environment variables in
15
+ * deployed services so the composed value never needs to be committed.
16
+ */
17
+ basicAuth?: string;
18
+ /**
19
+ * Optional additional static headers.
20
+ *
21
+ * Never include these in log payloads or error messages.
22
+ */
23
+ headers?: Record<string, string>;
24
+ batching?: boolean;
25
+ intervalSeconds?: number;
26
+ timeoutMs?: number;
27
+ clearOnError?: boolean;
28
+ /**
29
+ * Test-only override. In normal service bootstrap, the presence of a Loki
30
+ * host is sufficient to enable the transport.
31
+ */
32
+ forceEnable?: boolean;
33
+ };
34
+ type NodeLokiLoggingRuntime = {
35
+ enabled: boolean;
36
+ host: string | null;
37
+ labels: Readonly<Record<string, string>>;
38
+ forceFlush: () => Promise<void>;
39
+ shutdown: () => Promise<void>;
40
+ };
41
+ /**
42
+ * Installs the direct Grafana Loki destination exactly once per Node process.
43
+ *
44
+ * IMPORTANT:
45
+ * - This module is Node-only.
46
+ * - It is reached through @modainteract/moda-interact-shared/logging/node.
47
+ * - Do not import it from Vite/browser/application module graphs.
48
+ * - The normal ./logging entry contains no Winston/winston-loki imports.
49
+ */
50
+ declare function initNodeLokiLogging(options: NodeLokiLoggingOptions): NodeLokiLoggingRuntime;
51
+ declare function getNodeLokiLoggingRuntime(): NodeLokiLoggingRuntime | null;
52
+
53
+ type NodeOpenTelemetryLoggingOptions = {
54
+ serviceName: string;
55
+ environment: string;
56
+ serviceNamespace?: string;
57
+ /**
58
+ * Explicit endpoint override. Intended mainly for tests.
59
+ *
60
+ * Production normally uses:
61
+ * OTEL_EXPORTER_OTLP_LOGS_ENDPOINT
62
+ * or:
63
+ * OTEL_EXPORTER_OTLP_ENDPOINT
64
+ */
65
+ endpoint?: string;
66
+ /** Optional explicit OTLP headers override. */
67
+ headers?: Record<string, string>;
68
+ /** Test-only switch. Production should rely on environment configuration. */
69
+ forceEnable?: boolean;
70
+ };
71
+ type NodeOpenTelemetryLoggingRuntime = {
72
+ enabled: boolean;
73
+ endpoint: string | null;
74
+ forceFlush: () => Promise<void>;
75
+ shutdown: () => Promise<void>;
76
+ };
77
+ /**
78
+ * Installs the Node OpenTelemetry LoggerProvider exactly once for this process.
79
+ *
80
+ * IMPORTANT:
81
+ * This module is Node-only. Import and initialize it from the process bootstrap
82
+ * before framework/application modules are loaded. Do not import this module
83
+ * through Vite/React/browser bundles.
84
+ *
85
+ * Application modules should import only:
86
+ *
87
+ * @modainteract/moda-interact-shared/logging
88
+ *
89
+ * Once this provider is installed, createLogger(...) automatically fans safe
90
+ * canonical log records out to stdout and the OpenTelemetry Logs API.
91
+ *
92
+ * Direct Loki delivery, when desired, is independently enabled with:
93
+ *
94
+ * initNodeLokiLogging(...)
95
+ *
96
+ * from this same Node-only package entry.
97
+ */
98
+ declare function initNodeOpenTelemetryLogging(options: NodeOpenTelemetryLoggingOptions): NodeOpenTelemetryLoggingRuntime;
99
+ declare function getNodeOpenTelemetryLoggingRuntime(): NodeOpenTelemetryLoggingRuntime | null;
100
+ declare function resolveLogsEndpoint(explicitEndpoint?: string): string | undefined;
101
+ declare function parseOtlpHeaders(value: string | undefined): Record<string, string> | undefined;
102
+
103
+ export { type NodeLokiLoggingOptions, type NodeLokiLoggingRuntime, type NodeOpenTelemetryLoggingOptions, type NodeOpenTelemetryLoggingRuntime, getNodeLokiLoggingRuntime, getNodeOpenTelemetryLoggingRuntime, initNodeLokiLogging, initNodeOpenTelemetryLogging, parseOtlpHeaders, resolveLogsEndpoint };