@uploadista/observability 0.0.18-beta.9 → 0.0.19

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@uploadista/observability",
3
3
  "type": "module",
4
- "version": "0.0.18-beta.9",
4
+ "version": "0.0.19",
5
5
  "description": "Observability package for Uploadista",
6
6
  "license": "MIT",
7
7
  "author": "Uploadista",
@@ -15,6 +15,11 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "@effect/opentelemetry": "0.59.1",
18
+ "@opentelemetry/api": "1.9.0",
19
+ "@opentelemetry/api-logs": "0.208.0",
20
+ "@opentelemetry/exporter-metrics-otlp-http": "0.208.0",
21
+ "@opentelemetry/exporter-trace-otlp-http": "0.208.0",
22
+ "@opentelemetry/exporter-logs-otlp-http": "0.208.0",
18
23
  "@opentelemetry/sdk-logs": "0.208.0",
19
24
  "@opentelemetry/sdk-metrics": "2.2.0",
20
25
  "@opentelemetry/sdk-trace-base": "2.2.0",
@@ -31,8 +36,8 @@
31
36
  "effect": "3.19.8",
32
37
  "tsdown": "0.16.8",
33
38
  "typescript": "5.9.3",
34
- "vitest": "4.0.14",
35
- "@uploadista/typescript-config": "0.0.18-beta.9"
39
+ "vitest": "4.0.15",
40
+ "@uploadista/typescript-config": "0.0.19"
36
41
  },
