diff --git a/.changeset/hip-bags-smell.md b/.changeset/hip-bags-smell.md new file mode 100644 index 00000000000..0bf60d56b26 --- /dev/null +++ b/.changeset/hip-bags-smell.md @@ -0,0 +1,17 @@ +--- +"effect": patch +--- + +add fiber ref for disabling the tracer + +You can use it with the Effect.withTracerEnabled api: + +```ts +import { Effect } from "effect"; + +Effect.succeed(42).pipe( + Effect.withSpan("my-span"), + // the span will not be registered with the tracer + Effect.withTracerEnabled(false), +); +``` diff --git a/packages/effect/src/Effect.ts b/packages/effect/src/Effect.ts index 0e31178f5af..0bff64bf481 100644 --- a/packages/effect/src/Effect.ts +++ b/packages/effect/src/Effect.ts @@ -5061,6 +5061,25 @@ export const withTracer: { export const withTracerScoped: (value: Tracer.Tracer) => Effect = fiberRuntime.withTracerScoped +/** + * Disable the tracer for the given Effect. + * + * @since 2.0.0 + * @category tracing + * @example + * import { Effect } from "effect" + * + * Effect.succeed(42).pipe( + * Effect.withSpan("my-span"), + * // the span will not be registered with the tracer + * Effect.withTracerEnabled(false) + * ) + */ +export const withTracerEnabled: { + (enabled: boolean): (effect: Effect) => Effect + (effect: Effect, enabled: boolean): Effect +} = core.withTracerEnabled + /** * @since 2.0.0 * @category tracing diff --git a/packages/effect/src/FiberRef.ts b/packages/effect/src/FiberRef.ts index 7e1d076a9dc..463858adbcd 100644 --- a/packages/effect/src/FiberRef.ts +++ b/packages/effect/src/FiberRef.ts @@ -373,6 +373,12 @@ export const currentSupervisor: FiberRef> = fiberRunt */ export const currentMetricLabels: FiberRef> = core.currentMetricLabels +/** + * @since 2.0.0 + * @category fiberRefs + */ +export const currentTracerEnabled: FiberRef = core.currentTracerEnabled + /** * @since 2.0.0 * @category fiberRefs diff --git a/packages/effect/src/Layer.ts b/packages/effect/src/Layer.ts index defd9ea4e03..0fc23b71390 100644 --- a/packages/effect/src/Layer.ts +++ b/packages/effect/src/Layer.ts @@ -979,6 +979,15 @@ export const span: ( */ export const setTracer: (tracer: Tracer.Tracer) => Layer = circularLayer.setTracer +/** + * @since 2.0.0 + * @category tracing + */ +export const setTracerEnabled: (enabled: boolean) => Layer = (enabled: boolean) => + scopedDiscard( + fiberRuntime.fiberRefLocallyScoped(core.currentTracerEnabled, enabled) + ) + /** * @since 2.0.0 * @category tracing diff --git a/packages/effect/src/internal/core-effect.ts b/packages/effect/src/internal/core-effect.ts index 0e7d505e8a1..1686f9f306d 100644 --- a/packages/effect/src/internal/core-effect.ts +++ b/packages/effect/src/internal/core-effect.ts @@ -2051,6 +2051,11 @@ export const makeSpan = ( ): Effect.Effect => core.flatMap(fiberRefs, (fiberRefs) => core.sync(() => { + const enabled = FiberRefs.getOrDefault(fiberRefs, core.currentTracerEnabled) + if (enabled === false) { + return core.noopSpan + } + const context = FiberRefs.getOrDefault(fiberRefs, core.currentContext) const services = FiberRefs.getOrDefault(fiberRefs, defaultServices.currentServices) @@ -2131,10 +2136,12 @@ export const useSpan: { makeSpan(name, options), evaluate, (span, exit) => - core.flatMap( - currentTimeNanosTracing, - (endTime) => core.sync(() => span.end(endTime, exit)) - ) + span.status._tag === "Ended" ? + core.unit : + core.flatMap( + currentTimeNanosTracing, + (endTime) => core.sync(() => span.end(endTime, exit)) + ) ) } diff --git a/packages/effect/src/internal/core.ts b/packages/effect/src/internal/core.ts index fffd2f6119c..2f26473ca24 100644 --- a/packages/effect/src/internal/core.ts +++ b/packages/effect/src/internal/core.ts @@ -1372,6 +1372,17 @@ export const withRuntimeFlags = dual< return effect }) +/** @internal */ +export const withTracerEnabled = dual< + (enabled: boolean) => (effect: Effect.Effect) => Effect.Effect, + (effect: Effect.Effect, enabled: boolean) => Effect.Effect +>(2, (effect, enabled) => + fiberRefLocally( + effect, + currentTracerEnabled, + enabled + )) + /** @internal */ export const withTracerTiming = dual< (enabled: boolean) => (effect: Effect.Effect) => Effect.Effect, @@ -2015,6 +2026,12 @@ export const currentInterruptedCause: FiberRef.FiberRef> = gl }) ) +/** @internal */ +export const currentTracerEnabled: FiberRef.FiberRef = globalValue( + Symbol.for("effect/FiberRef/currentTracerEnabled"), + () => fiberRefUnsafeMake(true) +) + /** @internal */ export const currentTracerTimingEnabled: FiberRef.FiberRef = globalValue( Symbol.for("effect/FiberRef/currentTracerTiming"), @@ -3073,3 +3090,25 @@ export const currentSpanFromFiber = (fiber: Fiber.RuntimeFiber): Opt | undefined return span !== undefined && span._tag === "Span" ? Option.some(span) : Option.none() } + +/** @internal */ +export const noopSpan: Tracer.Span = globalValue("effect/Tracer/noopSpan", () => ({ + _tag: "Span", + spanId: "noop", + traceId: "noop", + name: "noop", + sampled: false, + parent: Option.none(), + context: Context.empty(), + status: { + _tag: "Ended", + startTime: BigInt(0), + endTime: BigInt(0), + exit: exitUnit + }, + attributes: new Map(), + links: [], + attribute() {}, + event() {}, + end() {} +})) diff --git a/packages/effect/src/internal/fiberRuntime.ts b/packages/effect/src/internal/fiberRuntime.ts index 5d512f527bc..d496f23c8eb 100644 --- a/packages/effect/src/internal/fiberRuntime.ts +++ b/packages/effect/src/internal/fiberRuntime.ts @@ -3505,10 +3505,12 @@ export const makeSpanScoped = ( acquireRelease( internalEffect.makeSpan(name, options), (span, exit) => - core.flatMap( - internalEffect.currentTimeNanosTracing, - (endTime) => core.sync(() => span.end(endTime, exit)) - ) + span.status._tag === "Ended" ? + core.unit : + core.flatMap( + internalEffect.currentTimeNanosTracing, + (endTime) => core.sync(() => span.end(endTime, exit)) + ) ) /* @internal */ diff --git a/packages/effect/src/internal/tracer.ts b/packages/effect/src/internal/tracer.ts index 15a8f0260e4..d4abfafa5b5 100644 --- a/packages/effect/src/internal/tracer.ts +++ b/packages/effect/src/internal/tracer.ts @@ -59,7 +59,7 @@ export class NativeSpan implements Tracer.Span { this.spanId = `span${randomString(16)}` } - end = (endTime: bigint, exit: Exit.Exit): void => { + end(endTime: bigint, exit: Exit.Exit): void { this.status = { _tag: "Ended", endTime, @@ -68,11 +68,11 @@ export class NativeSpan implements Tracer.Span { } } - attribute = (key: string, value: unknown): void => { + attribute(key: string, value: unknown): void { this.attributes.set(key, value) } - event = (name: string, startTime: bigint, attributes?: Record): void => { + event(name: string, startTime: bigint, attributes?: Record): void { this.events.push([name, startTime, attributes ?? {}]) } } diff --git a/packages/effect/test/Tracer.test.ts b/packages/effect/test/Tracer.test.ts index 01a5b092bf5..e6ab5eaf9be 100644 --- a/packages/effect/test/Tracer.test.ts +++ b/packages/effect/test/Tracer.test.ts @@ -258,4 +258,21 @@ describe("Tracer", () => { assert.strictEqual(onEndCalled, true) })) }) + + it.effect("withTracerEnabled", () => + Effect.gen(function*($) { + const span = yield* $( + Effect.currentSpan, + Effect.withSpan("A"), + Effect.withTracerEnabled(false) + ) + const spanB = yield* $( + Effect.currentSpan, + Effect.withSpan("B"), + Effect.withTracerEnabled(true) + ) + + assert.deepEqual(span.name, "noop") + assert.deepEqual(spanB.name, "B") + })) })