@upyo/opentelemetry 0.2.0-dev.24

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,558 @@
1
+ import { MeterProvider, TracerProvider } from "@opentelemetry/api";
2
+ import { Message, Receipt, Transport, TransportOptions } from "@upyo/core";
3
+
4
+ //#region src/config.d.ts
5
+
6
+ /**
7
+ * Configuration options for OpenTelemetry observability features.
8
+ * @since 0.2.0
9
+ */
10
+ interface ObservabilityConfig {
11
+ /**
12
+ * Whether tracing is enabled.
13
+ * @default true
14
+ */
15
+ readonly enabled?: boolean;
16
+ /**
17
+ * Custom sampling rate for this feature (0.0 to 1.0).
18
+ * @default 1.0 (sample all)
19
+ */
20
+ readonly samplingRate?: number;
21
+ }
22
+ /**
23
+ * Configuration options for metrics collection.
24
+ * @since 0.2.0
25
+ */
26
+ interface MetricsConfig extends ObservabilityConfig {
27
+ /**
28
+ * Custom prefix for metric names.
29
+ * @default "upyo"
30
+ */
31
+ readonly prefix?: string;
32
+ /**
33
+ * Custom histogram buckets for duration metrics (in seconds).
34
+ * @default [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0, 5.0, 10.0]
35
+ */
36
+ readonly durationBuckets?: readonly number[];
37
+ }
38
+ /**
39
+ * Configuration options for tracing.
40
+ * @since 0.2.0
41
+ */
42
+ interface TracingConfig extends ObservabilityConfig {
43
+ /**
44
+ * Whether to record sensitive information in spans.
45
+ * When false, email addresses and subjects are redacted.
46
+ * @default false
47
+ */
48
+ readonly recordSensitiveData?: boolean;
49
+ /**
50
+ * Custom span name prefix.
51
+ * @default "email"
52
+ */
53
+ readonly spanPrefix?: string;
54
+ }
55
+ /**
56
+ * Custom attribute extractor function type.
57
+ * @since 0.2.0
58
+ */
59
+ type AttributeExtractor = (operation: "send" | "send_batch", transportName: string, messageCount: number, totalSize?: number) => Record<string, string | number | boolean>;
60
+ /**
61
+ * Custom error classifier function type.
62
+ * @since 0.2.0
63
+ */
64
+ type ErrorClassifier = (error: unknown) => string;
65
+ /**
66
+ * Configuration options for auto-setup scenarios.
67
+ * @since 0.2.0
68
+ */
69
+ interface AutoConfig {
70
+ /**
71
+ * Service name for OpenTelemetry resource attributes.
72
+ * @default "email-service"
73
+ */
74
+ readonly serviceName?: string;
75
+ /**
76
+ * Service version for OpenTelemetry resource attributes.
77
+ */
78
+ readonly serviceVersion?: string;
79
+ /**
80
+ * Tracing endpoint configuration.
81
+ */
82
+ readonly tracing?: {
83
+ readonly endpoint?: string;
84
+ readonly headers?: Record<string, string>;
85
+ };
86
+ /**
87
+ * Metrics endpoint configuration.
88
+ */
89
+ readonly metrics?: {
90
+ readonly endpoint?: string;
91
+ readonly headers?: Record<string, string>;
92
+ };
93
+ }
94
+ /**
95
+ * Configuration for the OpenTelemetry transport.
96
+ * @since 0.2.0
97
+ */
98
+ interface OpenTelemetryConfig {
99
+ /**
100
+ * OpenTelemetry tracer provider.
101
+ * Required if tracing is enabled.
102
+ */
103
+ readonly tracerProvider?: TracerProvider;
104
+ /**
105
+ * OpenTelemetry meter provider.
106
+ * Required if metrics are enabled.
107
+ */
108
+ readonly meterProvider?: MeterProvider;
109
+ /**
110
+ * Metrics collection configuration.
111
+ * @default { enabled: true }
112
+ */
113
+ readonly metrics?: MetricsConfig;
114
+ /**
115
+ * Tracing configuration.
116
+ * @default { enabled: true }
117
+ */
118
+ readonly tracing?: TracingConfig;
119
+ /**
120
+ * Custom attribute extractor function.
121
+ * Called for each operation to add custom attributes.
122
+ */
123
+ readonly attributeExtractor?: AttributeExtractor;
124
+ /**
125
+ * Custom error classifier function.
126
+ * Called to classify errors into categories for metrics.
127
+ */
128
+ readonly errorClassifier?: ErrorClassifier;
129
+ /**
130
+ * Auto-configuration options for simplified setup.
131
+ * When provided, missing providers will be auto-configured.
132
+ */
133
+ readonly auto?: AutoConfig;
134
+ }
135
+ /**
136
+ * Resolved configuration with all defaults applied.
137
+ * @since 0.2.0
138
+ */
139
+ interface ResolvedOpenTelemetryConfig {
140
+ readonly tracerProvider?: TracerProvider;
141
+ readonly meterProvider?: MeterProvider;
142
+ readonly metrics: Required<MetricsConfig>;
143
+ readonly tracing: Required<TracingConfig>;
144
+ readonly attributeExtractor?: AttributeExtractor;
145
+ readonly errorClassifier?: ErrorClassifier;
146
+ }
147
+ /**
148
+ * Creates a resolved OpenTelemetry configuration with defaults applied.
149
+ *
150
+ * @param config The input configuration options.
151
+ * @returns A resolved configuration with all defaults applied.
152
+ * @throws {Error} When tracing is enabled but no TracerProvider is provided.
153
+ * @throws {Error} When metrics are enabled but no MeterProvider is provided.
154
+ * @since 0.2.0
155
+ */
156
+
157
+ /**
158
+ * Default error classifier that categorizes errors into standard types.
159
+ *
160
+ * @example
161
+ * ```typescript
162
+ * import { defaultErrorClassifier } from "@upyo/opentelemetry";
163
+ *
164
+ * console.log(defaultErrorClassifier(new Error("401 Unauthorized"))); // "auth"
165
+ * console.log(defaultErrorClassifier(new Error("Rate limit exceeded"))); // "rate_limit"
166
+ * console.log(defaultErrorClassifier(new Error("Connection timeout"))); // "network"
167
+ * console.log(defaultErrorClassifier(new Error("Invalid email format"))); // "validation"
168
+ * console.log(defaultErrorClassifier(new Error("500 Internal Server Error"))); // "server_error"
169
+ * console.log(defaultErrorClassifier(new Error("Something else"))); // "unknown"
170
+ * ```
171
+ *
172
+ * @param error The error to classify.
173
+ * @returns A string category such as `"auth"`, `"rate_limit"`, `"network"`,
174
+ * `"validation"`, `"server_error"`, or `"unknown"`.
175
+ * @since 0.2.0
176
+ */
177
+ declare function defaultErrorClassifier(error: unknown): string;
178
+ //#endregion
179
+ //#region src/opentelemetry-transport.d.ts
180
+ /**
181
+ * OpenTelemetry decorator transport that adds observability to any existing transport.
182
+ *
183
+ * This transport wraps another transport implementation to provide automatic
184
+ * OpenTelemetry metrics and tracing without requiring changes to existing code.
185
+ * It follows the decorator pattern, accepting any transport and adding standardized
186
+ * observability features including email delivery metrics, latency histograms,
187
+ * error classification, and distributed tracing support.
188
+ *
189
+ * The transport also supports automatic resource cleanup by implementing
190
+ * AsyncDisposable and properly disposing wrapped transports that support
191
+ * either Disposable or AsyncDisposable interfaces.
192
+ *
193
+ * @example Basic usage with explicit providers
194
+ * ```typescript
195
+ * import { OpenTelemetryTransport } from "@upyo/opentelemetry";
196
+ * import { createSmtpTransport } from "@upyo/smtp";
197
+ * import { trace, metrics } from "@opentelemetry/api";
198
+ *
199
+ * const baseTransport = createSmtpTransport({
200
+ * host: "smtp.example.com",
201
+ * port: 587,
202
+ * });
203
+ *
204
+ * const transport = new OpenTelemetryTransport(baseTransport, {
205
+ * tracerProvider: trace.getTracerProvider(),
206
+ * meterProvider: metrics.getMeterProvider(),
207
+ * tracing: {
208
+ * enabled: true,
209
+ * samplingRate: 1.0,
210
+ * recordSensitiveData: false,
211
+ * },
212
+ * metrics: {
213
+ * enabled: true,
214
+ * },
215
+ * });
216
+ *
217
+ * // Use the transport normally - it will automatically create spans and metrics
218
+ * await transport.send(message);
219
+ * ```
220
+ *
221
+ * @example With custom error classification and attribute extraction
222
+ * ```typescript
223
+ * const transport = new OpenTelemetryTransport(baseTransport, {
224
+ * tracerProvider: trace.getTracerProvider(),
225
+ * meterProvider: metrics.getMeterProvider(),
226
+ * errorClassifier: (error) => {
227
+ * if (error.message.includes("spam")) return "spam";
228
+ * if (error.message.includes("bounce")) return "bounce";
229
+ * return "unknown";
230
+ * },
231
+ * attributeExtractor: (operation, transportName) => ({
232
+ * "service.environment": process.env.NODE_ENV,
233
+ * "transport.type": transportName,
234
+ * }),
235
+ * });
236
+ * ```
237
+ *
238
+ * @since 0.2.0
239
+ */
240
+ declare class OpenTelemetryTransport implements Transport, AsyncDisposable {
241
+ /**
242
+ * The resolved OpenTelemetry configuration.
243
+ */
244
+ readonly config: ResolvedOpenTelemetryConfig;
245
+ private readonly wrappedTransport;
246
+ private readonly metricsCollector?;
247
+ private readonly tracingCollector?;
248
+ private readonly transportName;
249
+ private readonly transportVersion?;
250
+ /**
251
+ * Creates a new OpenTelemetry transport that wraps an existing transport.
252
+ *
253
+ * @param transport The base transport to wrap with observability.
254
+ * @param config OpenTelemetry configuration options.
255
+ */
256
+ constructor(transport: Transport, config?: OpenTelemetryConfig);
257
+ /**
258
+ * Sends a single email message with OpenTelemetry observability.
259
+ *
260
+ * @param message The email message to send.
261
+ * @param options Optional transport options including abort signal.
262
+ * @returns A promise that resolves to a receipt indicating success or failure.
263
+ */
264
+ send(message: Message, options?: TransportOptions): Promise<Receipt>;
265
+ /**
266
+ * Sends multiple email messages with OpenTelemetry observability.
267
+ *
268
+ * @param messages An iterable or async iterable of messages to send.
269
+ * @param options Optional transport options including abort signal.
270
+ * @returns An async iterable that yields receipts for each sent message.
271
+ */
272
+ sendMany(messages: Iterable<Message> | AsyncIterable<Message>, options?: TransportOptions): AsyncIterable<Receipt>;
273
+ /**
274
+ * Cleanup resources if the wrapped transport supports Disposable or AsyncDisposable.
275
+ */
276
+ [Symbol.asyncDispose](): Promise<void>;
277
+ private extractTransportName;
278
+ private extractTransportVersion;
279
+ private extractNetworkAttributes;
280
+ private classifyError;
281
+ private classifyErrors;
282
+ private estimateMessageSize;
283
+ }
284
+ //#endregion
285
+ //#region src/index.d.ts
286
+ /**
287
+ * Configuration options for the createOpenTelemetryTransport factory function.
288
+ *
289
+ * This interface extends the base OpenTelemetryConfig with additional options
290
+ * for service identification and auto-configuration. It provides a simplified
291
+ * way to configure OpenTelemetry observability without manually setting up
292
+ * providers and exporters.
293
+ *
294
+ * @example
295
+ * ```typescript
296
+ * const config: CreateOpenTelemetryTransportConfig = {
297
+ * serviceName: "email-service",
298
+ * serviceVersion: "1.2.0",
299
+ * tracing: {
300
+ * enabled: true,
301
+ * samplingRate: 0.1,
302
+ * recordSensitiveData: false,
303
+ * },
304
+ * metrics: {
305
+ * enabled: true,
306
+ * histogramBoundaries: [10, 50, 100, 500, 1000],
307
+ * },
308
+ * auto: {
309
+ * tracing: {
310
+ * endpoint: "http://jaeger:14268/api/traces",
311
+ * },
312
+ * metrics: {
313
+ * endpoint: "http://prometheus:9090/api/v1/write",
314
+ * },
315
+ * },
316
+ * };
317
+ * ```
318
+ *
319
+ * @since 0.2.0
320
+ */
321
+ interface CreateOpenTelemetryTransportConfig extends Omit<OpenTelemetryConfig, "auto"> {
322
+ /**
323
+ * Service name for OpenTelemetry resource attributes.
324
+ * This identifies your service in distributed traces and metrics.
325
+ * @default "email-service"
326
+ */
327
+ readonly serviceName?: string;
328
+ /**
329
+ * Service version for OpenTelemetry resource attributes.
330
+ * Useful for tracking performance across different deployment versions.
331
+ */
332
+ readonly serviceVersion?: string;
333
+ /**
334
+ * Auto-configuration options for setting up OpenTelemetry exporters and processors.
335
+ * When provided, the factory function will automatically configure the necessary
336
+ * infrastructure for sending telemetry data to observability backends.
337
+ */
338
+ readonly auto?: AutoConfig;
339
+ }
340
+ /**
341
+ * Creates an OpenTelemetry transport for email operations with comprehensive
342
+ * observability features.
343
+ *
344
+ * This function wraps an existing email transport with OpenTelemetry tracing
345
+ * and metrics collection, providing automatic instrumentation for email sending
346
+ * operations. It supports both single message and batch operations with
347
+ * configurable sampling, error classification, and attribute extraction.
348
+ * The function simplifies setup by automatically configuring providers when
349
+ * they're not explicitly provided, using global `TracerProvider` and
350
+ * `MeterProvider` from OpenTelemetry API as fallbacks.
351
+ *
352
+ * @param baseTransport The underlying email transport to wrap with
353
+ * OpenTelemetry instrumentation.
354
+ * @param config Configuration options for OpenTelemetry setup including
355
+ * providers, sampling rates, and custom extractors.
356
+ * @param config.serviceName Service name for OpenTelemetry resource attributes.
357
+ * Defaults to "email-service".
358
+ * @param config.serviceVersion Service version for OpenTelemetry resource
359
+ * attributes.
360
+ * @param config.tracerProvider Custom TracerProvider instance. Uses global
361
+ * provider if not specified.
362
+ * @param config.meterProvider Custom MeterProvider instance. Uses global
363
+ * provider if not specified.
364
+ * @param config.auto Auto-configuration options for setting up exporters and
365
+ * processors.
366
+ * @param config.tracing Tracing configuration including sampling rates and
367
+ * span settings.
368
+ * @param config.metrics Metrics configuration including histogram boundaries
369
+ * and collection settings.
370
+ * @param config.attributeExtractor Custom function for extracting attributes
371
+ * from operations.
372
+ * @param config.errorClassifier Custom function for categorizing errors into
373
+ * types.
374
+ * @returns An OpenTelemetry-enabled transport that instruments all email
375
+ * operations with traces and metrics.
376
+ *
377
+ * @example With minimal configuration
378
+ * ```typescript
379
+ * import { createOpenTelemetryTransport } from "@upyo/opentelemetry";
380
+ * import { createSmtpTransport } from "@upyo/smtp";
381
+ *
382
+ * const smtpTransport = createSmtpTransport({
383
+ * host: "smtp.example.com",
384
+ * port: 587,
385
+ * });
386
+ *
387
+ * const transport = createOpenTelemetryTransport(smtpTransport, {
388
+ * serviceName: "email-service",
389
+ * serviceVersion: "1.0.0",
390
+ * });
391
+ * ```
392
+ *
393
+ * @example With explicit providers
394
+ * ```typescript
395
+ * import { createOpenTelemetryTransport } from "@upyo/opentelemetry";
396
+ * import { trace, metrics } from "@opentelemetry/api";
397
+ *
398
+ * const transport = createOpenTelemetryTransport(baseTransport, {
399
+ * tracerProvider: trace.getTracerProvider(),
400
+ * meterProvider: metrics.getMeterProvider(),
401
+ * serviceName: "my-email-service",
402
+ * tracing: {
403
+ * enabled: true,
404
+ * samplingRate: 0.1,
405
+ * recordSensitiveData: false,
406
+ * },
407
+ * metrics: {
408
+ * enabled: true,
409
+ * histogramBoundaries: [10, 50, 100, 500, 1000, 5000],
410
+ * },
411
+ * });
412
+ * ```
413
+ *
414
+ * @example With auto-configuration
415
+ * ```typescript
416
+ * const transport = createOpenTelemetryTransport(baseTransport, {
417
+ * serviceName: "email-service",
418
+ * auto: {
419
+ * tracing: { endpoint: "http://jaeger:14268/api/traces" },
420
+ * metrics: { endpoint: "http://prometheus:9090/api/v1/write" }
421
+ * }
422
+ * });
423
+ * ```
424
+ *
425
+ * @since 0.2.0
426
+ */
427
+ declare function createOpenTelemetryTransport(baseTransport: Transport, config?: CreateOpenTelemetryTransportConfig): OpenTelemetryTransport;
428
+ /**
429
+ * Creates a custom email attribute extractor function for OpenTelemetry
430
+ * spans and metrics.
431
+ *
432
+ * This function creates a reusable attribute extractor that can be used to
433
+ * generate consistent attributes for OpenTelemetry traces and metrics across
434
+ * different email operations. It supports both basic transport information
435
+ * and custom attribute generation based on operation context.
436
+ *
437
+ * @param transportName The name of the email transport
438
+ * (e.g., `"smtp"`, `"mailgun"`, `"sendgrid"`).
439
+ * @param options Configuration options for customizing attribute extraction
440
+ * behavior.
441
+ * @param options.recordSensitiveData Whether to include sensitive data like
442
+ * email addresses in attributes.
443
+ * Defaults to false for security.
444
+ * @param options.transportVersion The version of the transport implementation
445
+ * for better observability.
446
+ * @param options.customAttributes A function to generate custom attributes
447
+ * based on operation context.
448
+ * @returns A function that extracts attributes from email operations,
449
+ * suitable for use with OpenTelemetry spans and metrics.
450
+ *
451
+ * @example Basic usage
452
+ * ```typescript
453
+ * import { createEmailAttributeExtractor } from "@upyo/opentelemetry";
454
+ *
455
+ * const extractor = createEmailAttributeExtractor("smtp", {
456
+ * transportVersion: "2.1.0",
457
+ * });
458
+ *
459
+ * const transport = createOpenTelemetryTransport(baseTransport, {
460
+ * attributeExtractor: extractor,
461
+ * });
462
+ * ```
463
+ *
464
+ * @example With custom attributes
465
+ * ```typescript
466
+ * import { createEmailAttributeExtractor } from "@upyo/opentelemetry";
467
+ *
468
+ * const extractor = createEmailAttributeExtractor("smtp", {
469
+ * recordSensitiveData: false,
470
+ * transportVersion: "2.1.0",
471
+ * customAttributes: (operation, transportName, messageCount, totalSize) => ({
472
+ * "service.environment": process.env.NODE_ENV || "development",
473
+ * "email.batch.size": messageCount || 1,
474
+ * "email.total.bytes": totalSize || 0,
475
+ * "transport.type": transportName,
476
+ * }),
477
+ * });
478
+ *
479
+ * const transport = createOpenTelemetryTransport(baseTransport, {
480
+ * attributeExtractor: extractor,
481
+ * });
482
+ * ```
483
+ *
484
+ * @since 0.2.0
485
+ */
486
+ declare function createEmailAttributeExtractor(_transportName: string, options?: {
487
+ readonly recordSensitiveData?: boolean;
488
+ readonly transportVersion?: string;
489
+ readonly customAttributes?: (operation: "send" | "send_batch", transportName: string, messageCount: number, totalSize?: number) => Record<string, string | number | boolean>;
490
+ }): AttributeExtractor;
491
+ /**
492
+ * Creates a custom error classifier function for categorizing email sending
493
+ * errors.
494
+ *
495
+ * This function creates an error classifier that categorizes errors into
496
+ * meaningful groups for better observability and alerting. It supports custom
497
+ * regex patterns for domain-specific error classification while falling back
498
+ * to the default classifier for common error types. The classifier is used to
499
+ * tag metrics and traces with error categories, enabling better
500
+ * monitoring and debugging of email delivery issues.
501
+ *
502
+ * @param options Configuration options for error classification behavior.
503
+ * @param options.patterns A record of category names mapped to regex patterns
504
+ * for custom error classification.
505
+ * @param options.fallback The fallback category to use when no patterns match
506
+ * and the default classifier returns "unknown".
507
+ * Defaults to "unknown".
508
+ * @returns A function that classifies errors into string categories for use
509
+ * in OpenTelemetry attributes and metrics.
510
+ *
511
+ * @example Basic usage with common patterns
512
+ * ```typescript
513
+ * import { createErrorClassifier } from "@upyo/opentelemetry";
514
+ *
515
+ * const classifier = createErrorClassifier({
516
+ * patterns: {
517
+ * spam: /blocked.*spam|spam.*detected/i,
518
+ * bounce: /bounce|undeliverable|invalid.*recipient/i,
519
+ * },
520
+ * fallback: "email_error",
521
+ * });
522
+ *
523
+ * const transport = createOpenTelemetryTransport(baseTransport, {
524
+ * errorClassifier: classifier,
525
+ * });
526
+ * ```
527
+ *
528
+ * @example Advanced patterns for specific email providers
529
+ * ```typescript
530
+ * import { createErrorClassifier } from "@upyo/opentelemetry";
531
+ *
532
+ * const classifier = createErrorClassifier({
533
+ * patterns: {
534
+ * spam: /blocked.*spam|spam.*detected|reputation/i,
535
+ * bounce: /bounce|undeliverable|invalid.*recipient/i,
536
+ * quota: /quota.*exceeded|mailbox.*full/i,
537
+ * reputation: /reputation|blacklist|blocked.*ip/i,
538
+ * temporary: /temporary.*failure|try.*again.*later/i,
539
+ * authentication: /authentication.*failed|invalid.*credentials/i,
540
+ * },
541
+ * fallback: "email_error",
542
+ * });
543
+ *
544
+ * // The classifier will categorize errors like:
545
+ * // new Error("Message blocked as spam") -> "spam"
546
+ * // new Error("Mailbox quota exceeded") -> "quota"
547
+ * // new Error("Authentication failed") -> "auth" (from default classifier)
548
+ * // new Error("Unknown error") -> "email_error" (fallback)
549
+ * ```
550
+ *
551
+ * @since 0.2.0
552
+ */
553
+ declare function createErrorClassifier(options?: {
554
+ readonly patterns?: Record<string, RegExp>;
555
+ readonly fallback?: string;
556
+ }): ErrorClassifier;
557
+ //#endregion
558
+ export { AttributeExtractor, AutoConfig, CreateOpenTelemetryTransportConfig, ErrorClassifier, MetricsConfig, ObservabilityConfig, OpenTelemetryConfig, OpenTelemetryTransport, TracingConfig, createEmailAttributeExtractor, createErrorClassifier, createOpenTelemetryTransport, defaultErrorClassifier };