@atrim/instrument-node 0.5.1-1451fcf-20260105212505 → 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, Tracer, Effect, FiberSet as FiberSet$1 } 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';
@@ -132,7 +134,135 @@ declare function createEffectInstrumentation(options?: EffectInstrumentationOpti
132
134
  * )
133
135
  * ```
134
136
  */
135
- declare const EffectInstrumentationLive: Layer.Layer<Tracer.Tracer, 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>;
136
266
 
137
267
  /**
138
268
  * Effect-specific span annotation helpers
@@ -525,4 +655,4 @@ declare const FiberSet: {
525
655
  }) => Effect.Effect<RuntimeFiber<A, E>, never, R>;
526
656
  };
527
657
 
528
- export { EffectInstrumentationLive, type EffectMetadata, FiberSet, type IsolationOptions, annotateBatch, annotateCache, annotateDataSize, annotateError, annotateHttpRequest, annotateLLM, annotatePriority, annotateQuery, annotateSpawnedTasks, annotateUser, autoEnrichSpan, createEffectInstrumentation, extractEffectMetadata, runIsolated, runWithSpan, withAutoEnrichedSpan };
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 };
@@ -1,4 +1,6 @@
1
- import { Layer, Tracer, Effect, FiberSet as FiberSet$1 } 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';
@@ -132,7 +134,135 @@ declare function createEffectInstrumentation(options?: EffectInstrumentationOpti
132
134
  * )
133
135
  * ```
134
136
  */
135
- declare const EffectInstrumentationLive: Layer.Layer<Tracer.Tracer, 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>;
136
266
 
