@atrim/instrument-node 1.0.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,548 @@
1
+ import { Effect } from 'effect';
2
+ import { NodeSDKConfiguration, NodeSDK } from '@opentelemetry/sdk-node';
3
+ import { Instrumentation } from '@opentelemetry/instrumentation';
4
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
5
+ import { ConfigLoaderOptions, InstrumentationConfig, PatternMatcher } from '@atrim/instrument-core';
6
+ export { ConfigLoaderOptions, InstrumentationConfig, PatternConfig, PatternMatcher, getPatternMatcher, loadConfig, shouldInstrumentSpan } from '@atrim/instrument-core';
7
+ import * as effect_Cause from 'effect/Cause';
8
+ import * as effect_Types from 'effect/Types';
9
+ import { SpanProcessor, Span, ReadableSpan } from '@opentelemetry/sdk-trace-base';
10
+ import { Context, Span as Span$1 } from '@opentelemetry/api';
11
+
12
+ /**
13
+ * OTLP Exporter Factory
14
+ *
15
+ * Creates and configures OTLP trace exporters with smart defaults
16
+ */
17
+
18
+ interface OtlpExporterOptions {
19
+ /**
20
+ * OTLP endpoint URL
21
+ * Defaults to OTEL_EXPORTER_OTLP_ENDPOINT or http://localhost:4318/v1/traces
22
+ */
23
+ endpoint?: string;
24
+ /**
25
+ * Custom headers to send with requests
26
+ * Useful for authentication, custom routing, etc.
27
+ */
28
+ headers?: Record<string, string>;
29
+ }
30
+ /**
31
+ * Create an OTLP trace exporter with smart defaults
32
+ *
33
+ * Priority for endpoint:
34
+ * 1. Explicit endpoint option
35
+ * 2. OTEL_EXPORTER_OTLP_TRACES_ENDPOINT environment variable
36
+ * 3. OTEL_EXPORTER_OTLP_ENDPOINT environment variable
37
+ * 4. Default: http://localhost:4318/v1/traces
38
+ */
39
+ declare function createOtlpExporter(options?: OtlpExporterOptions): OTLPTraceExporter;
40
+ /**
41
+ * Get the OTLP endpoint that would be used with current configuration
42
+ *
43
+ * Useful for debugging and logging
44
+ */
45
+ declare function getOtlpEndpoint(options?: OtlpExporterOptions): string;
46
+
47
+ /**
48
+ * NodeSDK Initialization
49
+ *
50
+ * Provides comprehensive OpenTelemetry SDK initialization with smart defaults
51
+ */
52
+
53
+ interface SdkInitializationOptions extends ConfigLoaderOptions {
54
+ /**
55
+ * OTLP exporter configuration
56
+ */
57
+ otlp?: OtlpExporterOptions;
58
+ /**
59
+ * Service name
60
+ * If not provided, auto-detects from OTEL_SERVICE_NAME or package.json
61
+ */
62
+ serviceName?: string;
63
+ /**
64
+ * Service version
65
+ * If not provided, auto-detects from OTEL_SERVICE_VERSION or package.json
66
+ */
67
+ serviceVersion?: string;
68
+ /**
69
+ * Enable auto-instrumentation
70
+ * Default: auto-detected based on your runtime and framework
71
+ * - true: Enables Express, HTTP, and other common instrumentations
72
+ * - false: Disables all auto-instrumentation (manual spans only)
73
+ * - undefined: Smart detection (checks for Effect-TS usage)
74
+ */
75
+ autoInstrument?: boolean;
76
+ /**
77
+ * Custom instrumentations to add
78
+ * These are added in addition to auto-instrumentations (if enabled)
79
+ */
80
+ instrumentations?: Instrumentation[];
81
+ /**
82
+ * Advanced: Full NodeSDK configuration override
83
+ * Provides complete control over SDK initialization
84
+ */
85
+ sdk?: Partial<NodeSDKConfiguration>;
86
+ /**
87
+ * Disable automatic shutdown handler registration
88
+ * Default: false (automatic shutdown is enabled)
89
+ */
90
+ disableAutoShutdown?: boolean;
91
+ }
92
+ /**
93
+ * Get the current SDK instance
94
+ */
95
+ declare function getSdkInstance(): NodeSDK | null;
96
+ /**
97
+ * Shutdown the SDK
98
+ */
99
+ declare function shutdownSdk(): Promise<void>;
100
+ /**
101
+ * Reset SDK instance (useful for testing)
102
+ */
103
+ declare function resetSdk(): void;
104
+
105
+ /**
106
+ * Typed error hierarchy for @atrim/instrumentation
107
+ *
108
+ * These errors use Effect's Data.TaggedError for typed error handling
109
+ * with proper discriminated unions.
110
+ */
111
+ declare const ConfigError_base: new <A extends Record<string, any> = {}>(args: effect_Types.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => effect_Cause.YieldableError & {
112
+ readonly _tag: "ConfigError";
113
+ } & Readonly<A>;
114
+ /**
115
+ * Base error for configuration-related failures
116
+ */
117
+ declare class ConfigError extends ConfigError_base<{
118
+ reason: string;
119
+ cause?: unknown;
120
+ }> {
121
+ }
122
+ declare const ConfigUrlError_base: new <A extends Record<string, any> = {}>(args: effect_Types.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => effect_Cause.YieldableError & {
123
+ readonly _tag: "ConfigUrlError";
124
+ } & Readonly<A>;
125
+ /**
126
+ * Error when fetching configuration from a URL fails
127
+ */
128
+ declare class ConfigUrlError extends ConfigUrlError_base<{
129
+ reason: string;
130
+ cause?: unknown;
131
+ }> {
132
+ }
133
+ declare const ConfigValidationError_base: new <A extends Record<string, any> = {}>(args: effect_Types.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => effect_Cause.YieldableError & {
134
+ readonly _tag: "ConfigValidationError";
135
+ } & Readonly<A>;
136
+ /**
137
+ * Error when configuration validation fails (invalid YAML, schema mismatch)
138
+ */
139
+ declare class ConfigValidationError extends ConfigValidationError_base<{
140
+ reason: string;
141
+ cause?: unknown;
142
+ }> {
143
+ }
144
+ declare const ConfigFileError_base: new <A extends Record<string, any> = {}>(args: effect_Types.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => effect_Cause.YieldableError & {
145
+ readonly _tag: "ConfigFileError";
146
+ } & Readonly<A>;
147
+ /**
148
+ * Error when reading configuration from a file fails
149
+ */
150
+ declare class ConfigFileError extends ConfigFileError_base<{
151
+ reason: string;
152
+ cause?: unknown;
153
+ }> {
154
+ }
155
+ declare const ServiceDetectionError_base: new <A extends Record<string, any> = {}>(args: effect_Types.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => effect_Cause.YieldableError & {
156
+ readonly _tag: "ServiceDetectionError";
157
+ } & Readonly<A>;
158
+ /**
159
+ * Error when service detection fails (package.json not found, invalid format)
160
+ */
161
+ declare class ServiceDetectionError extends ServiceDetectionError_base<{
162
+ reason: string;
163
+ cause?: unknown;
164
+ }> {
165
+ }
166
+ declare const InitializationError_base: new <A extends Record<string, any> = {}>(args: effect_Types.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => effect_Cause.YieldableError & {
167
+ readonly _tag: "InitializationError";
168
+ } & Readonly<A>;
169
+ /**
170
+ * Error when OpenTelemetry SDK initialization fails
171
+ */
172
+ declare class InitializationError extends InitializationError_base<{
173
+ reason: string;
174
+ cause?: unknown;
175
+ }> {
176
+ }
177
+ declare const ExportError_base: new <A extends Record<string, any> = {}>(args: effect_Types.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => effect_Cause.YieldableError & {
178
+ readonly _tag: "ExportError";
179
+ } & Readonly<A>;
180
+ /**
181
+ * Error when span export fails (e.g., ECONNREFUSED to collector)
182
+ */
183
+ declare class ExportError extends ExportError_base<{
184
+ reason: string;
185
+ cause?: unknown;
186
+ }> {
187
+ }
188
+ declare const ShutdownError_base: new <A extends Record<string, any> = {}>(args: effect_Types.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => effect_Cause.YieldableError & {
189
+ readonly _tag: "ShutdownError";
190
+ } & Readonly<A>;
191
+ /**
192
+ * Error when shutting down the SDK fails
193
+ */
194
+ declare class ShutdownError extends ShutdownError_base<{
195
+ reason: string;
196
+ cause?: unknown;
197
+ }> {
198
+ }
199
+
200
+ /**
201
+ * Public API for standard OpenTelemetry usage
202
+ *
203
+ * This module provides the main entry point for complete OpenTelemetry
204
+ * initialization including NodeSDK, OTLP export, and pattern-based filtering.
205
+ *
206
+ * Available in two flavors:
207
+ * - Effect API (primary): For typed error handling and composability
208
+ * - Promise API (backward compatible): For traditional async/await usage
209
+ */
210
+
211
+ /**
212
+ * Initialize OpenTelemetry instrumentation with complete SDK setup
213
+ *
214
+ * This function provides a single-line initialization for OpenTelemetry:
215
+ * - Loads instrumentation.yaml configuration
216
+ * - Creates and configures OTLP exporter
217
+ * - Sets up pattern-based span filtering
218
+ * - Initializes NodeSDK with auto-instrumentations
219
+ * - Registers graceful shutdown handlers
220
+ *
221
+ * Configuration priority (highest to lowest):
222
+ * 1. Explicit config object (options.config)
223
+ * 2. Environment variable (ATRIM_INSTRUMENTATION_CONFIG)
224
+ * 3. Explicit path/URL (options.configPath or options.configUrl)
225
+ * 4. Project root file (./instrumentation.yaml)
226
+ * 5. Default config (built-in defaults)
227
+ *
228
+ * OTLP endpoint priority:
229
+ * 1. options.otlp.endpoint
230
+ * 2. OTEL_EXPORTER_OTLP_TRACES_ENDPOINT environment variable
231
+ * 3. OTEL_EXPORTER_OTLP_ENDPOINT environment variable
232
+ * 4. Default: http://localhost:4318/v1/traces
233
+ *
234
+ * Service name priority:
235
+ * 1. options.serviceName
236
+ * 2. OTEL_SERVICE_NAME environment variable
237
+ * 3. package.json name field
238
+ * 4. Default: 'unknown-service'
239
+ *
240
+ * @param options - Initialization options
241
+ * @returns The initialized NodeSDK instance
242
+ *
243
+ * @example
244
+ * ```typescript
245
+ * // Zero-config initialization (recommended)
246
+ * await initializeInstrumentation()
247
+ * // Auto-detects everything from env vars and package.json
248
+ *
249
+ * // With custom OTLP endpoint
250
+ * await initializeInstrumentation({
251
+ * otlp: {
252
+ * endpoint: 'https://otel-collector.company.com:4318'
253
+ * }
254
+ * })
255
+ *
256
+ * // With custom service name
257
+ * await initializeInstrumentation({
258
+ * serviceName: 'my-api-service',
259
+ * serviceVersion: '2.0.0'
260
+ * })
261
+ *
262
+ * // Disable auto-instrumentation (manual spans only)
263
+ * await initializeInstrumentation({
264
+ * autoInstrument: false
265
+ * })
266
+ *
267
+ * // With custom config file
268
+ * await initializeInstrumentation({
269
+ * configPath: './config/custom-instrumentation.yaml'
270
+ * })
271
+ *
272
+ * // With remote config URL
273
+ * await initializeInstrumentation({
274
+ * configUrl: 'https://config.company.com/instrumentation.yaml',
275
+ * cacheTimeout: 300_000 // 5 minutes
276
+ * })
277
+ *
278
+ * // Advanced: Full control
279
+ * await initializeInstrumentation({
280
+ * otlp: {
281
+ * endpoint: process.env.CUSTOM_ENDPOINT,
282
+ * headers: { 'x-api-key': 'secret' }
283
+ * },
284
+ * serviceName: 'my-service',
285
+ * autoInstrument: true,
286
+ * instrumentations: [], // custom instrumentations
287
+ * sdk: {
288
+ * // Additional NodeSDK configuration
289
+ * }
290
+ * })
291
+ * ```
292
+ */
293
+ declare function initializeInstrumentation(options?: SdkInitializationOptions): Promise<NodeSDK | null>;
294
+ /**
295
+ * Legacy initialization function for pattern-only mode
296
+ *
297
+ * This function only initializes pattern matching without setting up the NodeSDK.
298
+ * Use this if you want to manually configure OpenTelemetry while still using
299
+ * pattern-based filtering.
300
+ *
301
+ * @deprecated Use initializeInstrumentation() instead for complete setup
302
+ */
303
+ declare function initializePatternMatchingOnly(options?: SdkInitializationOptions): Promise<void>;
304
+ /**
305
+ * Initialize OpenTelemetry instrumentation (Effect version)
306
+ *
307
+ * Provides typed error handling and composability with Effect ecosystem.
308
+ * All errors are returned in the error channel, not thrown.
309
+ *
310
+ * @param options - Initialization options
311
+ * @returns Effect that yields the initialized NodeSDK or null
312
+ *
313
+ * @example
314
+ * ```typescript
315
+ * import { Effect } from 'effect'
316
+ * import { initializeInstrumentationEffect } from '@atrim/instrumentation'
317
+ *
318
+ * // Basic usage
319
+ * const program = initializeInstrumentationEffect()
320
+ *
321
+ * await Effect.runPromise(program)
322
+ *
323
+ * // With error handling
324
+ * const program = initializeInstrumentationEffect().pipe(
325
+ * Effect.catchTag('ConfigError', (error) => {
326
+ * console.error('Config error:', error.reason)
327
+ * return Effect.succeed(null)
328
+ * }),
329
+ * Effect.catchTag('InitializationError', (error) => {
330
+ * console.error('Init error:', error.reason)
331
+ * return Effect.succeed(null)
332
+ * })
333
+ * )
334
+ *
335
+ * await Effect.runPromise(program)
336
+ *
337
+ * // With custom options
338
+ * const program = initializeInstrumentationEffect({
339
+ * otlp: { endpoint: 'https://otel.company.com:4318' },
340
+ * serviceName: 'my-service'
341
+ * })
342
+ * ```
343
+ */
344
+ declare const initializeInstrumentationEffect: (options?: SdkInitializationOptions) => Effect.Effect<NodeSDK | null, InitializationError | ConfigError>;
345
+ /**
346
+ * Initialize pattern matching only (Effect version)
347
+ *
348
+ * Use this if you want manual OpenTelemetry setup with pattern filtering.
349
+ *
350
+ * @param options - Configuration options
351
+ * @returns Effect that yields void
352
+ *
353
+ * @example
354
+ * ```typescript
355
+ * import { Effect } from 'effect'
356
+ * import { initializePatternMatchingOnlyEffect } from '@atrim/instrumentation'
357
+ *
358
+ * const program = initializePatternMatchingOnlyEffect({
359
+ * configPath: './instrumentation.yaml'
360
+ * }).pipe(
361
+ * Effect.catchAll((error) => {
362
+ * console.error('Pattern matching setup failed:', error.reason)
363
+ * return Effect.succeed(undefined)
364
+ * })
365
+ * )
366
+ *
367
+ * await Effect.runPromise(program)
368
+ * ```
369
+ */
370
+ declare const initializePatternMatchingOnlyEffect: (options?: SdkInitializationOptions) => Effect.Effect<void, ConfigError>;
371
+
372
+ /**
373
+ * Service Detection Utilities
374
+ *
375
+ * Auto-detects service name and version from environment variables and package.json
376
+ * Uses Effect for typed error handling and composability
377
+ */
378
+
379
+ interface ServiceInfo {
380
+ name: string;
381
+ version?: string | undefined;
382
+ }
383
+ /**
384
+ * Detect service name and version (Effect version)
385
+ *
386
+ * Priority order:
387
+ * 1. OTEL_SERVICE_NAME environment variable
388
+ * 2. package.json name field
389
+ * 3. Fallback to 'unknown-service'
390
+ *
391
+ * Version:
392
+ * 1. OTEL_SERVICE_VERSION environment variable
393
+ * 2. package.json version field
394
+ * 3. undefined (not set)
395
+ *
396
+ * @returns Effect that yields ServiceInfo or ServiceDetectionError
397
+ */
398
+ declare const detectServiceInfo: Effect.Effect<ServiceInfo, ServiceDetectionError>;
399
+ /**
400
+ * Get service name with fallback (Effect version)
401
+ *
402
+ * Never fails - returns 'unknown-service' if detection fails
403
+ */
404
+ declare const getServiceName: Effect.Effect<string, never>;
405
+ /**
406
+ * Get service version with fallback (Effect version)
407
+ *
408
+ * Never fails - returns undefined if detection fails
409
+ */
410
+ declare const getServiceVersion: Effect.Effect<string | undefined, never>;
411
+ /**
412
+ * Get service info with fallback (Effect version)
413
+ *
414
+ * Never fails - returns default ServiceInfo if detection fails
415
+ */
416
+ declare const getServiceInfoWithFallback: Effect.Effect<ServiceInfo, never>;
417
+ /**
418
+ * Detect service name and version (Promise version)
419
+ *
420
+ * @deprecated Use `detectServiceInfo` Effect API for better error handling
421
+ * @returns Promise that resolves to ServiceInfo with fallback
422
+ */
423
+ declare function detectServiceInfoAsync(): Promise<ServiceInfo>;
424
+ /**
425
+ * Get service name with fallback (Promise version)
426
+ *
427
+ * @deprecated Use `getServiceName` Effect API
428
+ */
429
+ declare function getServiceNameAsync(): Promise<string>;
430
+ /**
431
+ * Get service version if available (Promise version)
432
+ *
433
+ * @deprecated Use `getServiceVersion` Effect API
434
+ */
435
+ declare function getServiceVersionAsync(): Promise<string | undefined>;
436
+
437
+ /**
438
+ * OpenTelemetry SpanProcessor for pattern-based filtering
439
+ *
440
+ * This processor filters spans based on configured patterns before they are exported.
441
+ * It wraps another processor (typically BatchSpanProcessor) and only forwards spans
442
+ * that match the instrumentation patterns.
443
+ */
444
+
445
+ /**
446
+ * SpanProcessor that filters spans based on pattern configuration
447
+ *
448
+ * This processor sits in the processing pipeline and decides whether a span
449
+ * should be forwarded to the next processor (for export) or dropped.
450
+ *
451
+ * Usage:
452
+ * ```typescript
453
+ * const exporter = new OTLPTraceExporter()
454
+ * const batchProcessor = new BatchSpanProcessor(exporter)
455
+ * const patternProcessor = new PatternSpanProcessor(config, batchProcessor)
456
+ *
457
+ * const sdk = new NodeSDK({
458
+ * spanProcessor: patternProcessor
459
+ * })
460
+ * ```
461
+ */
462
+ declare class PatternSpanProcessor implements SpanProcessor {
463
+ private matcher;
464
+ private wrappedProcessor;
465
+ constructor(config: InstrumentationConfig, wrappedProcessor: SpanProcessor);
466
+ /**
467
+ * Called when a span is started
468
+ *
469
+ * We check if the span should be instrumented here. If not, we can mark it
470
+ * to be dropped later in onEnd().
471
+ */
472
+ onStart(span: Span, parentContext: Context): void;
473
+ /**
474
+ * Called when a span is ended
475
+ *
476
+ * This is where we make the final decision on whether to export the span.
477
+ */
478
+ onEnd(span: ReadableSpan): void;
479
+ /**
480
+ * Shutdown the processor
481
+ */
482
+ shutdown(): Promise<void>;
483
+ /**
484
+ * Force flush any pending spans
485
+ */
486
+ forceFlush(): Promise<void>;
487
+ /**
488
+ * Get the pattern matcher (for debugging/testing)
489
+ */
490
+ getPatternMatcher(): PatternMatcher;
491
+ }
492
+
493
+ /**
494
+ * Standard OpenTelemetry span helpers (no Effect dependency)
495
+ *
496
+ * These helpers work with any OpenTelemetry span and don't require Effect-TS.
497
+ */
498
+
499
+ /**
500
+ * Set multiple attributes on a span at once
501
+ */
502
+ declare function setSpanAttributes(span: Span$1, attributes: Record<string, string | number | boolean>): void;
503
+ /**
504
+ * Record an exception on a span with optional context
505
+ */
506
+ declare function recordException(span: Span$1, error: Error, context?: Record<string, string | number | boolean>): void;
507
+ /**
508
+ * Mark a span as successful (OK status)
509
+ */
510
+ declare function markSpanSuccess(span: Span$1): void;
511
+ /**
512
+ * Mark a span as failed with an error message
513
+ */
514
+ declare function markSpanError(span: Span$1, message?: string): void;
515
+ /**
516
+ * Helper for HTTP request spans
517
+ */
518
+ declare function annotateHttpRequest(span: Span$1, method: string, url: string, statusCode?: number): void;
519
+ /**
520
+ * Helper for database query spans
521
+ */
522
+ declare function annotateDbQuery(span: Span$1, system: string, statement: string, table?: string): void;
523
+ /**
524
+ * Helper for cache operation spans
525
+ */
526
+ declare function annotateCacheOperation(span: Span$1, operation: 'get' | 'set' | 'delete' | 'clear', key: string, hit?: boolean): void;
527
+
528
+ /**
529
+ * Test utilities for handling shutdown errors during testing
530
+ */
531
+ /**
532
+ * Suppress ECONNREFUSED errors during shutdown in test environments
533
+ *
534
+ * This function installs error handlers that ignore connection refused errors
535
+ * when the OpenTelemetry SDK tries to export spans during shutdown. This is
536
+ * expected behavior when collectors are stopped before child processes finish.
537
+ *
538
+ * @example
539
+ * ```typescript
540
+ * import { suppressShutdownErrors } from '@atrim/instrumentation/test-utils'
541
+ *
542
+ * // Call at the start of your main function
543
+ * suppressShutdownErrors()
544
+ * ```
545
+ */
546
+ declare function suppressShutdownErrors(): void;
547
+
548
+ export { ConfigError, ConfigFileError, ConfigUrlError, ConfigValidationError, ExportError, InitializationError, type OtlpExporterOptions, PatternSpanProcessor, type SdkInitializationOptions, ServiceDetectionError, type ServiceInfo, ShutdownError, annotateCacheOperation, annotateDbQuery, annotateHttpRequest, createOtlpExporter, detectServiceInfoAsync as detectServiceInfo, detectServiceInfo as detectServiceInfoEffect, getOtlpEndpoint, getSdkInstance, getServiceInfoWithFallback, getServiceNameAsync as getServiceName, getServiceName as getServiceNameEffect, getServiceVersionAsync as getServiceVersion, getServiceVersion as getServiceVersionEffect, initializeInstrumentation, initializeInstrumentationEffect, initializePatternMatchingOnly, initializePatternMatchingOnlyEffect, markSpanError, markSpanSuccess, recordException, resetSdk, setSpanAttributes, shutdownSdk, suppressShutdownErrors };