From deec64d18d45be58ddac4e5c38fd18b1c9a2f470 Mon Sep 17 00:00:00 2001 From: Michael Arnaldi Date: Tue, 26 Aug 2025 12:44:57 +0200 Subject: [PATCH] Automatically set otel parent when present as external span --- .changeset/giant-clowns-relax.md | 6 +++ packages/effect/src/Tracer.ts | 3 +- packages/effect/src/internal/core-effect.ts | 3 +- packages/opentelemetry/src/internal/tracer.ts | 37 ++++++++++++++++--- 4 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 .changeset/giant-clowns-relax.md diff --git a/.changeset/giant-clowns-relax.md b/.changeset/giant-clowns-relax.md new file mode 100644 index 00000000000..a655b12c24a --- /dev/null +++ b/.changeset/giant-clowns-relax.md @@ -0,0 +1,6 @@ +--- +"@effect/opentelemetry": minor +"effect": minor +--- + +Automatically set otel parent when present as external span diff --git a/packages/effect/src/Tracer.ts b/packages/effect/src/Tracer.ts index d0bd9487f25..c8c27ef7aeb 100644 --- a/packages/effect/src/Tracer.ts +++ b/packages/effect/src/Tracer.ts @@ -31,7 +31,8 @@ export interface Tracer { context: Context.Context, links: ReadonlyArray, startTime: bigint, - kind: SpanKind + kind: SpanKind, + options?: SpanOptions ): Span context(f: () => X, fiber: Fiber.RuntimeFiber): X } diff --git a/packages/effect/src/internal/core-effect.ts b/packages/effect/src/internal/core-effect.ts index 871d6f60468..04eb1eef819 100644 --- a/packages/effect/src/internal/core-effect.ts +++ b/packages/effect/src/internal/core-effect.ts @@ -2126,7 +2126,8 @@ export const unsafeMakeSpan = ( options.context ?? Context.empty(), links, timingEnabled ? clock.unsafeCurrentTimeNanos() : bigint0, - options.kind ?? "internal" + options.kind ?? "internal", + options ) if (annotationsFromEnv._tag === "Some") { diff --git a/packages/opentelemetry/src/internal/tracer.ts b/packages/opentelemetry/src/internal/tracer.ts index f0eaeeedb93..dfb4bf53d70 100644 --- a/packages/opentelemetry/src/internal/tracer.ts +++ b/packages/opentelemetry/src/internal/tracer.ts @@ -22,6 +22,21 @@ const kindMap = { "consumer": OtelApi.SpanKind.CONSUMER } +const getOtelParent = (tracer: OtelApi.TraceAPI, otelContext: OtelApi.Context, context: Context.Context) => { + const active = tracer.getSpan(otelContext) + const otelParent = active ? active.spanContext() : undefined + return otelParent + ? Option.some( + EffectTracer.externalSpan({ + spanId: otelParent.spanId, + traceId: otelParent.traceId, + sampled: (otelParent.traceFlags & 1) === 1, + context + }) + ) + : Option.none() +} + /** @internal */ export class OtelSpan implements EffectTracer.Span { readonly [OtelSpanTypeId]: typeof OtelSpanTypeId @@ -32,20 +47,28 @@ export class OtelSpan implements EffectTracer.Span { readonly traceId: string readonly attributes = new Map() readonly sampled: boolean + readonly parent: Option.Option status: EffectTracer.SpanStatus constructor( contextApi: OtelApi.ContextAPI, + traceApi: OtelApi.TraceAPI, tracer: OtelApi.Tracer, readonly name: string, - readonly parent: Option.Option, + effectParent: Option.Option, readonly context: Context.Context, readonly links: Array, startTime: bigint, - readonly kind: EffectTracer.SpanKind + readonly kind: EffectTracer.SpanKind, + options?: EffectTracer.SpanOptions ) { this[OtelSpanTypeId] = OtelSpanTypeId const active = contextApi.active() + this.parent = effectParent._tag === "Some" + ? effectParent + : (options?.root !== true) + ? getOtelParent(traceApi, active, context) + : Option.none() this.span = tracer.startSpan( name, { @@ -58,8 +81,8 @@ export class OtelSpan implements EffectTracer.Span { : undefined as any, kind: kindMap[this.kind] }, - parent._tag === "Some" - ? populateContext(active, parent.value, context) + this.parent._tag === "Some" + ? populateContext(active, this.parent.value, context) : OtelApi.trace.deleteSpan(active) ) const spanContext = this.span.spanContext() @@ -143,16 +166,18 @@ export const Tracer = Context.GenericTag("@effect/op /** @internal */ export const make = Effect.map(Tracer, (tracer) => EffectTracer.make({ - span(name, parent, context, links, startTime, kind) { + span(name, parent, context, links, startTime, kind, options) { return new OtelSpan( OtelApi.context, + OtelApi.trace, tracer, name, parent, context, links.slice(), startTime, - kind + kind, + options ) }, context(execution, fiber) {