@qlever-llc/trellis 0.10.11 → 0.10.13

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 (124) hide show
  1. package/esm/contract_support/mod.d.ts +1 -0
  2. package/esm/contract_support/mod.d.ts.map +1 -1
  3. package/esm/contract_support/mod.js +1 -0
  4. package/esm/contract_support/schema_pointers.d.ts +6 -0
  5. package/esm/contract_support/schema_pointers.d.ts.map +1 -1
  6. package/esm/contract_support/schema_pointers.js +59 -7
  7. package/esm/errors/TrellisError.d.ts +3 -3
  8. package/esm/errors/TrellisError.js +3 -3
  9. package/esm/server/internal_jobs/job-manager.d.ts.map +1 -1
  10. package/esm/server/internal_jobs/job-manager.js +32 -1
  11. package/esm/server/runtime.d.ts +3 -0
  12. package/esm/server/runtime.d.ts.map +1 -1
  13. package/esm/server/service.d.ts +15 -0
  14. package/esm/server/service.d.ts.map +1 -1
  15. package/esm/server/service.js +8 -0
  16. package/esm/server.d.ts.map +1 -1
  17. package/esm/server.js +54 -6
  18. package/esm/service/deno.d.ts +1 -1
  19. package/esm/service/deno.d.ts.map +1 -1
  20. package/esm/service/mod.d.ts +1 -1
  21. package/esm/service/mod.d.ts.map +1 -1
  22. package/esm/service/node.d.ts +1 -1
  23. package/esm/service/node.d.ts.map +1 -1
  24. package/esm/service/outbox_inbox.d.ts.map +1 -1
  25. package/esm/service/outbox_inbox.js +14 -0
  26. package/esm/telemetry/core.d.ts.map +1 -1
  27. package/esm/telemetry/core.js +1 -1
  28. package/esm/telemetry/env.d.ts.map +1 -1
  29. package/esm/telemetry/env.js +6 -1
  30. package/esm/telemetry/init.d.ts +3 -0
  31. package/esm/telemetry/init.d.ts.map +1 -0
  32. package/esm/telemetry/init.js +7 -0
  33. package/esm/telemetry/metrics.d.ts +34 -0
  34. package/esm/telemetry/metrics.d.ts.map +1 -0
  35. package/esm/telemetry/metrics.js +181 -0
  36. package/esm/telemetry/mod.d.ts +3 -0
  37. package/esm/telemetry/mod.d.ts.map +1 -1
  38. package/esm/telemetry/mod.js +2 -0
  39. package/esm/telemetry/runtime.d.ts +2 -0
  40. package/esm/telemetry/runtime.d.ts.map +1 -0
  41. package/esm/telemetry/runtime.js +134 -0
  42. package/esm/telemetry.d.ts +3 -0
  43. package/esm/telemetry.d.ts.map +1 -0
  44. package/esm/telemetry.js +2 -0
  45. package/esm/transfer.d.ts.map +1 -1
  46. package/esm/transfer.js +27 -16
  47. package/esm/trellis.d.ts.map +1 -1
  48. package/esm/trellis.js +460 -56
  49. package/package.json +7 -5
  50. package/script/contract_support/mod.d.ts +1 -0
  51. package/script/contract_support/mod.d.ts.map +1 -1
  52. package/script/contract_support/mod.js +4 -1
  53. package/script/contract_support/schema_pointers.d.ts +6 -0
  54. package/script/contract_support/schema_pointers.d.ts.map +1 -1
  55. package/script/contract_support/schema_pointers.js +59 -7
  56. package/script/errors/TrellisError.d.ts +3 -3
  57. package/script/errors/TrellisError.js +3 -3
  58. package/script/server/internal_jobs/job-manager.d.ts.map +1 -1
  59. package/script/server/internal_jobs/job-manager.js +32 -1
  60. package/script/server/runtime.d.ts +3 -0
  61. package/script/server/runtime.d.ts.map +1 -1
  62. package/script/server/service.d.ts +15 -0
  63. package/script/server/service.d.ts.map +1 -1
  64. package/script/server/service.js +8 -0
  65. package/script/server.d.ts.map +1 -1
  66. package/script/server.js +54 -6
  67. package/script/service/deno.d.ts +1 -1
  68. package/script/service/deno.d.ts.map +1 -1
  69. package/script/service/mod.d.ts +1 -1
  70. package/script/service/mod.d.ts.map +1 -1
  71. package/script/service/node.d.ts +1 -1
  72. package/script/service/node.d.ts.map +1 -1
  73. package/script/service/outbox_inbox.d.ts.map +1 -1
  74. package/script/service/outbox_inbox.js +14 -0
  75. package/script/telemetry/core.d.ts.map +1 -1
  76. package/script/telemetry/core.js +1 -1
  77. package/script/telemetry/env.d.ts.map +1 -1
  78. package/script/telemetry/env.js +6 -1
  79. package/script/telemetry/init.d.ts +3 -0
  80. package/script/telemetry/init.d.ts.map +1 -0
  81. package/script/telemetry/init.js +10 -0
  82. package/script/telemetry/metrics.d.ts +34 -0
  83. package/script/telemetry/metrics.d.ts.map +1 -0
  84. package/script/telemetry/metrics.js +186 -0
  85. package/script/telemetry/mod.d.ts +3 -0
  86. package/script/telemetry/mod.d.ts.map +1 -1
  87. package/script/telemetry/mod.js +7 -1
  88. package/script/telemetry/runtime.d.ts +2 -0
  89. package/script/telemetry/runtime.d.ts.map +1 -0
  90. package/script/telemetry/runtime.js +137 -0
  91. package/script/telemetry.d.ts +3 -0
  92. package/script/telemetry.d.ts.map +1 -0
  93. package/script/telemetry.js +18 -0
  94. package/script/transfer.d.ts.map +1 -1
  95. package/script/transfer.js +28 -17
  96. package/script/trellis.d.ts.map +1 -1
  97. package/script/trellis.js +490 -86
  98. package/src/contract_support/mod.ts +4 -0
  99. package/src/contract_support/schema_pointers.ts +80 -7
  100. package/src/errors/TrellisError.ts +4 -4
  101. package/src/server/internal_jobs/job-manager.ts +35 -5
  102. package/src/server/runtime.ts +4 -0
  103. package/src/server/service.ts +27 -0
  104. package/src/server.ts +66 -11
  105. package/src/service/deno.ts +1 -0
  106. package/src/service/mod.ts +1 -0
  107. package/src/service/node.ts +1 -0
  108. package/src/service/outbox_inbox.ts +14 -0
  109. package/src/telemetry/core.ts +1 -1
  110. package/src/telemetry/env.ts +5 -1
  111. package/src/telemetry/init.ts +8 -0
  112. package/src/telemetry/metrics.ts +294 -0
  113. package/src/telemetry/mod.ts +7 -0
  114. package/src/telemetry/runtime.ts +218 -0
  115. package/src/telemetry.ts +2 -0
  116. package/src/transfer.ts +69 -30
  117. package/src/trellis.ts +487 -88
  118. package/esm/tracing.d.ts +0 -5
  119. package/esm/tracing.d.ts.map +0 -1
  120. package/esm/tracing.js +0 -8
  121. package/script/tracing.d.ts +0 -5
  122. package/script/tracing.d.ts.map +0 -1
  123. package/script/tracing.js +0 -27
  124. package/src/tracing.ts +0 -28
