Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/fix-span-addevent-overload.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@effect/opentelemetry": patch
---

Fix `Span.addEvent` to correctly handle the 2-argument overload with attributes.

Previously, calling `span.addEvent("name", { foo: "bar" })` would throw `TypeError: {} is not iterable` because the implementation incorrectly treated the attributes object as a `TimeInput`. The fix adds proper runtime type discrimination to distinguish between `TimeInput` (number, Date, or HrTime tuple) and `Attributes` (plain object).
15 changes: 13 additions & 2 deletions packages/opentelemetry/src/internal/tracer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,13 @@ const makeOtelSpan = (span: EffectTracer.Span, clock: Clock.Clock): OtelApi.Span
if (arguments.length === 3) {
attributes = arguments[1]
startTime = arguments[2]
} else {
startTime = arguments[1]
} else if (arguments.length === 2) {
const arg1 = arguments[1]
if (isTimeInput(arg1)) {
startTime = arg1
} else {
attributes = arg1
}
}
span.event(name, convertOtelTimeInput(startTime, clock), attributes)
return self
Expand Down Expand Up @@ -318,6 +323,12 @@ const makeOtelSpan = (span: EffectTracer.Span, clock: Clock.Clock): OtelApi.Span
const bigint1e6 = BigInt(1_000_000)
const bigint1e9 = BigInt(1_000_000_000)

/** Distinguishes TimeInput (number | Date | [number, number]) from Attributes (plain object) */
const isTimeInput = (u: unknown): u is OtelApi.TimeInput =>
typeof u === "number" ||
u instanceof Date ||
(Array.isArray(u) && u.length === 2 && typeof u[0] === "number" && typeof u[1] === "number")

const convertOtelTimeInput = (input: OtelApi.TimeInput | undefined, clock: Clock.Clock): bigint => {
if (input === undefined) {
return clock.unsafeCurrentTimeNanos()
Expand Down
66 changes: 66 additions & 0 deletions packages/opentelemetry/test/Tracer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,5 +159,71 @@ describe("Tracer", () => {
),
OtlpTracingLive
))

it.effect("addEvent with attributes (2-arg overload) does not throw", () =>
Effect.provide(
Effect.withSpan("root")(
Effect.gen(function*() {
const otelSpan = yield* Tracer.currentOtelSpan
expect(() => otelSpan.addEvent("test-event", { foo: "bar", count: 42 })).not.toThrow()
})
),
OtlpTracingLive
))

it.effect("addEvent with HrTime tuple (2-arg overload) does not throw", () =>
Effect.provide(
Effect.withSpan("root")(
Effect.gen(function*() {
const otelSpan = yield* Tracer.currentOtelSpan
expect(() => otelSpan.addEvent("test-event", [1, 2])).not.toThrow()
})
),
OtlpTracingLive
))

it.effect("addEvent with number timestamp (2-arg overload) does not throw", () =>
Effect.provide(
Effect.withSpan("root")(
Effect.gen(function*() {
const otelSpan = yield* Tracer.currentOtelSpan
expect(() => otelSpan.addEvent("test-event", Date.now())).not.toThrow()
})
),
OtlpTracingLive
))

it.effect("addEvent with Date timestamp (2-arg overload) does not throw", () =>
Effect.provide(
Effect.withSpan("root")(
Effect.gen(function*() {
const otelSpan = yield* Tracer.currentOtelSpan
expect(() => otelSpan.addEvent("test-event", new Date())).not.toThrow()
})
),
OtlpTracingLive
))

it.effect("addEvent with name only (1-arg) does not throw", () =>
Effect.provide(
Effect.withSpan("root")(
Effect.gen(function*() {
const otelSpan = yield* Tracer.currentOtelSpan
expect(() => otelSpan.addEvent("test-event")).not.toThrow()
})
),
OtlpTracingLive
))

it.effect("addEvent with attributes and timestamp (3-arg overload) does not throw", () =>
Effect.provide(
Effect.withSpan("root")(
Effect.gen(function*() {
const otelSpan = yield* Tracer.currentOtelSpan
expect(() => otelSpan.addEvent("test-event", { foo: "bar" }, Date.now())).not.toThrow()
})
),
OtlpTracingLive
))
})
})