From 92623c5b819192270771fec545c3fd152f51581b Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 7 Oct 2024 12:01:46 +1300 Subject: [PATCH 1/2] add Tracer.withSpanContext --- .changeset/fluffy-pandas-think.md | 32 ++++++++++++++++ .changeset/nasty-lizards-bathe.md | 5 +++ packages/opentelemetry/src/Tracer.ts | 17 +++++++-- packages/opentelemetry/src/internal/tracer.ts | 24 +++++++----- packages/opentelemetry/test/Tracer.test.ts | 37 ++++++++++--------- 5 files changed, 85 insertions(+), 30 deletions(-) create mode 100644 .changeset/fluffy-pandas-think.md create mode 100644 .changeset/nasty-lizards-bathe.md diff --git a/.changeset/fluffy-pandas-think.md b/.changeset/fluffy-pandas-think.md new file mode 100644 index 0000000000..50e9234826 --- /dev/null +++ b/.changeset/fluffy-pandas-think.md @@ -0,0 +1,32 @@ +--- +"@effect/opentelemetry": patch +--- + +add Tracer.withSpanContext + +This api is useful for attaching a parent span to an Effect from an opentelemetry +span outside of Effect. + +```typescript +import { Effect } from "effect" +import { Tracer } from "@effect/opentelemetry" +import * as OtelApi from "@opentelemetry/api" + +await OtelApi.trace.getTracer("test").startActiveSpan( + "otel-span", + { + root: true + }, + async (span) => { + try { + await Effect.runPromise( + Effect.log("inside otel parent span").pipe( + Tracer.withSpanContext(span.spanContext()) + ) + ) + } finally { + span.end() + } + } +) +``` diff --git a/.changeset/nasty-lizards-bathe.md b/.changeset/nasty-lizards-bathe.md new file mode 100644 index 0000000000..c291d16ae7 --- /dev/null +++ b/.changeset/nasty-lizards-bathe.md @@ -0,0 +1,5 @@ +--- +"@effect/opentelemetry": minor +--- + +remove Tracer.withActiveSpan diff --git a/packages/opentelemetry/src/Tracer.ts b/packages/opentelemetry/src/Tracer.ts index 3f57032e75..d0f055fc41 100644 --- a/packages/opentelemetry/src/Tracer.ts +++ b/packages/opentelemetry/src/Tracer.ts @@ -6,7 +6,7 @@ import type { NoSuchElementException } from "effect/Cause" import type { Tag } from "effect/Context" import type { Effect } from "effect/Effect" import type { Layer } from "effect/Layer" -import type { ExternalSpan, Tracer as EffectTracer } from "effect/Tracer" +import type { ExternalSpan, ParentSpan, Tracer as EffectTracer } from "effect/Tracer" import * as internal from "./internal/tracer.js" import type { Resource } from "./Resource.js" @@ -84,8 +84,7 @@ export const TraceFlags: Tag = internal.traceF export const TraceState: Tag = internal.traceStateTag /** - * Attach the provided Effect to the current Span as reported from OpenTelemetry's - * context propagation. + * Set the effect's parent span from the given opentelemetry `SpanContext`. * * This is handy when you set up OpenTelemetry outside of Effect and want to * attach to a parent span. @@ -93,4 +92,14 @@ export const TraceState: Tag = internal.traceS * @since 1.0.0 * @category propagation */ -export const withActiveSpan: (effect: Effect) => Effect = internal.withActiveSpan +export const withSpanContext: { + ( + spanContext: Otel.SpanContext + ): ( + effect: Effect + ) => Effect> + ( + effect: Effect, + spanContext: Otel.SpanContext + ): Effect> +} = internal.withSpanContext diff --git a/packages/opentelemetry/src/internal/tracer.ts b/packages/opentelemetry/src/internal/tracer.ts index 85cfcf5fde..41ff457649 100644 --- a/packages/opentelemetry/src/internal/tracer.ts +++ b/packages/opentelemetry/src/internal/tracer.ts @@ -3,6 +3,7 @@ import * as Cause from "effect/Cause" import * as Context from "effect/Context" import * as Effect from "effect/Effect" import type { Exit } from "effect/Exit" +import { dual } from "effect/Function" import * as Inspectable from "effect/Inspectable" import * as Layer from "effect/Layer" import * as Option from "effect/Option" @@ -305,12 +306,17 @@ const unknownToAttributeValue = (value: unknown): OtelApi.AttributeValue => { return Inspectable.toStringUnknown(value) } -export const withActiveSpan = (effect: Effect.Effect): Effect.Effect => - Effect.suspend(() => { - const activeSpan = OtelApi.trace.getActiveSpan() - if (!activeSpan) { - return effect - } - const span = makeExternalSpan(activeSpan.spanContext()) - return Effect.withParentSpan(effect, span) - }) +/** @internal */ +export const withSpanContext = dual< + ( + spanContext: OtelApi.SpanContext + ) => (effect: Effect.Effect) => Effect.Effect>, + ( + effect: Effect.Effect, + spanContext: OtelApi.SpanContext + ) => Effect.Effect> +>(2, ( + effect: Effect.Effect, + spanContext: OtelApi.SpanContext +): Effect.Effect> => + Effect.provideService(effect, EffectTracer.ParentSpan, makeExternalSpan(spanContext))) diff --git a/packages/opentelemetry/test/Tracer.test.ts b/packages/opentelemetry/test/Tracer.test.ts index e0e8ff65ce..26bf134caf 100644 --- a/packages/opentelemetry/test/Tracer.test.ts +++ b/packages/opentelemetry/test/Tracer.test.ts @@ -1,5 +1,6 @@ -import { currentOtelSpan, OtelSpan, withActiveSpan } from "@effect/opentelemetry/internal/tracer" +import { OtelSpan } from "@effect/opentelemetry/internal/tracer" import * as NodeSdk from "@effect/opentelemetry/NodeSdk" +import * as Tracer from "@effect/opentelemetry/Tracer" import * as it from "@effect/vitest" import * as OtelApi from "@opentelemetry/api" import { AsyncHooksContextManager } from "@opentelemetry/context-async-hooks" @@ -24,8 +25,8 @@ describe("Tracer", () => { it.effect("withSpan", () => Effect.provide( Effect.withSpan("ok")( - Effect.gen(function*(_) { - const span = yield* _(Effect.currentSpan) + Effect.gen(function*() { + const span = yield* Effect.currentSpan expect(span).instanceOf(OtelSpan) }) ), @@ -33,10 +34,9 @@ describe("Tracer", () => { )) it.effect("withSpan links", () => - Effect.gen(function*(_) { - const linkedSpan = yield* _(Effect.makeSpanScoped("B")) - const span = yield* _( - Effect.currentSpan, + Effect.gen(function*() { + const linkedSpan = yield* Effect.makeSpanScoped("B") + const span = yield* Effect.currentSpan.pipe( Effect.withSpan("A"), Effect.linkSpans(linkedSpan) ) @@ -58,8 +58,8 @@ describe("Tracer", () => { )) it.effect("supervisor sets context generator", () => - Effect.gen(function*(_) { - yield* _(Effect.yieldNow()) + Effect.gen(function*() { + yield* Effect.yieldNow() expect(OtelApi.trace.getSpan(OtelApi.context.active())).toBeDefined() }).pipe( Effect.withSpan("ok"), @@ -69,23 +69,23 @@ describe("Tracer", () => { it.effect("currentOtelSpan", () => Effect.provide( Effect.withSpan("ok")( - Effect.gen(function*(_) { - const span = yield* _(Effect.currentSpan) - const otelSpan = yield* _(currentOtelSpan) + Effect.gen(function*() { + const span = yield* Effect.currentSpan + const otelSpan = yield* Tracer.currentOtelSpan expect((span as OtelSpan).span).toBe(otelSpan) }) ), TracingLive )) - it.scoped("withActiveSpan", () => + it.scoped("withSpanContext", () => Effect.gen(function*() { const effect = Effect.gen(function*() { const span = yield* Effect.currentParentSpan assert(span._tag === "Span") const parent = yield* span.parent return parent - }).pipe(Effect.withSpan("child"), withActiveSpan) + }).pipe(Effect.withSpan("child")) const runtime = yield* Effect.runtime() @@ -95,7 +95,10 @@ describe("Tracer", () => { attributes: { "root": "yes" } }, async (span) => { try { - const parent = await Runtime.runPromise(runtime)(effect) + const parent = await Runtime.runPromise(runtime)(Tracer.withSpanContext( + effect, + span.spanContext() + )) const { spanId, traceId } = span.spanContext() expect(parent).toMatchObject({ spanId, @@ -112,8 +115,8 @@ describe("Tracer", () => { describe("not provided", () => { it.effect("withSpan", () => Effect.withSpan("ok")( - Effect.gen(function*(_) { - const span = yield* _(Effect.currentSpan) + Effect.gen(function*() { + const span = yield* Effect.currentSpan expect(span).not.instanceOf(OtelSpan) }) )) From 043e83c99aae4e98f8f4ca196226674a8afca042 Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 7 Oct 2024 12:03:15 +1300 Subject: [PATCH 2/2] use withParentSpan --- packages/opentelemetry/src/internal/tracer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opentelemetry/src/internal/tracer.ts b/packages/opentelemetry/src/internal/tracer.ts index 41ff457649..b9f77b931a 100644 --- a/packages/opentelemetry/src/internal/tracer.ts +++ b/packages/opentelemetry/src/internal/tracer.ts @@ -319,4 +319,4 @@ export const withSpanContext = dual< effect: Effect.Effect, spanContext: OtelApi.SpanContext ): Effect.Effect> => - Effect.provideService(effect, EffectTracer.ParentSpan, makeExternalSpan(spanContext))) + Effect.withParentSpan(effect, makeExternalSpan(spanContext)))