Skip to content

Commit 35e06d9

Browse files
mikearnaldieffect-bot
authored andcommitted
Add logs to first propagated span (#5710)
1 parent cc54fa2 commit 35e06d9

File tree

6 files changed

+122
-15
lines changed

6 files changed

+122
-15
lines changed

.changeset/fast-shoes-appear.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
"@effect/opentelemetry": patch
3+
"effect": patch
4+
---
5+
6+
Add logs to first propagated span, in the following case before this fix the log would not be added to the `p` span because `Effect.fn` adds a fake span for the purpose of adding a stack frame.
7+
8+
```ts
9+
import { Effect } from "effect"
10+
11+
const f = Effect.fn(function* () {
12+
yield* Effect.logWarning("FooBar")
13+
return yield* Effect.fail("Oops")
14+
})
15+
16+
const p = f().pipe(Effect.withSpan("p"))
17+
```

.changeset/violet-years-stare.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"effect": minor
3+
---
4+
5+
Fix annotateCurrentSpan, add Effect.currentPropagatedSpan

packages/effect/src/Effect.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12998,6 +12998,12 @@ export const annotateCurrentSpan: {
1299812998
*/
1299912999
export const currentSpan: Effect<Tracer.Span, Cause.NoSuchElementException> = effect.currentSpan
1300013000

13001+
/**
13002+
* @since 3.20.0
13003+
* @category Tracing
13004+
*/
13005+
export const currentPropagatedSpan: Effect<Tracer.Span, Cause.NoSuchElementException> = effect.currentPropagatedSpan
13006+
1300113007
/**
1300213008
* @since 2.0.0
1300313009
* @category Tracing

packages/effect/src/internal/core-effect.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1966,7 +1966,7 @@ export const annotateCurrentSpan: {
19661966
} = function(): Effect.Effect<void> {
19671967
const args = arguments
19681968
return ignore(core.flatMap(
1969-
currentSpan,
1969+
currentPropagatedSpan,
19701970
(span) =>
19711971
core.sync(() => {
19721972
if (typeof args[0] === "string") {
@@ -2041,6 +2041,16 @@ export const currentSpan: Effect.Effect<Tracer.Span, Cause.NoSuchElementExceptio
20412041
}
20422042
)
20432043

2044+
export const currentPropagatedSpan: Effect.Effect<Tracer.Span, Cause.NoSuchElementException> = core.flatMap(
2045+
core.context<never>(),
2046+
(context) => {
2047+
const span = filterDisablePropagation(Context.getOption(context, internalTracer.spanTag))
2048+
return span._tag === "Some" && span.value._tag === "Span"
2049+
? core.succeed(span.value)
2050+
: core.fail(new core.NoSuchElementException())
2051+
}
2052+
)
2053+
20442054
/* @internal */
20452055
export const linkSpans = dual<
20462056
(
@@ -2070,12 +2080,13 @@ export const linkSpans = dual<
20702080

20712081
const bigint0 = BigInt(0)
20722082

2073-
const filterDisablePropagation: (self: Option.Option<Tracer.AnySpan>) => Option.Option<Tracer.AnySpan> = Option.flatMap(
2074-
(span) =>
2075-
Context.get(span.context, internalTracer.DisablePropagation)
2076-
? span._tag === "Span" ? filterDisablePropagation(span.parent) : Option.none()
2077-
: Option.some(span)
2078-
)
2083+
export const filterDisablePropagation: (self: Option.Option<Tracer.AnySpan>) => Option.Option<Tracer.AnySpan> = Option
2084+
.flatMap(
2085+
(span) =>
2086+
Context.get(span.context, internalTracer.DisablePropagation)
2087+
? span._tag === "Span" ? filterDisablePropagation(span.parent) : Option.none()
2088+
: Option.some(span)
2089+
)
20792090

20802091
/** @internal */
20812092
export const unsafeMakeSpan = <XA, XE>(

packages/effect/src/internal/fiberRuntime.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1517,13 +1517,15 @@ export const tracerLogger = globalValue(
15171517
logLevel,
15181518
message
15191519
}) => {
1520-
const span = Context.getOption(
1520+
const span = internalEffect.filterDisablePropagation(Context.getOption(
15211521
fiberRefs.getOrDefault(context, core.currentContext),
15221522
tracer.spanTag
1523-
)
1523+
))
1524+
15241525
if (span._tag === "None" || span.value._tag === "ExternalSpan") {
15251526
return
15261527
}
1528+
15271529
const clockService = Context.unsafeGet(
15281530
fiberRefs.getOrDefault(context, defaultServices.currentServices),
15291531
clock.clockTag

packages/opentelemetry/test/Tracer.test.ts

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,27 @@ import { assert, describe, expect, it } from "@effect/vitest"
44
import * as OtelApi from "@opentelemetry/api"
55
import { AsyncHooksContextManager } from "@opentelemetry/context-async-hooks"
66
import { InMemorySpanExporter, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base"
7+
import * as Console from "effect/Console"
78
import * as Effect from "effect/Effect"
9+
import * as FiberRef from "effect/FiberRef"
10+
import * as Layer from "effect/Layer"
811
import * as Runtime from "effect/Runtime"
912
import { OtelSpan } from "../src/internal/tracer.js"
1013

11-
const TracingLive = NodeSdk.layer(Effect.sync(() => ({
12-
resource: {
13-
serviceName: "test"
14-
},
15-
spanProcessor: [new SimpleSpanProcessor(new InMemorySpanExporter())]
16-
})))
14+
class Exporter extends Effect.Service<Exporter>()("Exporter", {
15+
effect: Effect.sync(() => ({ exporter: new InMemorySpanExporter() }))
16+
}) {}
17+
18+
const TracingLive = Layer.unwrapEffect(Effect.gen(function*() {
19+
const { exporter } = yield* Exporter
20+
21+
return NodeSdk.layer(Effect.sync(() => ({
22+
resource: {
23+
serviceName: "test"
24+
},
25+
spanProcessor: [new SimpleSpanProcessor(exporter)]
26+
})))
27+
})).pipe(Layer.provideMerge(Exporter.Default))
1728

1829
// needed to test context propagation
1930
const contextManager = new AsyncHooksContextManager()
@@ -123,4 +134,59 @@ describe("Tracer", () => {
123134
})
124135
))
125136
})
137+
138+
describe("Log Attributes", () => {
139+
it.effect("propagates attributes with Effect.fnUntraced", () =>
140+
Effect.gen(function*() {
141+
const f = Effect.fnUntraced(function*() {
142+
yield* Effect.logWarning("FooBar")
143+
return yield* Effect.fail("Oops")
144+
})
145+
146+
const p = f().pipe(Effect.withSpan("p"))
147+
148+
yield* Effect.ignore(p)
149+
150+
const { exporter } = yield* Exporter
151+
152+
assert.isNotEmpty(exporter.getFinishedSpans()[0].events.filter((_) => _.name === "FooBar"))
153+
assert.isNotEmpty(exporter.getFinishedSpans()[0].events.filter((_) => _.name === "exception"))
154+
}).pipe(Effect.provide(TracingLive)))
155+
156+
it.effect("propagates attributes with Effect.fn(name)", () =>
157+
Effect.gen(function*() {
158+
const f = Effect.fn("f")(function*() {
159+
yield* Effect.logWarning("FooBar")
160+
return yield* Effect.fail("Oops")
161+
})
162+
163+
const p = f().pipe(Effect.withSpan("p"))
164+
165+
yield* Effect.ignore(p)
166+
167+
const { exporter } = yield* Exporter
168+
169+
assert.isNotEmpty(exporter.getFinishedSpans()[0].events.filter((_) => _.name === "FooBar"))
170+
assert.isNotEmpty(exporter.getFinishedSpans()[0].events.filter((_) => _.name === "exception"))
171+
}).pipe(Effect.provide(TracingLive)))
172+
173+
it.effect("propagates attributes with Effect.fn", () =>
174+
Effect.gen(function*() {
175+
const f = Effect.fn(function*() {
176+
yield* Effect.logWarning("FooBar")
177+
return yield* Effect.fail("Oops")
178+
})
179+
180+
const p = f().pipe(Effect.withSpan("p"))
181+
182+
yield* Effect.ignore(p)
183+
184+
const { exporter } = yield* Exporter
185+
186+
yield* Console.log(Array.from(yield* FiberRef.get(FiberRef.currentLoggers)))
187+
188+
assert.isNotEmpty(exporter.getFinishedSpans()[0].events.filter((_) => _.name === "FooBar"))
189+
assert.isNotEmpty(exporter.getFinishedSpans()[0].events.filter((_) => _.name === "exception"))
190+
}).pipe(Effect.provide(TracingLive)))
191+
})
126192
})

0 commit comments

Comments
 (0)