@atrim/instrument-node 0.5.0 → 0.5.1-21bb978-20260105202350

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.
@@ -1,4 +1,6 @@
1
- import { Layer, FiberSet as FiberSet$1, Effect } from 'effect';
1
+ import { Layer, Effect, Tracer as Tracer$1, FiberSet as FiberSet$1 } from 'effect';
2
+ import * as Tracer from '@effect/opentelemetry/Tracer';
3
+ import { SpanContext } from '@opentelemetry/api';
2
4
  import { InstrumentationConfig } from '@atrim/instrument-core';
3
5
  import * as effect_Runtime from 'effect/Runtime';
4
6
  import * as effect_FiberId from 'effect/FiberId';
@@ -6,9 +8,10 @@ import * as effect_Scope from 'effect/Scope';
6
8
  import { RuntimeFiber } from 'effect/Fiber';
7
9
 
8
10
  /**
9
- * Node.js configuration loader using Effect Platform
11
+ * Node.js configuration loader
10
12
  *
11
- * Provides FileSystem and HttpClient layers for the core ConfigLoader service
13
+ * Provides configuration loading using native Node.js APIs (fs, fetch)
14
+ * This module doesn't require Effect Platform, making it work without Effect installed.
12
15
  */
13
16
 
14
17
  /**
@@ -46,17 +49,12 @@ interface ConfigLoaderOptions {
46
49
  */
