@atrim/instrument-node 0.1.3-12a8f92-20251118011211

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,343 @@
1
+ import { Layer, FiberSet as FiberSet$1, Effect } from 'effect';
2
+ import { ConfigLoaderOptions } from '@atrim/instrument-core';
3
+ import * as effect_Runtime from 'effect/Runtime';
4
+ import * as effect_FiberId from 'effect/FiberId';
5
+ import * as effect_Scope from 'effect/Scope';
6
+ import { RuntimeFiber } from 'effect/Fiber';
7
+
8
+ /**
9
+ * Effect-TS Tracer integration with context propagation
10
+ *
11
+ * This module provides Effect-TS tracing that seamlessly integrates with
12
+ * OpenTelemetry NodeSDK auto-instrumentation. Effect spans will automatically
13
+ * continue existing traces created by NodeSDK (e.g., HTTP requests).
14
+ *
15
+ * Context Propagation:
16
+ * - NodeSDK auto-instrumentation creates root spans (e.g., HTTP requests)
17
+ * - Effect operations automatically become child spans of the active trace
18
+ * - Uses OpenTelemetry Context API (equivalent to Java thread-local)
19
+ * - No configuration needed - works out of the box
20
+ *
21
+ * Architecture:
22
+ * 1. @effect/opentelemetry uses the global OpenTelemetry tracer provider
23
+ * 2. When an Effect operation starts, it checks context.active() for existing spans
24
+ * 3. If found, creates child spans. If not, creates new root span.
25
+ * 4. This happens automatically via OpenTelemetry Context propagation
26
+ */
27
+
28
+ /**
29
+ * Configuration options for Effect instrumentation
30
+ */
31
+ interface EffectInstrumentationOptions extends ConfigLoaderOptions {
32
+ /**
33
+ * OTLP endpoint URL
34
+ * @default process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4318'
35
+ */
36
+ otlpEndpoint?: string;
37
+ /**
38
+ * Service name
39
+ * @default process.env.OTEL_SERVICE_NAME || 'effect-service'
40
+ */
41
+ serviceName?: string;
42
+ /**
43
+ * Service version
44
+ * @default process.env.npm_package_version || '1.0.0'
45
+ */
46
+ serviceVersion?: string;
47
+ /**
48
+ * Whether to automatically extract Effect fiber metadata
49
+ * @default true
50
+ */
51
+ autoExtractMetadata?: boolean;
52
+ /**
53
+ * Whether to continue existing traces from NodeSDK auto-instrumentation
54
+ *
55
+ * When true (default):
56
+ * - Effect spans become children of existing NodeSDK spans
57
+ * - Example: HTTP request span → Effect business logic span
58
+ * - Uses OpenTelemetry Context API for propagation
59
+ *
60
+ * When false:
61
+ * - Effect operations always create new root spans
62
+ * - Not recommended unless you have specific requirements
63
+ *
64
+ * @default true
65
+ */
66
+ continueExistingTraces?: boolean;
67
+ }
68
+ /**
69
+ * Create Effect instrumentation layer with custom options
70
+ *
71
+ * This function creates an Effect Layer that provides OpenTelemetry tracing
72
+ * with automatic context propagation from NodeSDK auto-instrumentation.
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * // With NodeSDK auto-instrumentation
77
+ * import { NodeSDK } from '@opentelemetry/sdk-node'
78
+ * import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
79
+ *
80
+ * // 1. Start NodeSDK (creates HTTP spans automatically)
81
+ * const sdk = new NodeSDK({
82
+ * instrumentations: [getNodeAutoInstrumentations()]
83
+ * })
84
+ * sdk.start()
85
+ *
86
+ * // 2. Create Effect instrumentation (will continue NodeSDK traces)
87
+ * const EffectLayer = createEffectInstrumentation()
88
+ *
89
+ * // 3. Use in Effect operations
90
+ * const program = Effect.gen(function* () {
91
+ * // This span will be a child of the HTTP request span (if any)
92
+ * yield* Effect.log("Business logic")
93
+ * }).pipe(
94
+ * Effect.withSpan("app.business.logic"),
95
+ * Effect.provide(EffectLayer)
96
+ * )
97
+ * ```
98
+ */
99
+ declare function createEffectInstrumentation(options?: EffectInstrumentationOptions): Layer.Layer<never, never, never>;
100
+ /**
101
+ * Zero-config Effect instrumentation layer
102
+ *
103
+ * Uses the global OpenTelemetry tracer provider that was set up by
104
+ * initializeInstrumentation(). This ensures all traces (Express, Effect, etc.)
105
+ * go to the same OTLP endpoint.
106
+ *
107
+ * Context Propagation:
108
+ * - Automatically continues traces from NodeSDK auto-instrumentation
109
+ * - Effect spans become children of HTTP request spans
110
+ * - No configuration needed
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * import { EffectInstrumentationLive } from '@atrim/instrumentation/effect'
115
+ *
116
+ * const program = Effect.gen(function* () {
117
+ * // This span continues any existing trace from NodeSDK
118
+ * yield* Effect.log("Processing")
119
+ * }).pipe(
120
+ * Effect.withSpan("app.process"),
121
+ * Effect.provide(EffectInstrumentationLive)
122
+ * )
123
+ * ```
124
+ */
125
+ declare const EffectInstrumentationLive: Layer.Layer<never, never, never>;
126
+
127
+ /**
128
+ * Effect-specific span annotation helpers
129
+ */
130
+ declare function annotateUser(_userId: string, _email?: string): void;
131
+ declare function annotateDataSize(_bytes: number, _count: number): void;
132
+ declare function annotateBatch(_size: number, _batchSize: number): void;
133
+ declare function annotateLLM(_model: string, _operation: string, _inputTokens: number, _outputTokens: number): void;
134
+ declare function annotateQuery(_query: string, _database: string): void;
135
+ declare function annotateHttpRequest(_method: string, _url: string, _statusCode: number): void;
136
+ declare function annotateError(_error: Error, _context?: Record<string, string | number | boolean>): void;
137
+ declare function annotatePriority(_priority: string): void;
138
+ declare function annotateCache(_operation: string, _hit: boolean): void;
139
+
140
+ /**
141
+ * Effect metadata extraction
142
+ *
143
+ * Automatically extracts metadata from Effect fibers and adds them as span attributes.
144
+ * This provides valuable context about the Effect execution environment.
145
+ */
146
+ /**
147
+ * Metadata extracted from Effect fibers
148
+ */
149
+ interface EffectMetadata {
150
+ 'effect.fiber.id'?: string;
151
+ 'effect.fiber.status'?: string;
152
+ 'effect.operation.root'?: boolean;
153
+ 'effect.operation.interrupted'?: boolean;
154
+ }
155
+
156
+ /**
157
+ * Options for span isolation when running effects in FiberSet
158
+ */
159
+ interface IsolationOptions {
160
+ /**
161
+ * Whether to create a root span (breaks parent chain)
162
+ * @default true (from config or this default)
163
+ */
164
+ readonly createRoot?: boolean;
165
+ /**
166
+ * Capture logical parent relationship via span links and attributes
167
+ * @default true
168
+ */
169
+ readonly captureLogicalParent?: boolean;
170
+ /**
171
+ * Use OpenTelemetry span links to track logical parent
172
+ * Works in: Honeycomb, Datadog, Lightstep, SigNoz, Splunk
173
+ * @default true
174
+ */
175
+ readonly useSpanLinks?: boolean;
176
+ /**
177
+ * Add custom attributes for logical parent tracking
178
+ * Works in: ALL observability tools (universal fallback)
179
+ * @default true
180
+ */
181
+ readonly useAttributes?: boolean;
182
+ /**
183
+ * Propagate interruption from parent
184
+ * @default undefined (FiberSet.run default behavior)
185
+ */
186
+ readonly propagateInterruption?: boolean;
187
+ /**
188
+ * Custom span attributes to add
189
+ */
190
+ readonly attributes?: Record<string, unknown>;
191
+ /**
192
+ * Span category for Atrim grouping
193
+ * @default "background_task"
194
+ */
195
+ readonly category?: string;
196
+ }
197
+ /**
198
+ * Run an effect in a FiberSet with automatic span isolation and virtual parent tracking.
199
+ *
200
+ * This function prevents span context leakage (fibers inheriting parent spans incorrectly)
201
+ * while maintaining logical parent relationships for observability.
202
+ *
203
+ * **Technical:** Uses `{ root: true }` to create independent root span
204
+ * **Observability:** Uses span links + attributes to track logical parent
205
+ *
206
+ * @example
207
+ * ```typescript
208
+ * import { runIsolated } from "@atrim/instrumentation/effect/fiberset"
209
+ *
210
+ * const program = Effect.scoped(
211
+ * Effect.gen(function* () {
212
+ * const set = yield* FiberSet.make()
213
+ *
214
+ * yield* Effect.gen(function* () {
215
+ * // Spawn background tasks with automatic isolation
216
+ * yield* runIsolated(set, backgroundTask1(), "background-task-1")
217
+ * yield* runIsolated(set, backgroundTask2(), "background-task-2")
218
+ * }).pipe(
219
+ * Effect.withSpan("parent-operation")
220
+ * )
221
+ *
222
+ * yield* Effect.sleep("100 millis")
223
+ * })
224
+ * )
225
+ *
226
+ * // In Honeycomb/Datadog/SigNoz:
227
+ * // - background tasks are ROOT spans (no context leakage)
228
+ * // - span links show they were spawned by parent-operation
229
+ * // - can reconstruct virtual hierarchy
230
+ * ```
231
+ *
232
+ * @param set - The FiberSet to run the effect in
233
+ * @param effect - The effect to run (will be isolated)
234
+ * @param name - Span name for the isolated effect
235
+ * @param options - Configuration options
236
+ * @returns Effect that returns the forked fiber
237
+ *
238
+ * @see https://github.com/atrim-ai/instrumentation/issues/5
239
+ */
240
+ declare const runIsolated: <A, E, R>(set: FiberSet$1.FiberSet<A, E>, effect: Effect.Effect<A, E, R>, name: string, options?: IsolationOptions) => Effect.Effect<RuntimeFiber<A, E>, never, R>;
241
+ /**
242
+ * Convenience function to run an effect in a FiberSet with automatic span isolation.
243
+ *
244
+ * This is a simpler version of `runIsolated` with sensible defaults.
245
+ *
246
+ * @example
247
+ * ```typescript
248
+ * yield* runWithSpan(set, "background-task", backgroundTask(), {
249
+ * attributes: { "task.priority": "high" }
250
+ * })
251
+ * ```
252
+ */
253
+ declare const runWithSpan: <A, E, R>(set: FiberSet$1.FiberSet<A, E>, name: string, effect: Effect.Effect<A, E, R>, options?: {
254
+ readonly propagateInterruption?: boolean;
255
+ readonly attributes?: Record<string, unknown>;
256
+ readonly category?: string;
257
+ }) => Effect.Effect<RuntimeFiber<A, E>, never, R>;
258
+ /**
259
+ * Annotate the current span with metadata about spawned FiberSet tasks.
260
+ *
261
+ * Call this in the parent operation before spawning tasks to add metadata
262
+ * that helps reconstruct the virtual hierarchy.
263
+ *
264
+ * @example
265
+ * ```typescript
266
+ * yield* Effect.gen(function* () {
267
+ * yield* annotateSpawnedTasks([
268
+ * { name: "background-task-1" },
269
+ * { name: "background-task-2" },
270
+ * { name: "background-task-3" }
271
+ * ])
272
+ *
273
+ * yield* runIsolated(set, task1(), "background-task-1")
274
+ * yield* runIsolated(set, task2(), "background-task-2")
275
+ * yield* runIsolated(set, task3(), "background-task-3")
276
+ * }).pipe(
277
+ * Effect.withSpan("parent-operation")
278
+ * )
279
+ * ```
280
+ */
281
+ declare const annotateSpawnedTasks: (tasks: Array<{
282
+ name: string;
283
+ spanId?: string;
284
+ category?: string;
285
+ }>) => Effect.Effect<void>;
286
+ /**
287
+ * Re-export FiberSet namespace with isolation helpers
288
+ *
289
+ * This provides a convenient way to import all FiberSet functions:
290
+ * ```typescript
291
+ * import { FiberSet } from "@atrim/instrumentation/effect/fiberset"
292
+ * ```
293
+ */
294
+ declare const FiberSet: {
295
+ make: <A = unknown, E = unknown>() => Effect.Effect<FiberSet$1.FiberSet<A, E>, never, effect_Scope.Scope>;
296
+ add: {
297
+ <A, E, XE extends E, XA extends A>(fiber: RuntimeFiber<XA, XE>, options?: {
298
+ readonly propagateInterruption?: boolean | undefined;
299
+ } | undefined): (self: FiberSet$1.FiberSet<A, E>) => Effect.Effect<void>;
300
+ <A, E, XE extends E, XA extends A>(self: FiberSet$1.FiberSet<A, E>, fiber: RuntimeFiber<XA, XE>, options?: {
301
+ readonly propagateInterruption?: boolean | undefined;
302
+ } | undefined): Effect.Effect<void>;
303
+ };
304
+ unsafeAdd: {
305
+ <A, E, XE extends E, XA extends A>(fiber: RuntimeFiber<XA, XE>, options?: {
306
+ readonly interruptAs?: effect_FiberId.FiberId | undefined;
307
+ readonly propagateInterruption?: boolean | undefined;
308
+ } | undefined): (self: FiberSet$1.FiberSet<A, E>) => void;
309
+ <A, E, XE extends E, XA extends A>(self: FiberSet$1.FiberSet<A, E>, fiber: RuntimeFiber<XA, XE>, options?: {
310
+ readonly interruptAs?: effect_FiberId.FiberId | undefined;
311
+ readonly propagateInterruption?: boolean | undefined;
312
+ } | undefined): void;
313
+ };
314
+ run: {
315
+ <A, E>(self: FiberSet$1.FiberSet<A, E>, options?: {
316
+ readonly propagateInterruption?: boolean | undefined;
317
+ } | undefined): <R, XE extends E, XA extends A>(effect: Effect.Effect<XA, XE, R>) => Effect.Effect<RuntimeFiber<XA, XE>, never, R>;
318
+ <A, E, R, XE extends E, XA extends A>(self: FiberSet$1.FiberSet<A, E>, effect: Effect.Effect<XA, XE, R>, options?: {
319
+ readonly propagateInterruption?: boolean | undefined;
320
+ } | undefined): Effect.Effect<RuntimeFiber<XA, XE>, never, R>;
321
+ };
322
+ clear: <A, E>(self: FiberSet$1.FiberSet<A, E>) => Effect.Effect<void>;
323
+ join: <A, E>(self: FiberSet$1.FiberSet<A, E>) => Effect.Effect<void, E>;
324
+ awaitEmpty: <A, E>(self: FiberSet$1.FiberSet<A, E>) => Effect.Effect<void>;
325
+ size: <A, E>(self: FiberSet$1.FiberSet<A, E>) => Effect.Effect<number>;
326
+ runtime: <A, E>(self: FiberSet$1.FiberSet<A, E>) => <R = never>() => Effect.Effect<(<XE extends E, XA extends A>(effect: Effect.Effect<XA, XE, R>, options?: (effect_Runtime.RunForkOptions & {
327
+ readonly propagateInterruption?: boolean | undefined;
328
+ }) | undefined) => RuntimeFiber<XA, XE>), never, R>;
329
+ runtimePromise: <A, E>(self: FiberSet$1.FiberSet<A, E>) => <R = never>() => Effect.Effect<(<XE extends E, XA extends A>(effect: Effect.Effect<XA, XE, R>, options?: (effect_Runtime.RunForkOptions & {
330
+ readonly propagateInterruption?: boolean | undefined;
331
+ }) | undefined) => Promise<XA>), never, R>;
332
+ makeRuntime: <R = never, A = unknown, E = unknown>() => Effect.Effect<(<XE extends E, XA extends A>(effect: Effect.Effect<XA, XE, R>, options?: effect_Runtime.RunForkOptions | undefined) => RuntimeFiber<XA, XE>), never, effect_Scope.Scope | R>;
333
+ makeRuntimePromise: <R = never, A = unknown, E = unknown>() => Effect.Effect<(<XE extends E, XA extends A>(effect: Effect.Effect<XA, XE, R>, options?: effect_Runtime.RunForkOptions | undefined) => Promise<XA>), never, effect_Scope.Scope | R>;
334
+ isFiberSet: (u: unknown) => u is FiberSet$1.FiberSet<unknown, unknown>;
335
+ runIsolated: <A, E, R>(set: FiberSet$1.FiberSet<A, E>, effect: Effect.Effect<A, E, R>, name: string, options?: IsolationOptions) => Effect.Effect<RuntimeFiber<A, E>, never, R>;
336
+ runWithSpan: <A, E, R>(set: FiberSet$1.FiberSet<A, E>, name: string, effect: Effect.Effect<A, E, R>, options?: {
337
+ readonly propagateInterruption?: boolean;
338
+ readonly attributes?: Record<string, unknown>;
339
+ readonly category?: string;
340
+ }) => Effect.Effect<RuntimeFiber<A, E>, never, R>;
341
+ };
342
+
343
+ export { EffectInstrumentationLive, type EffectMetadata, FiberSet, type IsolationOptions, annotateBatch, annotateCache, annotateDataSize, annotateError, annotateHttpRequest, annotateLLM, annotatePriority, annotateQuery, annotateSpawnedTasks, annotateUser, createEffectInstrumentation, runIsolated, runWithSpan };