Skip to content

Commit 13ff1f0

Browse files
mikearnaldieffect-bot
authored andcommitted
Add logs to first propagated span (#5710)
1 parent 488d6e8 commit 13ff1f0

File tree

6 files changed

+121
-15
lines changed

6 files changed

+121
-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: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,27 @@ import { assert, describe, expect, it } from "@effect/vitest"
66
import * as OtelApi from "@opentelemetry/api"
77
import { AsyncHooksContextManager } from "@opentelemetry/context-async-hooks"
88
import { InMemorySpanExporter, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base"
9+
import * as Console from "effect/Console"
910
import * as Effect from "effect/Effect"
11+
import * as FiberRef from "effect/FiberRef"
1012
import * as Layer from "effect/Layer"
1113
import * as Runtime from "effect/Runtime"
1214
import { OtelSpan } from "../src/internal/tracer.js"
1315

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

2131
// needed to test context propagation
2232
const contextManager = new AsyncHooksContextManager()
@@ -160,4 +170,59 @@ describe("Tracer", () => {
160170
OtlpTracingLive
161171
))
162172
})
173+
174+
describe("Log Attributes", () => {
175+
it.effect("propagates attributes with Effect.fnUntraced", () =>
176+
Effect.gen(function*() {
177+
const f = Effect.fnUntraced(function*() {
178+
yield* Effect.logWarning("FooBar")
179+
return yield* Effect.fail("Oops")
180+
})
181+
182+
const p = f().pipe(Effect.withSpan("p"))
183+
184+
yield* Effect.ignore(p)
185+
186+
const { exporter } = yield* Exporter
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+
192+
it.effect("propagates attributes with Effect.fn(name)", () =>
193+
Effect.gen(function*() {
194+
const f = Effect.fn("f")(function*() {
195+
yield* Effect.logWarning("FooBar")
196+
return yield* Effect.fail("Oops")
197+
})
198+
199+
const p = f().pipe(Effect.withSpan("p"))
200+
201+
yield* Effect.ignore(p)
202+
203+
const { exporter } = yield* Exporter
204+
205+
assert.isNotEmpty(exporter.getFinishedSpans()[0].events.filter((_) => _.name === "FooBar"))
206+
assert.isNotEmpty(exporter.getFinishedSpans()[0].events.filter((_) => _.name === "exception"))
207+
}).pipe(Effect.provide(TracingLive)))
208+
209+
it.effect("propagates attributes with Effect.fn", () =>
210+
Effect.gen(function*() {
211+
const f = Effect.fn(function*() {
212+
yield* Effect.logWarning("FooBar")
213+
return yield* Effect.fail("Oops")
214+
})
215+
216+
const p = f().pipe(Effect.withSpan("p"))
217+
218+
yield* Effect.ignore(p)
219+
220+
const { exporter } = yield* Exporter
221+
222+
yield* Console.log(Array.from(yield* FiberRef.get(FiberRef.currentLoggers)))
223+
224+
assert.isNotEmpty(exporter.getFinishedSpans()[0].events.filter((_) => _.name === "FooBar"))
225+
assert.isNotEmpty(exporter.getFinishedSpans()[0].events.filter((_) => _.name === "exception"))
226+
}).pipe(Effect.provide(TracingLive)))
227+
})
163228
})

0 commit comments

Comments
 (0)