Skip to content

Commit

Permalink
add span annotation to disable propagation to the tracer (#4123)
Browse files Browse the repository at this point in the history
  • Loading branch information
tim-smart authored and gcanti committed Dec 13, 2024
1 parent 3fc16d8 commit cfe8926
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 51 deletions.
5 changes: 5 additions & 0 deletions .changeset/shiny-vans-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": minor
---

add span annotation to disable propagation to the tracer
14 changes: 14 additions & 0 deletions packages/effect/src/Tracer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,17 @@ export const externalSpan: (
*/
export const tracerWith: <A, E, R>(f: (tracer: Tracer) => Effect.Effect<A, E, R>) => Effect.Effect<A, E, R> =
defaultServices.tracerWith

/**
* @since 3.12.0
* @category annotations
*/
export interface DisablePropagation {
readonly _: unique symbol
}

/**
* @since 3.12.0
* @category annotations
*/
export const DisablePropagation: Context.Reference<DisablePropagation, boolean> = internal.DisablePropagation
96 changes: 55 additions & 41 deletions packages/effect/src/internal/core-effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2017,61 +2017,75 @@ export const linkSpans = dual<

const bigint0 = BigInt(0)

const filterDisablePropagation: (self: Option.Option<Tracer.AnySpan>) => Option.Option<Tracer.AnySpan> = Option.flatMap(
(span) =>
Context.get(span.context, internalTracer.DisablePropagation)
? span._tag === "Span" ? filterDisablePropagation(span.parent) : Option.none()
: Option.some(span)
)

/** @internal */
export const unsafeMakeSpan = <XA, XE>(
fiber: FiberRuntime<XA, XE>,
name: string,
options: Tracer.SpanOptions
) => {
const enabled = fiber.getFiberRef(core.currentTracerEnabled)
if (enabled === false) {
return core.noopSpan(name)
}

const disablePropagation = !fiber.getFiberRef(core.currentTracerEnabled) ||
(options.context && Context.get(options.context, internalTracer.DisablePropagation))
const context = fiber.getFiberRef(core.currentContext)
const services = fiber.getFiberRef(defaultServices.currentServices)

const tracer = Context.get(services, internalTracer.tracerTag)
const clock = Context.get(services, Clock.Clock)
const timingEnabled = fiber.getFiberRef(core.currentTracerTimingEnabled)

const fiberRefs = fiber.getFiberRefs()
const annotationsFromEnv = FiberRefs.get(fiberRefs, core.currentTracerSpanAnnotations)
const linksFromEnv = FiberRefs.get(fiberRefs, core.currentTracerSpanLinks)

const parent = options.parent
? Option.some(options.parent)
: options.root
? Option.none()
: Context.getOption(context, internalTracer.spanTag)

const links = linksFromEnv._tag === "Some" ?
options.links !== undefined ?
[
...Chunk.toReadonlyArray(linksFromEnv.value),
...(options.links ?? [])
] :
Chunk.toReadonlyArray(linksFromEnv.value) :
options.links ?? Arr.empty()

const span = tracer.span(
name,
parent,
options.context ?? Context.empty(),
links,
timingEnabled ? clock.unsafeCurrentTimeNanos() : bigint0,
options.kind ?? "internal"
)
: filterDisablePropagation(Context.getOption(context, internalTracer.spanTag))

if (typeof options.captureStackTrace === "function") {
internalCause.spanToTrace.set(span, options.captureStackTrace)
}
let span: Tracer.Span

if (annotationsFromEnv._tag === "Some") {
HashMap.forEach(annotationsFromEnv.value, (value, key) => span.attribute(key, value))
if (disablePropagation) {
span = core.noopSpan({
name,
parent,
context: Context.add(options.context ?? Context.empty(), internalTracer.DisablePropagation, true)
})
} else {
const services = fiber.getFiberRef(defaultServices.currentServices)

const tracer = Context.get(services, internalTracer.tracerTag)
const clock = Context.get(services, Clock.Clock)
const timingEnabled = fiber.getFiberRef(core.currentTracerTimingEnabled)

const fiberRefs = fiber.getFiberRefs()
const annotationsFromEnv = FiberRefs.get(fiberRefs, core.currentTracerSpanAnnotations)
const linksFromEnv = FiberRefs.get(fiberRefs, core.currentTracerSpanLinks)

const links = linksFromEnv._tag === "Some" ?
options.links !== undefined ?
[
...Chunk.toReadonlyArray(linksFromEnv.value),
...(options.links ?? [])
] :
Chunk.toReadonlyArray(linksFromEnv.value) :
options.links ?? Arr.empty()

span = tracer.span(
name,
parent,
options.context ?? Context.empty(),
links,
timingEnabled ? clock.unsafeCurrentTimeNanos() : bigint0,
options.kind ?? "internal"
)

if (annotationsFromEnv._tag === "Some") {
HashMap.forEach(annotationsFromEnv.value, (value, key) => span.attribute(key, value))
}
if (options.attributes !== undefined) {
Object.entries(options.attributes).forEach(([k, v]) => span.attribute(k, v))
}
}
if (options.attributes !== undefined) {
Object.entries(options.attributes).forEach(([k, v]) => span.attribute(k, v))

if (typeof options.captureStackTrace === "function") {
internalCause.spanToTrace.set(span, options.captureStackTrace)
}

return span
Expand Down
15 changes: 6 additions & 9 deletions packages/effect/src/internal/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3063,14 +3063,11 @@ export const currentSpanFromFiber = <A, E>(fiber: Fiber.RuntimeFiber<A, E>): Opt
return span !== undefined && span._tag === "Span" ? Option.some(span) : Option.none()
}

const NoopSpanProto: Tracer.Span = {
const NoopSpanProto: Omit<Tracer.Span, "parent" | "name" | "context"> = {
_tag: "Span",
spanId: "noop",
traceId: "noop",
name: "noop",
sampled: false,
parent: Option.none(),
context: Context.empty(),
status: {
_tag: "Ended",
startTime: BigInt(0),
Expand All @@ -3086,8 +3083,8 @@ const NoopSpanProto: Tracer.Span = {
}

/** @internal */
export const noopSpan = (name: string): Tracer.Span => {
const span = Object.create(NoopSpanProto)
span.name = name
return span
}
export const noopSpan = (options: {
readonly name: string
readonly parent: Option.Option<Tracer.AnySpan>
readonly context: Context.Context<never>
}): Tracer.Span => Object.assign(Object.create(NoopSpanProto), options)
6 changes: 6 additions & 0 deletions packages/effect/src/internal/tracer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import * as Context from "../Context.js"
import type * as Exit from "../Exit.js"
import { constFalse } from "../Function.js"
import type * as Option from "../Option.js"
import type * as Tracer from "../Tracer.js"

Expand Down Expand Up @@ -135,3 +136,8 @@ export const addSpanStackTrace = (options: Tracer.SpanOptions | undefined): Trac
}
}
}

/** @internal */
export const DisablePropagation = Context.Reference<Tracer.DisablePropagation>()("effect/Tracer/DisablePropagation", {
defaultValue: constFalse
})
41 changes: 40 additions & 1 deletion packages/effect/test/Tracer.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Cause, Tracer } from "effect"
import * as Context from "effect/Context"
import { millis, seconds } from "effect/Duration"
import * as Effect from "effect/Effect"
Expand Down Expand Up @@ -274,6 +275,44 @@ it.effect("withTracerEnabled", () =>
assert.deepEqual(spanB.name, "B")
}))

describe("Tracer.DisablePropagation", () => {
it.effect("creates noop span", () =>
Effect.gen(function*() {
const span = yield* Effect.currentSpan.pipe(
Effect.withSpan("A", { context: Tracer.DisablePropagation.context(true) })
)
const spanB = yield* Effect.currentSpan.pipe(
Effect.withSpan("B")
)

assert.deepEqual(span.name, "A")
assert.deepEqual(span.spanId, "noop")
assert.deepEqual(spanB.name, "B")
}))

it.effect("captures stack", () =>
Effect.gen(function*() {
const cause = yield* Effect.die(new Error("boom")).pipe(
Effect.withSpan("C", { context: Tracer.DisablePropagation.context(true) }),
Effect.sandbox,
Effect.flip
)
assert.include(Cause.pretty(cause), "Tracer.test.ts:295")
}))

it.effect("isnt used as parent span", () =>
Effect.gen(function*() {
const span = yield* Effect.currentSpan.pipe(
Effect.withSpan("child"),
Effect.withSpan("disabled", { context: Tracer.DisablePropagation.context(true) }),
Effect.withSpan("parent")
)
assert.strictEqual(span.name, "child")
assert(span.parent._tag === "Some" && span.parent.value._tag === "Span")
assert.strictEqual(span.parent.value.name, "parent")
}))
})

it.effect("includes trace when errored", () =>
Effect.gen(function*() {
let maybeSpan: undefined | Span
Expand All @@ -290,7 +329,7 @@ it.effect("includes trace when errored", () =>
})
yield* Effect.flip(getSpan("fail"))
assert.isDefined(maybeSpan)
assert.include(maybeSpan!.attributes.get("code.stacktrace"), "Tracer.test.ts:291:24")
assert.include(maybeSpan!.attributes.get("code.stacktrace"), "Tracer.test.ts:330:24")
}))

describe("functionWithSpan", () => {
Expand Down

0 comments on commit cfe8926

Please sign in to comment.