@@ -0,0 +1,294 @@
1
+ import { type Meter, metrics } from "@opentelemetry/api";
2
+
3
+ const TRELLIS_METER_NAME = "@qlever-llc/trellis";
4
+ const MAX_ATTRIBUTE_LENGTH = 96;
5
+ const LOW_CARDINALITY_PATTERN = /^[A-Za-z0-9_.:-]+$/;
6
+ const UUID_PATTERN =
7
+ /(^|[_.:-])[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}($|[_.:-])/i;
8
+ const ULID_PATTERN = /(^|[_.:-])[0-7][0-9A-HJKMNP-TV-Z]{25}($|[_.:-])/i;
9
+ const LONG_HEX_SEGMENT_PATTERN = /(^|[_.:-])[0-9a-f]{12,}($|[_.:-])/i;
10
+ const TRELLIS_SUBJECT_PREFIX_PATTERN =
11
+ /^(rpc|events|feeds|operations|jobs|state|kv|store|resources|transfer)\.v\d+\./;
12
+
13
+ const AUTH_REASONS = new Set([
14
+ "invalid_request",
15
+ "missing_session_key",
16
+ "missing_proof",
17
+ "session_not_found",
18
+ "session_expired",
19
+ "invalid_signature",
20
+ "user_not_found",
21
+ "user_already_exists",
22
+ "username_taken",
23
+ "identity_already_exists",
24
+ "identity_not_found",
25
+ "user_inactive",
26
+ "unknown_device",
27
+ "device_deployment_not_found",
28
+ "device_deployment_disabled",
29
+ "device_activation_revoked",
30
+ "unknown_service",
31
+ "service_disabled",
32
+ "iat_out_of_range",
33
+ "invalid_binding_token",
34
+ "session_corrupted",
35
+ "session_already_bound",
36
+ "authtoken_already_used",
37
+ "oauth_session_key_mismatch",
38
+ "reply_subject_mismatch",
39
+ "insufficient_permissions",
40
+ "forbidden",
41
+ "last_admin_required",
42
+ "missing_flow_id",
43
+ "device_activation_flow_not_found",
44
+ "device_activation_flow_expired",
45
+ "device_activation_rejected",
46
+ "device_identity_key_mismatch",
47
+ "invalid_device_qr_mac",
48
+ ]);
49
+
50
+ /** Low-cardinality attributes accepted by {@link recordTrellisError}. */
51
+ export type TrellisErrorMetricAttributes = {
52
+ /** Stable Trellis surface name, such as `rpc`, `jobs`, or `operations`. */
53
+ surface?: string;
54
+ /** Stable flow direction such as `client`, `server`, `publish`, or `consume`. */
55
+ direction?: string;
56
+ /** Stable operation kind or method name. Do not pass IDs, subjects, or URLs. */
57
+ operation?: string;
58
+ /** Stable lifecycle phase such as `encode`, `send`, `auth`, or `handler`. */
59
+ phase?: string;
60
+ /** Stable local Trellis error type override when the thrown value does not expose one. */
61
+ errorType?: string;
62
+ /** Stable wrapped remote Trellis error type override. */
63
+ remoteErrorType?: string;
64
+ /** Stable auth failure reason, such as `missing_session_key`. */
65
+ authReason?: string;
66
+ /** Static messaging system name, when already known. */
67
+ messagingSystem?: string;
68
+ /** Static messaging operation name, when already known. */
69
+ messagingOperation?: string;
70
+ };
71
+
72
+ type SerializableErrorData = {
73
+ type?: unknown;
74
+ reason?: unknown;
75
+ remoteError?: unknown;
76
+ context?: unknown;
77
+ };
78
+
79
+ /** Returns the shared Trellis OpenTelemetry meter. */
80
+ export function getTrellisMeter(): Meter {
81
+ return metrics.getMeter(TRELLIS_METER_NAME);
82
+ }
83
+
84
+ /** Records one Trellis error with only stable, low-cardinality attributes. */
85
+ export function recordTrellisError(
86
+ error: unknown,
87
+ attributes: TrellisErrorMetricAttributes = {},
88
+ ): void {
89
+ getTrellisMeter().createCounter("trellis.errors", {
90
+ description: "Trellis errors observed by runtime instrumentation.",
91
+ unit: "{error}",
92
+ }).add(
93
+ 1,
94
+ buildTrellisErrorMetricAttributes(
95
+ error,
96
+ attributes,
97
+ ),
98
+ );
99
+ }
100
+
101
+ /**
102
+ * Builds sanitized, low-cardinality attributes for `trellis.errors`.
103
+ *
104
+ * @internal Exported for focused tests; runtime callers should use
105
+ * {@link recordTrellisError}.
106
+ */
107
+ export function buildTrellisErrorMetricAttributes(
108
+ error: unknown,
109
+ attributes: TrellisErrorMetricAttributes = {},
110
+ ): Record<string, string> {
111
+ const serializable = serializableErrorData(error);
112
+ const metricAttributes: Record<string, string> = {
113
+ "exception.type": exceptionType(error),
114
+ "trellis.error.type": trellisErrorType(
115
+ error,
116
+ serializable,
117
+ attributes.errorType,
118
+ ),
119
+ };
120
+
121
+ const remoteErrorType = lowCardinalityValue(attributes.remoteErrorType) ??
122
+ remoteSerializableErrorType(serializable);
123
+ if (remoteErrorType) {
124
+ metricAttributes["trellis.remote_error.type"] = remoteErrorType;
125
+ }
126
+
127
+ setLowCardinalityAttribute(
128
+ metricAttributes,
129
+ "trellis.surface",
130
+ attributes.surface,
131
+ );
132
+ setLowCardinalityAttribute(
133
+ metricAttributes,
134
+ "trellis.direction",
135
+ attributes.direction,
136
+ );
137
+ setLowCardinalityAttribute(
138
+ metricAttributes,
139
+ "trellis.operation",
140
+ attributes.operation,
141
+ );
142
+ setLowCardinalityAttribute(
143
+ metricAttributes,
144
+ "trellis.phase",
145
+ attributes.phase,
146
+ );
147
+ setLowCardinalityAttribute(
148
+ metricAttributes,
149
+ "trellis.auth.reason",
150
+ boundedAuthReason(attributes.authReason) ?? authReason(serializable),
151
+ );
152
+ setLowCardinalityAttribute(
153
+ metricAttributes,
154
+ "messaging.system",
155
+ attributes.messagingSystem,
156
+ );
157
+ setLowCardinalityAttribute(
158
+ metricAttributes,
159
+ "messaging.operation",
160
+ attributes.messagingOperation,
161
+ );
162
+
163
+ return metricAttributes;
164
+ }
165
+
166
+ function trellisErrorType(
167
+ error: unknown,
168
+ serializable: SerializableErrorData | undefined,
169
+ override: string | undefined,
170
+ ): string {
171
+ const overrideType = lowCardinalityValue(override);
172
+ if (overrideType) return overrideType;
173
+
174
+ const serializableType = lowCardinalityValue(serializable?.type);
175
+ if (serializableType) return serializableType;
176
+
177
+ const objectType = objectStringProperty(error, "type");
178
+ if (objectType) return objectType;
179
+
180
+ return exceptionType(error);
181
+ }
182
+
183
+ function exceptionType(error: unknown): string {
184
+ if (error instanceof Error) {
185
+ const name = lowCardinalityValue(error.name);
186
+ if (name) return name;
187
+ }
188
+
189
+ const objectType = objectStringProperty(error, "type");
190
+ if (objectType) return objectType;
191
+
192
+ return "unknown";
193
+ }
194
+
195
+ function serializableErrorData(
196
+ error: unknown,
197
+ ): SerializableErrorData | undefined {
198
+ const toSerializable = safeProperty(error, "toSerializable");
199
+ if (!isSerializableMethod(toSerializable)) return undefined;
200
+
201
+ try {
202
+ const data = toSerializable.call(error);
203
+ return isRecord(data) ? data : undefined;
204
+ } catch {
205
+ return undefined;
206
+ }
207
+ }
208
+
209
+ function remoteSerializableErrorType(
210
+ serializable: SerializableErrorData | undefined,
211
+ ): string | undefined {
212
+ const remoteError = safeProperty(serializable, "remoteError");
213
+ if (!isRecord(remoteError)) return undefined;
214
+ return objectStringProperty(remoteError, "type");
215
+ }
216
+
217
+ function authReason(
218
+ serializable: SerializableErrorData | undefined,
219
+ ): string | undefined {
220
+ const type = safeProperty(serializable, "type");
221
+ if (type === "AuthError") {
222
+ return boundedAuthReason(safeProperty(serializable, "reason"));
223
+ }
224
+
225
+ if (type === "RemoteError") {
226
+ const remoteError = safeProperty(serializable, "remoteError");
227
+ if (
228
+ isRecord(remoteError) && objectStringProperty(remoteError, "type") ===
229
+ "AuthError"
230
+ ) {
231
+ return boundedAuthReason(safeProperty(remoteError, "reason"));
232
+ }
233
+ }
234
+
235
+ return undefined;
236
+ }
237
+
238
+ function boundedAuthReason(value: unknown): string | undefined {
239
+ const sanitized = lowCardinalityValue(value);
240
+ if (!sanitized || !AUTH_REASONS.has(sanitized)) return undefined;
241
+ return sanitized;
242
+ }
243
+
244
+ function isRecord(value: unknown): value is Record<string, unknown> {
245
+ return value !== null && typeof value === "object" && !Array.isArray(value);
246
+ }
247
+
248
+ function isSerializableMethod(
249
+ value: unknown,
250
+ ): value is (this: unknown) => unknown {
251
+ return typeof value === "function";
252
+ }
253
+
254
+ function safeProperty(value: unknown, key: string): unknown {
255
+ if (!isRecord(value)) return undefined;
256
+
257
+ try {
258
+ return value[key];
259
+ } catch {
260
+ return undefined;
261
+ }
262
+ }
263
+
264
+ function objectStringProperty(value: unknown, key: string): string | undefined {
265
+ return lowCardinalityValue(safeProperty(value, key));
266
+ }
267
+
268
+ function setLowCardinalityAttribute(
269
+ attributes: Record<string, string>,
270
+ key: string,
271
+ value: unknown,
272
+ ): void {
273
+ const sanitized = lowCardinalityValue(value);
274
+ if (sanitized) attributes[key] = sanitized;
275
+ }
276
+
277
+ function lowCardinalityValue(value: unknown): string | undefined {
278
+ if (typeof value !== "string") return undefined;
279
+
280
+ const trimmed = value.trim();
281
+ if (
282
+ trimmed.length === 0 ||
283
+ trimmed.length > MAX_ATTRIBUTE_LENGTH ||
284
+ !LOW_CARDINALITY_PATTERN.test(trimmed) ||
285
+ UUID_PATTERN.test(trimmed) ||
286
+ ULID_PATTERN.test(trimmed) ||
287
+ LONG_HEX_SEGMENT_PATTERN.test(trimmed) ||
288
+ TRELLIS_SUBJECT_PREFIX_PATTERN.test(trimmed)
289
+ ) {
290
+ return undefined;
291
+ }
292
+
293
+ return trimmed;
294
+ }
@@ -16,6 +16,13 @@ export {
16
16
  withSpanAsync,
17
17
  } from "./core.js";