37
42
  "scripts": {
38
43
  "build": "tsdown",
@@ -0,0 +1,467 @@
1
+ /**
2
+ * OTLP Exporter Configuration for Uploadista SDK.
3
+ *
4
+ * This module provides factory functions for creating OpenTelemetry Protocol (OTLP)
5
+ * exporters that send traces, metrics, and logs to observability backends like Grafana,
6
+ * Jaeger, Datadog, and others.
7
+ *
8
+ * Configuration is done via standard OpenTelemetry environment variables:
9
+ * - OTEL_EXPORTER_OTLP_ENDPOINT: Base endpoint URL (default: http://localhost:4318)
10
+ * - OTEL_EXPORTER_OTLP_HEADERS: Headers for authentication (format: key=value,key2=value2)
11
+ * - OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: Override endpoint for traces only
12
+ * - OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: Override endpoint for metrics only
13
+ * - OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: Override endpoint for logs only
14
+ *
15
+ * @module core/exporters
16
+ */
17
+
18
+ import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
19
+ import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
20
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
21
+ import {
22
+ BatchLogRecordProcessor,
23
+ LoggerProvider,
24
+ } from "@opentelemetry/sdk-logs";
25
+ import {
26
+ MeterProvider,
27
+ PeriodicExportingMetricReader,
28
+ } from "@opentelemetry/sdk-metrics";
29
+
30
+ /**
31
+ * Configuration options for OTLP exporters.
32
+ */
33
+ export interface OtlpExporterConfig {
34
+ /** Base endpoint URL. Defaults to OTEL_EXPORTER_OTLP_ENDPOINT or http://localhost:4318 */
35
+ endpoint?: string;
36
+ /** Headers to include in requests (for authentication). Defaults to OTEL_EXPORTER_OTLP_HEADERS */
37
+ headers?: Record<string, string>;
38
+ /** Request timeout in milliseconds. Defaults to 5000 */
39
+ timeoutMillis?: number;
40
+ }
41
+
42
+ /**
43
+ * Parses the OTEL_EXPORTER_OTLP_HEADERS environment variable.
44
+ *
45
+ * Format: key=value,key2=value2
46
+ * Example: Authorization=Basic abc123,X-Custom-Header=value
47
+ *
48
+ * @returns Parsed headers as a Record, or undefined if not set
49
+ */
50
+ export function parseOtlpHeaders(): Record<string, string> | undefined {
51
+ const headersEnv =
52
+ typeof process !== "undefined"
53
+ ? process.env.OTEL_EXPORTER_OTLP_HEADERS
54
+ : undefined;
55
+
56
+ if (!headersEnv) {
57
+ return undefined;
58
+ }
59
+
60
+ const headers: Record<string, string> = {};
61
+ const pairs = headersEnv.split(",");
62
+
63
+ for (const pair of pairs) {
64
+ const [key, ...valueParts] = pair.split("=");
65
+ if (key && valueParts.length > 0) {
66
+ headers[key.trim()] = valueParts.join("=").trim();
67
+ }
68
+ }
69
+
70
+ return Object.keys(headers).length > 0 ? headers : undefined;
71
+ }
72
+
73
+ /**
74
+ * Gets the OTLP endpoint from environment variables with fallback.
75
+ *
76
+ * Checks in order:
77
+ * 1. Provided endpoint parameter
78
+ * 2. Signal-specific endpoint (OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT, or OTEL_EXPORTER_OTLP_LOGS_ENDPOINT)
79
+ * 3. Base endpoint (OTEL_EXPORTER_OTLP_ENDPOINT)
80
+ * 4. Default: http://localhost:4318
81
+ *
82
+ * @param signal - The signal type ('traces', 'metrics', or 'logs')
83
+ * @param configEndpoint - Optional endpoint from config
84
+ * @returns The resolved endpoint URL
85
+ */
86
+ export function getOtlpEndpoint(
87
+ signal: "traces" | "metrics" | "logs",
88
+ configEndpoint?: string,
89
+ ): string {
90
+ if (configEndpoint) {
91
+ return configEndpoint;
92
+ }
93
+
94
+ if (typeof process !== "undefined") {
95
+ let signalEndpoint: string | undefined;
96
+ switch (signal) {
97
+ case "traces":
98
+ signalEndpoint = process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT;
99
+ break;
100
+ case "metrics":
101
+ signalEndpoint = process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT;
102
+ break;
103
+ case "logs":
104
+ signalEndpoint = process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;
105
+ break;
106
+ }
107
+
108
+ if (signalEndpoint) {
109
+ return signalEndpoint;
110
+ }
111
+
112
+ if (process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {
113
+ return process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
114
+ }
115
+ }
116
+
117
+ return "http://localhost:4318";
118
+ }
119
+
120
+ /**
121
+ * Creates an OTLP trace exporter configured from environment variables.
122
+ *
123
+ * The exporter sends traces to an OTLP-compatible endpoint using HTTP/protobuf.
124
+ *
125
+ * @param config - Optional configuration overrides
126
+ * @returns Configured OTLPTraceExporter instance
127
+ *
128
+ * @example
129
+ * ```typescript
130
+ * // Use environment variables
131
+ * const exporter = createOtlpTraceExporter();
132
+ *
133
+ * // Override endpoint
134
+ * const exporter = createOtlpTraceExporter({
135
+ * endpoint: 'https://otlp.grafana.net'
136
+ * });
137
+ * ```
138
+ */
139
+ export function createOtlpTraceExporter(
140
+ config: OtlpExporterConfig = {},
141
+ ): OTLPTraceExporter {
142
+ const endpoint = getOtlpEndpoint("traces", config.endpoint);
143
+ const headers = config.headers ?? parseOtlpHeaders();
144
+ // Default to 30 seconds to accommodate cloud endpoints like Grafana Cloud
145
+ const timeoutMillis = config.timeoutMillis ?? 30000;
146
+
147
+ return new OTLPTraceExporter({
148
+ url: `${endpoint}/v1/traces`,
149
+ headers,
150
+ timeoutMillis,
151
+ });
152
+ }
153
+
154
+ /**
155
+ * Creates an OTLP metric exporter configured from environment variables.
156
+ *
157
+ * The exporter sends metrics to an OTLP-compatible endpoint using HTTP/protobuf.
158
+ *
159
+ * @param config - Optional configuration overrides
160
+ * @returns Configured OTLPMetricExporter instance
161
+ *
162
+ * @example
163
+ * ```typescript
164
+ * // Use environment variables
165
+ * const exporter = createOtlpMetricExporter();
166
+ *
167
+ * // Override endpoint
168
+ * const exporter = createOtlpMetricExporter({
169
+ * endpoint: 'https://otlp.grafana.net'
170
+ * });
171
+ * ```
172
+ */
173
+ export function createOtlpMetricExporter(
174
+ config: OtlpExporterConfig = {},
175
+ ): OTLPMetricExporter {
176
+ const endpoint = getOtlpEndpoint("metrics", config.endpoint);
177
+ const headers = config.headers ?? parseOtlpHeaders();
178
+ // Default to 30 seconds to accommodate cloud endpoints like Grafana Cloud
179
+ const timeoutMillis = config.timeoutMillis ?? 30000;
180
+
181
+ return new OTLPMetricExporter({
182
+ url: `${endpoint}/v1/metrics`,
183
+ headers,
184
+ timeoutMillis,
185
+ });
186
+ }
187
+
188
+ /**
189
+ * Checks if observability is enabled via environment variable.
190
+ *
191
+ * Reads UPLOADISTA_OBSERVABILITY_ENABLED environment variable.
192
+ * Defaults to true if not set.
193
+ *
194
+ * @returns true if observability should be enabled
195
+ */
196
+ export function isOtlpExportEnabled(): boolean {
197
+ if (typeof process !== "undefined") {
198
+ const enabled = process.env.UPLOADISTA_OBSERVABILITY_ENABLED;
199
+ if (enabled !== undefined) {
200
+ return enabled.toLowerCase() !== "false" && enabled !== "0";
201
+ }
202
+ }
203
+ return true;
204
+ }
205
+
206
+ /**
207
+ * Gets the service name from environment variables.
208
+ *
209
+ * Reads OTEL_SERVICE_NAME environment variable.
210
+ * Defaults to "uploadista" if not set.
211
+ *
212
+ * @param defaultName - Default service name if not configured
213
+ * @returns The service name to use
214
+ */
215
+ export function getServiceName(defaultName = "uploadista"): string {
216
+ if (typeof process !== "undefined" && process.env.OTEL_SERVICE_NAME) {
217
+ return process.env.OTEL_SERVICE_NAME;
218
+ }
219
+ return defaultName;
220
+ }
221
+
222
+ /**
223
+ * Parses resource attributes from OTEL_RESOURCE_ATTRIBUTES environment variable.
224
+ *
225
+ * Format: key=value,key2=value2
226
+ * Example: tenant.id=abc123,deployment.environment=production
227
+ *
228
+ * @returns Parsed attributes as a Record, or empty object if not set
229
+ */
230
+ export function parseResourceAttributes(): Record<string, string> {
231
+ if (typeof process === "undefined") {
232
+ return {};
233
+ }
234
+
235
+ const attrsEnv = process.env.OTEL_RESOURCE_ATTRIBUTES;
236
+ if (!attrsEnv) {
237
+ return {};
238
+ }
239
+
240
+ const attrs: Record<string, string> = {};
241
+ const pairs = attrsEnv.split(",");
242
+
243
+ for (const pair of pairs) {
244
+ const [key, ...valueParts] = pair.split("=");
245
+ if (key && valueParts.length > 0) {
246
+ attrs[key.trim()] = valueParts.join("=").trim();
247
+ }
248
+ }
249
+
250
+ return attrs;
251
+ }
252
+
253
+ // ============================================================================
254
+ // Logs Exporter
255
+ // ============================================================================
256
+
257
+ /**
258
+ * Creates an OTLP log exporter configured from environment variables.
259
+ *
260
+ * The exporter sends logs to an OTLP-compatible endpoint using HTTP/protobuf.
261
+ *
262
+ * @param config - Optional configuration overrides
263
+ * @returns Configured OTLPLogExporter instance
264
+ *
265
+ * @example
266
+ * ```typescript
267
+ * // Use environment variables
268
+ * const exporter = createOtlpLogExporter();
269
+ *
270
+ * // Override endpoint
271
+ * const exporter = createOtlpLogExporter({
272
+ * endpoint: 'https://otlp.grafana.net'
273
+ * });
274
+ * ```
275
+ */
276
+ export function createOtlpLogExporter(
277
+ config: OtlpExporterConfig = {},
278
+ ): OTLPLogExporter {
279
+ const endpoint = getOtlpEndpoint("logs", config.endpoint);
280
+ const headers = config.headers ?? parseOtlpHeaders();
281
+ // Default to 30 seconds to accommodate cloud endpoints like Grafana Cloud
282
+ const timeoutMillis = config.timeoutMillis ?? 30000;
283
+
284
+ return new OTLPLogExporter({
285
+ url: `${endpoint}/v1/logs`,
286
+ headers,
287
+ timeoutMillis,
288
+ });
289
+ }
290
+
291
+ // ============================================================================
292
+ // Metrics SDK Configuration
293
+ // ============================================================================
294
+
295
+ /**
296
+ * Configuration for metrics export.
297
+ */
298
+ export interface MetricsSdkConfig extends OtlpExporterConfig {
299
+ /** Service name for metrics. Defaults to OTEL_SERVICE_NAME or "uploadista" */
300
+ serviceName?: string;
301
+ /** Export interval in milliseconds. Defaults to OTEL_METRICS_EXPORT_INTERVAL or 60000 */
302
+ exportIntervalMillis?: number;
303
+ /** Export timeout in milliseconds. Defaults to 30000 */
304
+ exportTimeoutMillis?: number;
305
+ }
306
+
307
+ /**
308
+ * Gets the metrics export interval from environment or config.
309
+ *
310
+ * @param configInterval - Optional interval from config
311
+ * @returns Export interval in milliseconds
312
+ */
313
+ export function getMetricsExportInterval(configInterval?: number): number {
314
+ if (configInterval !== undefined) {
315
+ return configInterval;
316
+ }
317
+
318
+ if (
319
+ typeof process !== "undefined" &&
320
+ process.env.OTEL_METRICS_EXPORT_INTERVAL
321
+ ) {
322
+ const interval = Number.parseInt(
323
+ process.env.OTEL_METRICS_EXPORT_INTERVAL,
324
+ 10,
325
+ );
326
+ if (!Number.isNaN(interval) && interval > 0) {
327
+ return interval;
328
+ }
329
+ }
330
+
331
+ return 60000; // Default: 60 seconds
332
+ }
333
+
334
+ /**
335
+ * Creates an OTLP MeterProvider with PeriodicExportingMetricReader.
336
+ *
337
+ * The MeterProvider is pre-configured with:
338
+ * - OTLP HTTP exporter for metrics
339
+ * - Periodic export based on OTEL_METRICS_EXPORT_INTERVAL (default: 60s)
340
+ * - Graceful error handling (failures logged, not thrown)
341
+ *
342
+ * @param config - Optional configuration
343
+ * @returns Configured MeterProvider instance
344
+ *
345
+ * @example
346
+ * ```typescript
347
+ * const meterProvider = createOtlpMeterProvider();
348
+ * const meter = meterProvider.getMeter("uploadista");
349
+ * const counter = meter.createCounter("uploads_total");
350
+ * counter.add(1, { storage: "s3" });
351
+ * ```
352
+ */
353
+ export function createOtlpMeterProvider(
354
+ config: MetricsSdkConfig = {},
355
+ ): MeterProvider {
356
+ const exporter = createOtlpMetricExporter(config);
357
+ const exportIntervalMillis = getMetricsExportInterval(
358
+ config.exportIntervalMillis,
359
+ );
360
+ const exportTimeoutMillis = config.exportTimeoutMillis ?? 30000;
361
+
362
+ const reader = new PeriodicExportingMetricReader({
363
+ exporter,
364
+ exportIntervalMillis,
365
+ exportTimeoutMillis,
366
+ });
367
+
368
+ return new MeterProvider({
369
+ readers: [reader],
370
+ });
371
+ }
372
+
373
+ // ============================================================================
374
+ // Logs SDK Configuration
375
+ // ============================================================================
376
+
377
+ /**
378
+ * Configuration for logs export.
379
+ */
380
+ export interface LogsSdkConfig extends OtlpExporterConfig {
381
+ /** Service name for logs. Defaults to OTEL_SERVICE_NAME or "uploadista" */
382
+ serviceName?: string;
383
+ /** Maximum queue size for batch processor. Defaults to 512 */
384
+ maxQueueSize?: number;
385
+ /** Maximum export batch size. Defaults to 512 */
386
+ maxExportBatchSize?: number;
387
+ /** Schedule delay in milliseconds. Defaults to OTEL_LOGS_EXPORT_INTERVAL or 5000 */
388
+ scheduledDelayMillis?: number;
389
+ /** Export timeout in milliseconds. Defaults to 30000 */
390
+ exportTimeoutMillis?: number;
391
+ }
392
+
393
+ /**
394
+ * Gets the logs export interval from environment or config.
395
+ *
396
+ * @param configInterval - Optional interval from config
397
+ * @returns Export interval in milliseconds
398
+ */
399
+ export function getLogsExportInterval(configInterval?: number): number {
400
+ if (configInterval !== undefined) {
401
+ return configInterval;
402
+ }
403
+
404
+ if (typeof process !== "undefined" && process.env.OTEL_LOGS_EXPORT_INTERVAL) {
405
+ const interval = Number.parseInt(process.env.OTEL_LOGS_EXPORT_INTERVAL, 10);
406
+ if (!Number.isNaN(interval) && interval > 0) {
407
+ return interval;
408
+ }
409
+ }
410
+
411
+ return 5000; // Default: 5 seconds
412
+ }
413
+
414
+ /**
415
+ * Creates an OTLP LoggerProvider with BatchLogRecordProcessor.
416
+ *
417
+ * The LoggerProvider is pre-configured with:
418
+ * - OTLP HTTP exporter for logs
419
+ * - Batch processing with configurable queue and batch sizes
420
+ * - Graceful error handling (failures logged, not thrown)
421
+ *
422
+ * @param config - Optional configuration
423
+ * @returns Configured LoggerProvider instance
424
+ *
425
+ * @example
426
+ * ```typescript
427
+ * const loggerProvider = createOtlpLoggerProvider();
428
+ * const logger = loggerProvider.getLogger("uploadista");
429
+ * logger.emit({
430
+ * severityNumber: SeverityNumber.INFO,
431
+ * body: "Upload completed",
432
+ * attributes: { uploadId: "123" },
433
+ * });
434
+ * ```
435
+ */
436
+ export function createOtlpLoggerProvider(
437
+ config: LogsSdkConfig = {},
438
+ ): LoggerProvider {
439
+ const exporter = createOtlpLogExporter(config);
440
+ const scheduledDelayMillis = getLogsExportInterval(
441
+ config.scheduledDelayMillis,
442
+ );
443
+
444
+ const processor = new BatchLogRecordProcessor(exporter, {
445
+ maxQueueSize: config.maxQueueSize ?? 512,
446
+ maxExportBatchSize: config.maxExportBatchSize ?? 512,
447
+ scheduledDelayMillis,
448
+ exportTimeoutMillis: config.exportTimeoutMillis ?? 30000,
449
+ });
450
+
451
+ // Create provider with processor in constructor (SDK 0.208.0+ API)
452
+ return new LoggerProvider({
453
+ processors: [processor],
454
+ });
455
+ }
456
+
457
+ export { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
458
+ export { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
459
+ export {
460
+ BatchLogRecordProcessor,
461
+ LoggerProvider,
462
+ } from "@opentelemetry/sdk-logs";
463
+ // Re-export types for convenience
464
+ export {
465
+ MeterProvider,
466
+ PeriodicExportingMetricReader,
467
+ } from "@opentelemetry/sdk-metrics";