47
50
  interface EffectInstrumentationOptions extends ConfigLoaderOptions {
48
51
  /**
49
- * OTLP endpoint URL
50
- * @default process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4318'
51
- */
52
- otlpEndpoint?: string;
53
- /**
54
- * Service name
52
+ * Service name for Effect spans
55
53
  * @default process.env.OTEL_SERVICE_NAME || 'effect-service'
56
54
  */
57
55
  serviceName?: string;
58
56
  /**
59
- * Service version
57
+ * Service version for Effect spans
60
58
  * @default process.env.npm_package_version || '1.0.0'
61
59
  */
62
60
  serviceVersion?: string;
@@ -66,20 +64,17 @@ interface EffectInstrumentationOptions extends ConfigLoaderOptions {
66
64
  */
67
65
  autoExtractMetadata?: boolean;
68
66
  /**
69
- * Whether to continue existing traces from NodeSDK auto-instrumentation
70
- *
71
- * When true (default):
72
- * - Effect spans become children of existing NodeSDK spans
73
- * - Example: HTTP request span → Effect business logic span
74
- * - Uses OpenTelemetry Context API for propagation
75
- *
76
- * When false:
77
- * - Effect operations always create new root spans
78
- * - Not recommended unless you have specific requirements
79
- *
80
- * @default true
67
+ * OTLP endpoint URL (only used when exporter mode is 'standalone')
68
+ * @default process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4318'
69
+ */
70
+ otlpEndpoint?: string;
71
+ /**
72
+ * Exporter mode:
73
+ * - 'unified': Use global TracerProvider from Node SDK (recommended, enables filtering)
74
+ * - 'standalone': Use Effect's own OTLP exporter (bypasses Node SDK filtering)
75
+ * @default 'unified'
81
76
  */
82
- continueExistingTraces?: boolean;
77
+ exporterMode?: 'unified' | 'standalone';
83
78
  }
84
79
  /**
85
80
  * Create Effect instrumentation layer with custom options
@@ -118,11 +113,12 @@ declare function createEffectInstrumentation(options?: EffectInstrumentationOpti
118
113
  *
119
114
  * Uses the global OpenTelemetry tracer provider that was set up by
120
115
  * initializeInstrumentation(). This ensures all traces (Express, Effect, etc.)
121
- * go to the same OTLP endpoint.
116
+ * go through the same TracerProvider and PatternSpanProcessor.
122
117
  *
123
118
  * Context Propagation:
124
119
  * - Automatically continues traces from NodeSDK auto-instrumentation
125
120
  * - Effect spans become children of HTTP request spans
121
+ * - Respects http.ignore_incoming_paths and other filtering patterns
126
122
  * - No configuration needed
127
123
  *
128
124
  * @example
@@ -138,27 +134,243 @@ declare function createEffectInstrumentation(options?: EffectInstrumentationOpti
138
134
  * )
139
135
  * ```
140
136
  */
141
- declare const EffectInstrumentationLive: Layer.Layer<never, never, never>;
137
+ declare const EffectInstrumentationLive: Layer.Layer<Tracer.OtelTracer, never, never>;
138
+ /**
139
+ * Bridge the current OpenTelemetry span context to Effect's tracer.
140
+ *
141
+ * Use this when running Effect code inside an HTTP handler that was
142
+ * auto-instrumented by OpenTelemetry. This ensures Effect spans become
143
+ * children of the HTTP span rather than starting a new trace.
144
+ *
145
+ * @example
146
+ * ```typescript
147
+ * // In an Express handler
148
+ * app.get('/api/users', async (req, res) => {
149
+ * const program = fetchUsers().pipe(
150
+ * Effect.withSpan('api.fetchUsers'),
151
+ * withOtelParentSpan, // Bridge to HTTP span
152
+ * Effect.provide(EffectInstrumentationLive)
153
+ * )
154
+ * const result = await Effect.runPromise(program)
155
+ * res.json(result)
156
+ * })
157
+ * ```
158
+ *
159
+ * Without this utility, Effect spans would start a new trace instead of
160
+ * continuing the HTTP request trace, resulting in disconnected spans.
161
+ *
162
+ * @param effect - The Effect to run with the current OTel span as parent
163
+ * @returns The same Effect with OTel parent span context attached
164
+ */
165
+ declare const withOtelParentSpan: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
166
+ /**
167
+ * Create an Effect that gets the current OpenTelemetry span context.
168
+ *
169
+ * This is useful when you need to access the current span context for
170
+ * custom propagation or logging purposes.
171
+ *
172
+ * @example
173
+ * ```typescript
174
+ * const program = Effect.gen(function* () {
175
+ * const spanContext = yield* getCurrentOtelSpanContext
176
+ * if (spanContext) {
177
+ * console.log('Current trace:', spanContext.traceId)
178
+ * }
179
+ * })
180
+ * ```
181
+ *
182
+ * @returns Effect that yields the current SpanContext or undefined if none
183
+ */
184
+ declare const getCurrentOtelSpanContext: Effect.Effect<SpanContext | undefined>;
185
+ /**
186
+ * Create an external span reference from the current OpenTelemetry context.
187
+ *
188
+ * This creates an Effect ExternalSpan that can be used as a parent for
189
+ * Effect spans. Useful when you need more control over span parenting.
190
+ *
191
+ * @example
192
+ * ```typescript
193
+ * const program = Effect.gen(function* () {
194
+ * const parentSpan = yield* getOtelParentSpan
195
+ * if (parentSpan) {
196
+ * yield* myOperation.pipe(
197
+ * Effect.withSpan('child.operation', { parent: parentSpan })
198
+ * )
199
+ * }
200
+ * })
201
+ * ```
202
+ *
203
+ * @returns Effect that yields an ExternalSpan or undefined if no active span
204
+ */
205
+ declare const getOtelParentSpan: Effect.Effect<Tracer$1.ExternalSpan | undefined>;
206
+ /**
207
+ * Run an async operation with the current Effect span set as the active OTel span.
208
+ *
209
+ * Use this when calling async code that uses OTel auto-instrumentation
210
+ * (e.g., fetch, database drivers) from within an Effect span. This ensures
211
+ * the auto-instrumented spans become children of the Effect span.
212
+ *
213
+ * @example
214
+ * ```typescript
215
+ * const fetchData = Effect.gen(function* () {
216
+ * // This fetch call will have the Effect span as its parent
217
+ * const result = yield* runWithOtelContext(
218
+ * Effect.tryPromise(() => fetch('https://api.example.com/data'))
219
+ * )
220
+ * return result
221
+ * }).pipe(Effect.withSpan('fetch.data'))
222
+ * ```
223
+ *
224
+ * Note: This is most useful when you need OTel auto-instrumentation (HTTP, DB)
225
+ * to see Effect spans as parents. For Effect-only code, this isn't needed.
226
+ *
227
+ * @param effect - The Effect containing async operations with OTel auto-instrumentation
228
+ * @returns The same Effect but with OTel context propagation during execution
229
+ */
230
+ declare const runWithOtelContext: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
231
+ /**
232
+ * Higher-order function to create an Effect that bridges both directions:
233
+ * 1. Captures active OTel span as Effect's parent (OTel → Effect)
234
+ * 2. Sets Effect span as active OTel span during execution (Effect → OTel)
235
+ *
236
+ * This is the recommended way to run Effect code in HTTP handlers when you need
237
+ * full bidirectional tracing with OTel auto-instrumentation.
238
+ *
239
+ * @example
240
+ * ```typescript
241
+ * // In an Express handler
242
+ * app.get('/api/users', async (req, res) => {
243
+ * const program = Effect.gen(function* () {
244
+ * // Effect spans are children of HTTP request span
245
+ * const users = yield* fetchUsersFromDb()
246
+ *
247
+ * // HTTP client spans (from fetch) are children of Effect span
248
+ * yield* notifyExternalService(users)
249
+ *
250
+ * return users
251
+ * }).pipe(
252
+ * Effect.withSpan('api.getUsers'),
253
+ * withFullOtelBridging, // Bidirectional bridging
254
+ * Effect.provide(EffectInstrumentationLive)
255
+ * )
256
+ *
257
+ * const result = await Effect.runPromise(program)
258
+ * res.json(result)
259
+ * })
260
+ * ```
261
+ *
262
+ * @param effect - The Effect to run with full OTel bridging
263
+ * @returns Effect with bidirectional OTel context bridging
264
+ */
265
+ declare const withFullOtelBridging: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
142
266
 
143
267
  /**
144
268
  * Effect-specific span annotation helpers
269
+ *
270
+ * Provides reusable helper functions for adding common span attributes.
271
+ * These helpers follow OpenTelemetry semantic conventions and platform patterns.
272
+ *
273
+ * Usage:
274
+ * ```typescript
275
+ * Effect.gen(function* () {
276
+ * yield* annotateUser(userId, email)
277
+ * yield* annotateBatch(items.length, 5)
278
+ * const results = yield* storage.writeBatch(items)
279
+ * yield* annotateBatch(items.length, 5, results.success, results.failures)
280
+ * }).pipe(Effect.withSpan('storage.writeBatch'))
281
+ * ```
145
282
  */
146
- declare function annotateUser(_userId: string, _email?: string): void;
147
- declare function annotateDataSize(_bytes: number, _count: number): void;
148
- declare function annotateBatch(_size: number, _batchSize: number): void;
149
- declare function annotateLLM(_model: string, _operation: string, _inputTokens: number, _outputTokens: number): void;
150
- declare function annotateQuery(_query: string, _database: string): void;
151
- declare function annotateHttpRequest(_method: string, _url: string, _statusCode: number): void;
152
- declare function annotateError(_error: Error, _context?: Record<string, string | number | boolean>): void;
153
- declare function annotatePriority(_priority: string): void;
154
- declare function annotateCache(_operation: string, _hit: boolean): void;
283
+
284
+ /**
285
+ * Annotate span with user context
286
+ *
287
+ * @param userId - User identifier
288
+ * @param email - Optional user email
289
+ * @param username - Optional username
290
+ */
291
+ declare function annotateUser(userId: string, email?: string, username?: string): Effect.Effect<void, never, never>;
292
+ /**
293
+ * Annotate span with data size metrics
294
+ *
295
+ * @param bytes - Total bytes processed
296
+ * @param items - Number of items
297
+ * @param compressionRatio - Optional compression ratio
298
+ */
299
+ declare function annotateDataSize(bytes: number, items: number, compressionRatio?: number): Effect.Effect<void, never, never>;
300
+ /**
301
+ * Annotate span with batch operation metadata
302
+ *
303
+ * @param totalItems - Total number of items in batch
304
+ * @param batchSize - Size of each batch
305
+ * @param successCount - Optional number of successful items
306
+ * @param failureCount - Optional number of failed items
307
+ */
308
+ declare function annotateBatch(totalItems: number, batchSize: number, successCount?: number, failureCount?: number): Effect.Effect<void, never, never>;
309
+ /**
310
+ * Annotate span with LLM operation metadata
311
+ *
312
+ * @param model - Model name (e.g., 'gpt-4', 'claude-3-opus')
313
+ * @param provider - LLM provider (e.g., 'openai', 'anthropic')
314
+ * @param tokens - Optional token usage information
315
+ */
316
+ declare function annotateLLM(model: string, provider: string, tokens?: {
317
+ prompt?: number;
318
+ completion?: number;
319
+ total?: number;
320
+ }): Effect.Effect<void, never, never>;
321
+ /**
322
+ * Annotate span with database query metadata
323
+ *
324
+ * @param query - SQL query or query description
325
+ * @param duration - Optional query duration in milliseconds
326
+ * @param rowCount - Optional number of rows returned/affected
327
+ * @param database - Optional database name
328
+ */
329
+ declare function annotateQuery(query: string, duration?: number, rowCount?: number, database?: string): Effect.Effect<void, never, never>;
330
+ /**
331
+ * Annotate span with HTTP request metadata
332
+ *
333
+ * @param method - HTTP method (GET, POST, etc.)
334
+ * @param url - Request URL
335
+ * @param statusCode - Optional HTTP status code
336
+ * @param contentLength - Optional response content length
337
+ */
338
+ declare function annotateHttpRequest(method: string, url: string, statusCode?: number, contentLength?: number): Effect.Effect<void, never, never>;
339
+ /**
340
+ * Annotate span with error context
341
+ *
342
+ * @param error - Error object or message
343
+ * @param recoverable - Whether the error is recoverable
344
+ * @param errorType - Optional error type/category
345
+ */
346
+ declare function annotateError(error: Error | string, recoverable: boolean, errorType?: string): Effect.Effect<void, never, never>;
347
+ /**
348
+ * Annotate span with operation priority
349
+ *
350
+ * @param priority - Priority level (high, medium, low)
351
+ * @param reason - Optional reason for priority level
352
+ */
353
+ declare function annotatePriority(priority: 'high' | 'medium' | 'low', reason?: string): Effect.Effect<void, never, never>;
354
+ /**
355
+ * Annotate span with cache operation metadata
356
+ *
357
+ * @param hit - Whether the cache was hit
358
+ * @param key - Cache key
359
+ * @param ttl - Optional time-to-live in seconds
360
+ */
361
+ declare function annotateCache(hit: boolean, key: string, ttl?: number): Effect.Effect<void, never, never>;
155
362
 
156
363
  /**
157
364
  * Effect metadata extraction
158
365
  *
159
366
  * Automatically extracts metadata from Effect fibers and adds them as span attributes.
160
367
  * This provides valuable context about the Effect execution environment.
368
+ *
369
+ * Uses Effect's public APIs:
370
+ * - Fiber.getCurrentFiber() - Get current fiber information
371
+ * - Effect.currentSpan - Detect parent spans and nesting
161
372
  */
373
+
162
374
  /**
163
375
  * Metadata extracted from Effect fibers
164
376
  */
@@ -166,8 +378,95 @@ interface EffectMetadata {
166
378
  'effect.fiber.id'?: string;
167
379
  'effect.fiber.status'?: string;
168
380
  'effect.operation.root'?: boolean;
169
- 'effect.operation.interrupted'?: boolean;
381
+ 'effect.operation.nested'?: boolean;
382
+ 'effect.parent.span.id'?: string;
383
+ 'effect.parent.span.name'?: string;
384
+ 'effect.parent.trace.id'?: string;
170
385
  }
386
+ /**
387
+ * Extract Effect-native metadata from current execution context
388
+ *
389
+ * Uses Effect's native APIs:
390
+ * - Fiber.getCurrentFiber() - Get current fiber information
391
+ * - Effect.currentSpan - Detect parent spans and nesting
392
+ *
393
+ * @returns Effect that yields extracted metadata
394
+ */
395
+ declare function extractEffectMetadata(): Effect.Effect<EffectMetadata>;
396
+
397
+ /**
398
+ * Effect Auto-Enrichment Utilities
399
+ *
400
+ * Provides utilities for automatically extracting and enriching spans with Effect-native metadata.
401
+ *
402
+ * Usage:
403
+ * ```typescript
404
+ * import { autoEnrichSpan, withAutoEnrichedSpan } from '@atrim/instrument-node/effect'
405
+ *
406
+ * // Option 1: Manual enrichment
407
+ * Effect.gen(function* () {
408
+ * yield* autoEnrichSpan() // Auto-add Effect metadata
409
+ * yield* annotateBatch(items.length, 10) // Add custom attributes
410
+ * const result = yield* storage.writeBatch(items)
411
+ * return result
412
+ * }).pipe(Effect.withSpan('storage.writeBatch'))
413
+ *
414
+ * // Option 2: Automatic enrichment wrapper
415
+ * const instrumented = withAutoEnrichedSpan('storage.writeBatch')(
416
+ * Effect.gen(function* () {
417
+ * yield* annotateBatch(items.length, 10)
418
+ * return yield* storage.writeBatch(items)
419
+ * })
420
+ * )
421
+ * ```
422
+ */
423
+
424
+ /**
425
+ * Auto-enrich the current span with Effect metadata
426
+ *
427
+ * This function should be called within an Effect.withSpan() context.
428
+ * It extracts Effect metadata (fiber ID, status, parent span info)
429
+ * and adds it as span attributes.
430
+ *
431
+ * Best practice: Call this at the start of your instrumented Effect:
432
+ *
433
+ * ```typescript
434
+ * Effect.gen(function* () {
435
+ * yield* autoEnrichSpan() // Auto-add metadata
436
+ * yield* annotateBatch(items.length, 10) // Add custom attributes
437
+ * const result = yield* storage.writeBatch(items)
438
+ * return result
439
+ * }).pipe(Effect.withSpan('storage.writeBatch'))
440
+ * ```
441
+ *
442
+ * @returns Effect that annotates the current span with Effect metadata
443
+ */
444
+ declare function autoEnrichSpan(): Effect.Effect<void>;
445
+ /**
446
+ * Create a wrapper that combines Effect.withSpan with automatic enrichment
447
+ *
448
+ * This is a convenience function that wraps an Effect with both:
449
+ * 1. A span (via Effect.withSpan)
450
+ * 2. Automatic metadata extraction (via autoEnrichSpan)
451
+ *
452
+ * Usage:
453
+ * ```typescript
454
+ * const instrumented = withAutoEnrichedSpan('storage.writeBatch')(
455
+ * Effect.gen(function* () {
456
+ * yield* annotateBatch(items.length, 10)
457
+ * return yield* storage.writeBatch(items)
458
+ * })
459
+ * )
460
+ * ```
461
+ *
462
+ * @param spanName - The name of the span
463
+ * @param options - Optional span options
464
+ * @returns Function that wraps an Effect with an auto-enriched span
465
+ */
466
+ declare function withAutoEnrichedSpan<A, E, R>(spanName: string, options?: {
467
+ readonly attributes?: Record<string, unknown>;
468
+ readonly kind?: 'client' | 'server' | 'producer' | 'consumer' | 'internal';
469
+ }): (self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
171
470
 
172
471
  /**
173
472
  * Options for span isolation when running effects in FiberSet
@@ -356,4 +655,4 @@ declare const FiberSet: {
356
655
  }) => Effect.Effect<RuntimeFiber<A, E>, never, R>;
357
656
  };
358
657
 
359
- export { EffectInstrumentationLive, type EffectMetadata, FiberSet, type IsolationOptions, annotateBatch, annotateCache, annotateDataSize, annotateError, annotateHttpRequest, annotateLLM, annotatePriority, annotateQuery, annotateSpawnedTasks, annotateUser, createEffectInstrumentation, runIsolated, runWithSpan };
658
+ export { EffectInstrumentationLive, type EffectMetadata, FiberSet, type IsolationOptions, annotateBatch, annotateCache, annotateDataSize, annotateError, annotateHttpRequest, annotateLLM, annotatePriority, annotateQuery, annotateSpawnedTasks, annotateUser, autoEnrichSpan, createEffectInstrumentation, extractEffectMetadata, getCurrentOtelSpanContext, getOtelParentSpan, runIsolated, runWithOtelContext, runWithSpan, withAutoEnrichedSpan, withFullOtelBridging, withOtelParentSpan };