18
18
  export { getEnv } from "./env.js";
19
+ export { initTelemetry } from "./init.js";
20
+ export {
21
+ buildTrellisErrorMetricAttributes,
22
+ getTrellisMeter,
23
+ recordTrellisError,
24
+ } from "./metrics.js";
25
+ export type { TrellisErrorMetricAttributes } from "./metrics.js";
19
26
  export type { NatsHeadersLike } from "./nats.js";
20
27
  export { createNatsHeaderCarrier } from "./nats.js";
21
28
  export { configureErrorTraceId } from "./result.js";
@@ -0,0 +1,218 @@
1
+ import { metrics } from "@opentelemetry/api";
2
+ import { getEnv } from "./env.js";
3
+
4
+ let initialized = false;
5
+ let provider: { register(): void } | undefined;
6
+ let metricProvider: unknown;
7
+
8
+ type TracingRuntimeModules = {
9
+ NodeTracerProvider:
10
+ typeof import("@opentelemetry/sdk-trace-node").NodeTracerProvider;
11
+ OTLPTraceExporter:
12
+ typeof import("@opentelemetry/exporter-trace-otlp-proto").OTLPTraceExporter;
13
+ BatchSpanProcessor:
14
+ typeof import("@opentelemetry/sdk-trace-base").BatchSpanProcessor;
15
+ ConsoleSpanExporter:
16
+ typeof import("@opentelemetry/sdk-trace-base").ConsoleSpanExporter;
17
+ resourceFromAttributes:
18
+ typeof import("@opentelemetry/resources").resourceFromAttributes;
19
+ ATTR_SERVICE_NAME:
20
+ typeof import("@opentelemetry/semantic-conventions").ATTR_SERVICE_NAME;
21
+ };
22
+
23
+ type MetricsRuntimeModules = {
24
+ MeterProvider: typeof import("@opentelemetry/sdk-metrics").MeterProvider;
25
+ PeriodicExportingMetricReader:
26
+ typeof import("@opentelemetry/sdk-metrics").PeriodicExportingMetricReader;
27
+ ConsoleMetricExporter:
28
+ typeof import("@opentelemetry/sdk-metrics").ConsoleMetricExporter;
29
+ OTLPMetricExporter:
30
+ typeof import("@opentelemetry/exporter-metrics-otlp-proto").OTLPMetricExporter;
31
+ resourceFromAttributes:
32
+ typeof import("@opentelemetry/resources").resourceFromAttributes;
33
+ ATTR_SERVICE_NAME:
34
+ typeof import("@opentelemetry/semantic-conventions").ATTR_SERVICE_NAME;
35
+ };
36
+
37
+ function runtimeImport<TModule>(specifier: string): Promise<TModule> {
38
+ const load = new Function("specifier", "return import(specifier);") as (
39
+ specifier: string,
40
+ ) => Promise<TModule>;
41
+ return load(specifier);
42
+ }
43
+
44
+ async function loadTracingRuntime(): Promise<TracingRuntimeModules> {
45
+ const [traceNode, otlp, traceBase, resources, semantic] = await Promise.all([
46
+ runtimeImport<typeof import("@opentelemetry/sdk-trace-node")>(
47
+ ["@opentelemetry", "sdk-trace-node"].join("/"),
48
+ ),
49
+ runtimeImport<typeof import("@opentelemetry/exporter-trace-otlp-proto")>(
50
+ ["@opentelemetry", "exporter-trace-otlp-proto"].join("/"),
51
+ ),
52
+ runtimeImport<typeof import("@opentelemetry/sdk-trace-base")>(
53
+ ["@opentelemetry", "sdk-trace-base"].join("/"),
54
+ ),
55
+ runtimeImport<typeof import("@opentelemetry/resources")>(
56
+ ["@opentelemetry", "resources"].join("/"),
57
+ ),
58
+ runtimeImport<typeof import("@opentelemetry/semantic-conventions")>(
59
+ ["@opentelemetry", "semantic-conventions"].join("/"),
60
+ ),
61
+ ]);
62
+
63
+ return {
64
+ NodeTracerProvider: traceNode.NodeTracerProvider,
65
+ OTLPTraceExporter: otlp.OTLPTraceExporter,
66
+ BatchSpanProcessor: traceBase.BatchSpanProcessor,
67
+ ConsoleSpanExporter: traceBase.ConsoleSpanExporter,
68
+ resourceFromAttributes: resources.resourceFromAttributes,
69
+ ATTR_SERVICE_NAME: semantic.ATTR_SERVICE_NAME,
70
+ };
71
+ }
72
+
73
+ async function loadMetricsRuntime(): Promise<MetricsRuntimeModules> {
74
+ const [sdkMetrics, otlp, resources, semantic] = await Promise.all([
75
+ runtimeImport<typeof import("@opentelemetry/sdk-metrics")>(
76
+ ["@opentelemetry", "sdk-metrics"].join("/"),
77
+ ),
78
+ runtimeImport<typeof import("@opentelemetry/exporter-metrics-otlp-proto")>(
79
+ ["@opentelemetry", "exporter-metrics-otlp-proto"].join("/"),
80
+ ),
81
+ runtimeImport<typeof import("@opentelemetry/resources")>(
82
+ ["@opentelemetry", "resources"].join("/"),
83
+ ),
84
+ runtimeImport<typeof import("@opentelemetry/semantic-conventions")>(
85
+ ["@opentelemetry", "semantic-conventions"].join("/"),
86
+ ),
87
+ ]);
88
+
89
+ return {
90
+ MeterProvider: sdkMetrics.MeterProvider,
91
+ PeriodicExportingMetricReader: sdkMetrics.PeriodicExportingMetricReader,
92
+ ConsoleMetricExporter: sdkMetrics.ConsoleMetricExporter,
93
+ OTLPMetricExporter: otlp.OTLPMetricExporter,
94
+ resourceFromAttributes: resources.resourceFromAttributes,
95
+ ATTR_SERVICE_NAME: semantic.ATTR_SERVICE_NAME,
96
+ };
97
+ }
98
+
99
+ async function initTracingRuntime(serviceName: string): Promise<void> {
100
+ const endpoint = getEnv("OTEL_EXPORTER_OTLP_ENDPOINT");
101
+ const consoleTracing = getEnv("OTEL_TRACES_CONSOLE") === "true";
102
+
103
+ if (!endpoint && !consoleTracing) {
104
+ return;
105
+ }
106
+
107
+ try {
108
+ const runtime = await loadTracingRuntime();
109
+ const spanProcessors: Array<
110
+ InstanceType<typeof runtime.BatchSpanProcessor>
111
+ > = [];
112
+
113
+ if (endpoint) {
114
+ spanProcessors.push(
115
+ new runtime.BatchSpanProcessor(
116
+ new runtime.OTLPTraceExporter({ url: `${endpoint}/v1/traces` }),
117
+ ),
118
+ );
119
+ }
120
+
121
+ if (consoleTracing) {
122
+ spanProcessors.push(
123
+ new runtime.BatchSpanProcessor(new runtime.ConsoleSpanExporter()),
124
+ );
125
+ }
126
+
127
+ const nextProvider = new runtime.NodeTracerProvider({
128
+ resource: runtime.resourceFromAttributes({
129
+ [runtime.ATTR_SERVICE_NAME]: serviceName,
130
+ }),
131
+ ...(spanProcessors.length > 0 ? { spanProcessors } : {}),
132
+ });
133
+
134
+ provider = nextProvider;
135
+ nextProvider.register();
136
+ } catch (error) {
137
+ console.warn("Failed to initialize tracing runtime", error);
138
+ }
139
+ }
140
+
141
+ function metricEndpoint(): string | undefined {
142
+ const metricsEndpoint = getEnv("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT");
143
+ if (metricsEndpoint) return metricsEndpoint;
144
+
145
+ const endpoint = getEnv("OTEL_EXPORTER_OTLP_ENDPOINT");
146
+ if (!endpoint) return undefined;
147
+
148
+ return `${endpoint.replace(/\/$/, "")}/v1/metrics`;
149
+ }
150
+
151
+ function metricExportIntervalMillis(): number | undefined {
152
+ const configured = getEnv("OTEL_METRIC_EXPORT_INTERVAL");
153
+ if (!configured) return undefined;
154
+
155
+ const parsed = Number(configured);
156
+ if (!Number.isFinite(parsed) || parsed <= 0) return undefined;
157
+
158
+ return Math.trunc(parsed);
159
+ }
160
+
161
+ async function initMetricsRuntime(serviceName: string): Promise<void> {
162
+ const endpoint = metricEndpoint();
163
+ const consoleMetrics = getEnv("TRELLIS_METRICS_CONSOLE") === "true";
164
+
165
+ if (!endpoint && !consoleMetrics) {
166
+ return;
167
+ }
168
+
169
+ try {
170
+ const runtime = await loadMetricsRuntime();
171
+ const readers: Array<
172
+ InstanceType<typeof runtime.PeriodicExportingMetricReader>
173
+ > = [];
174
+ const exportIntervalMillis = metricExportIntervalMillis();
175
+
176
+ if (endpoint) {
177
+ readers.push(
178
+ new runtime.PeriodicExportingMetricReader({
179
+ exporter: new runtime.OTLPMetricExporter({ url: endpoint }),
180
+ ...(exportIntervalMillis !== undefined
181
+ ? { exportIntervalMillis }
182
+ : {}),
183
+ }),
184
+ );
185
+ }
186
+
187
+ if (consoleMetrics) {
188
+ readers.push(
189
+ new runtime.PeriodicExportingMetricReader({
190
+ exporter: new runtime.ConsoleMetricExporter(),
191
+ ...(exportIntervalMillis !== undefined
192
+ ? { exportIntervalMillis }
193
+ : {}),
194
+ }),
195
+ );
196
+ }
197
+
198
+ const nextMetricProvider = new runtime.MeterProvider({
199
+ resource: runtime.resourceFromAttributes({
200
+ [runtime.ATTR_SERVICE_NAME]: serviceName,
201
+ }),
202
+ ...(readers.length > 0 ? { readers } : {}),
203
+ });
204
+
205
+ metricProvider = nextMetricProvider;
206
+ metrics.setGlobalMeterProvider(nextMetricProvider);
207
+ } catch (error) {
208
+ console.warn("Failed to initialize metrics runtime", error);
209
+ }
210
+ }
211
+
212
+ export function initTelemetryRuntime(serviceName: string): void {
213
+ if (initialized) return;
214
+ initialized = true;
215
+
216
+ void initTracingRuntime(serviceName);
217
+ void initMetricsRuntime(serviceName);
218
+ }
@@ -0,0 +1,2 @@
1
+ import "./_dnt.polyfills.js";
2
+ export * from "./telemetry/mod.js";