137
267
  /**
138
268
  * Effect-specific span annotation helpers
@@ -525,4 +655,4 @@ declare const FiberSet: {
525
655
  }) => Effect.Effect<RuntimeFiber<A, E>, never, R>;
526
656
  };
527
657
 
528
- export { EffectInstrumentationLive, type EffectMetadata, FiberSet, type IsolationOptions, annotateBatch, annotateCache, annotateDataSize, annotateError, annotateHttpRequest, annotateLLM, annotatePriority, annotateQuery, annotateSpawnedTasks, annotateUser, autoEnrichSpan, createEffectInstrumentation, extractEffectMetadata, runIsolated, runWithSpan, withAutoEnrichedSpan };
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 };
@@ -1,9 +1,9 @@
1
- import { Data, Context, Effect, Layer, Tracer, FiberSet as FiberSet$1, Fiber, Option, FiberId } from 'effect';
2
- import * as OtelTracer from '@effect/opentelemetry/Tracer';
1
+ import { Data, Context, Effect, Layer, FiberSet as FiberSet$1, Fiber, Option, FiberId, Tracer as Tracer$1 } from 'effect';
2
+ import * as Tracer from '@effect/opentelemetry/Tracer';
3
3
  import * as Resource from '@effect/opentelemetry/Resource';
4
4
  import * as Otlp from '@effect/opentelemetry/Otlp';
5
5
  import { FetchHttpClient } from '@effect/platform';
6
- import { TraceFlags, trace, context } from '@opentelemetry/api';
6
+ import { trace, context, TraceFlags } from '@opentelemetry/api';
7
7
  import { TELEMETRY_SDK_LANGUAGE_VALUE_NODEJS, ATTR_TELEMETRY_SDK_NAME, ATTR_TELEMETRY_SDK_LANGUAGE } from '@opentelemetry/semantic-conventions';
8
8
  import { FileSystem } from '@effect/platform/FileSystem';
9
9
  import * as HttpClient from '@effect/platform/HttpClient';
@@ -81,6 +81,11 @@ var InstrumentationConfigSchema = z.object({
81
81
  // - "standalone": Use Effect's own OTLP exporter (bypasses Node SDK filtering)
82
82
  exporter: z.enum(["unified", "standalone"]).default("unified"),
83
83
  auto_extract_metadata: z.boolean(),
84
+ // Auto-bridge OpenTelemetry context to Effect spans
85
+ // When true, Effect spans automatically become children of the active OTel span
86
+ // (e.g., HTTP request span from auto-instrumentation)
87
+ // This is essential for proper trace hierarchy when using Effect with HTTP frameworks
88
+ auto_bridge_context: z.boolean().default(true),
84
89
  auto_isolation: AutoIsolationConfigSchema.optional()
85
90
  }).optional(),
86
91
  http: HttpFilteringConfigSchema.optional()
@@ -105,7 +110,8 @@ var defaultConfig = {
105
110
  effect: {
106
111
  enabled: true,
107
112
  exporter: "unified",
108
- auto_extract_metadata: true
113
+ auto_extract_metadata: true,
114
+ auto_bridge_context: true
109
115
  }
110
116
  };
111
117
  function parseAndValidateConfig(content) {
@@ -494,21 +500,6 @@ async function loadConfigWithOptions(options = {}) {
494
500
  // src/integrations/effect/effect-tracer.ts
495
501
  var SDK_NAME = "@effect/opentelemetry";
496
502
  var ATTR_TELEMETRY_EXPORTER_MODE = "telemetry.exporter.mode";
497
- var ATTR_EFFECT_INSTRUMENTED = "effect.instrumented";
498
- var EffectAttributeTracerLayer = Layer.effect(
499
- Tracer.Tracer,
500
- Effect.gen(function* () {
501
- const baseTracer = yield* OtelTracer.make;
502
- return Tracer.make({
503
- span: (name, parent, context2, links, startTime, kind, options) => {
504
- const span = baseTracer.span(name, parent, context2, links, startTime, kind, options);
505
- span.attribute(ATTR_EFFECT_INSTRUMENTED, true);
506
- return span;
507
- },
508
- context: (f, fiber) => baseTracer.context(f, fiber)
509
- });
510
- }).pipe(Effect.provide(OtelTracer.layerGlobalTracer))
511
- );
512
503
  function createEffectInstrumentation(options = {}) {
513
504
  return Layer.unwrapEffect(
514
505
  Effect.gen(function* () {
@@ -569,7 +560,7 @@ function createEffectInstrumentation(options = {}) {
569
560
  logger.log("Effect OpenTelemetry instrumentation (unified)");
570
561
  logger.log(` Service: ${serviceName}`);
571
562
  logger.log(" Using global TracerProvider for span export");
572
- return EffectAttributeTracerLayer.pipe(
563
+ return Tracer.layerGlobal.pipe(
573
564
  Layer.provide(
574
565
  Resource.layer({
575
566
  serviceName,
@@ -588,7 +579,7 @@ var EffectInstrumentationLive = Effect.sync(() => {
588
579
  logger.minimal(`@atrim/instrumentation/effect: Effect tracing enabled (${serviceName})`);
589
580
  logger.log("Effect OpenTelemetry tracer (unified)");
590
581
  logger.log(` Service: ${serviceName}`);
591
- return EffectAttributeTracerLayer.pipe(
582
+ return Tracer.layerGlobal.pipe(
592
583
  Layer.provide(
593
584
  Resource.layer({
594
585
  serviceName,
@@ -603,6 +594,55 @@ var EffectInstrumentationLive = Effect.sync(() => {
603
594
  )
604
595
  );
605
596
  }).pipe(Layer.unwrapEffect);
597
+ var withOtelParentSpan = (effect) => {
598
+ const currentSpan = trace.getSpan(context.active());
599
+ if (currentSpan) {
600
+ const spanContext = currentSpan.spanContext();
601
+ return Tracer.withSpanContext(spanContext)(effect);
602
+ }
603
+ return effect;
604
+ };
605
+ var getCurrentOtelSpanContext = Effect.sync(() => {
606
+ const currentSpan = trace.getSpan(context.active());
607
+ return currentSpan?.spanContext();
608
+ });
609
+ var getOtelParentSpan = Effect.sync(
610
+ () => {
611
+ const currentSpan = trace.getSpan(context.active());
612
+ if (!currentSpan) {
613
+ return void 0;
614
+ }
615
+ const spanContext = currentSpan.spanContext();
616
+ return Tracer.makeExternalSpan({
617
+ traceId: spanContext.traceId,
618
+ spanId: spanContext.spanId,
619
+ traceFlags: spanContext.traceFlags,
620
+ traceState: spanContext.traceState?.serialize()
621
+ });
622
+ }
623
+ );
624
+ var runWithOtelContext = (effect) => {
625
+ return Effect.flatMap(
626
+ Tracer.currentOtelSpan,
627
+ (otelSpan) => Effect.async((resume) => {
628
+ context.with(trace.setSpan(context.active(), otelSpan), () => {
629
+ Effect.runPromiseExit(effect).then((exit) => {
630
+ if (exit._tag === "Success") {
631
+ resume(Effect.succeed(exit.value));
632
+ } else {
633
+ resume(Effect.failCause(exit.cause));
634
+ }
635
+ });
636
+ });
637
+ })
638
+ ).pipe(
639
+ // If no OTel span is available, just run the effect normally
640
+ Effect.catchAll(() => effect)
641
+ );
642
+ };
643
+ var withFullOtelBridging = (effect) => {
644
+ return withOtelParentSpan(effect);
645
+ };
606
646
  function annotateUser(userId, email, username) {
607
647
  const attributes = {
608
648
  "user.id": userId
@@ -790,7 +830,7 @@ var runIsolated = (set, effect, name, options) => {
790
830
  return FiberSet$1.run(set, effect, { propagateInterruption });
791
831
  }
792
832
  return Effect.gen(function* () {
793
- const maybeParent = yield* Effect.serviceOption(Tracer.ParentSpan);
833
+ const maybeParent = yield* Effect.serviceOption(Tracer$1.ParentSpan);
794
834
  if (maybeParent._tag === "None" || !captureLogicalParent) {
795
835
  const isolated2 = effect.pipe(
796
836
  Effect.withSpan(name, {
@@ -867,6 +907,6 @@ var FiberSet = {
867
907
  runWithSpan
868
908
  };
869
909
 
870
- export { EffectInstrumentationLive, FiberSet, annotateBatch, annotateCache, annotateDataSize, annotateError, annotateHttpRequest, annotateLLM, annotatePriority, annotateQuery, annotateSpawnedTasks, annotateUser, autoEnrichSpan, createEffectInstrumentation, extractEffectMetadata, runIsolated, runWithSpan, withAutoEnrichedSpan };
910
+ export { EffectInstrumentationLive, FiberSet, annotateBatch, annotateCache, annotateDataSize, annotateError, annotateHttpRequest, annotateLLM, annotatePriority, annotateQuery, annotateSpawnedTasks, annotateUser, autoEnrichSpan, createEffectInstrumentation, extractEffectMetadata, getCurrentOtelSpanContext, getOtelParentSpan, runIsolated, runWithOtelContext, runWithSpan, withAutoEnrichedSpan, withFullOtelBridging, withOtelParentSpan };
871
911
  //# sourceMappingURL=index.js.map
872
912
  //# sourceMappingURL=index.js.map