Repository navigation
Commit df2b796
feat(effect): Add opt-in for external span parents and isolate root spans (#23900)
`@sentry/effect` parented every Effect span on the active Sentry span. A
`Tracer.ExternalSpan` parent was dropped, so a persisted trace continued
with `Tracer.externalSpan` started a disconnected trace, and a `root:
true` span or a span leaked from another fiber through the async context
could become the parent of an unrelated span. On the server, every
parentless span also shared the process-wide propagation context, so a
long-lived process put all of its work into one trace.
- A parentless span only nests under a foreign active Sentry span (an
`http.server` span from the Node SDK, a pageload), never under a span
this tracer created.
- The server tracer starts a new trace for every parentless span, unless
the user set up the current scope (`continueTrace`, `withScope`, an
isolation scope). The client tracer keeps parentless spans in the page
trace.
- An external parent is ignored by default, and the span nests where
Effect would have put it without the `parent` option: under the fiber's
current span, or parentless.
- The new `SentryEffectExternalSpanLayer` opts into continuing external
parents: the span becomes a new root span with the external span as
`parent_span_id`. No dynamic sampling context is frozen, so the SDK
builds one from the client. Next to the tracer layer it applies to the
whole runtime, with `Effect.provide` to a single effect only there.
### Decisions
**External parents are opt-in.** A `Tracer.externalSpan` parent bridges
a trace the SDK did not start, for example an OpenTelemetry span of
another app in the same process, or trace state persisted with a queue
message. Joining such a trace silently would re-parent spans into a
trace the SDK cannot vouch for, so the user adds the layer to ask for
it. Incoming trace headers on the Effect HTTP server are out of scope
here and get their own PR.
**The opt-in is a layer, not a second tracer.** Two layers that set the
tracer race inside `Layer.mergeAll`, and a wrapping tracer only sees the
Sentry one when nested with `Layer.provide`. A flag in the fiber context
composes in `Layer.mergeAll` like `Layer.setTracer` does, and also works
per effect. It is a plain service in both Effect versions, because v4
has no `FiberRef` and v3 has no `Context.Reference`.
**How the tracer reads the flag.** Effect's `span` hook gets no fiber in
either version, only the span's own context or annotations. The
`context` hook does get the fiber and wraps every operation the fiber
evaluates, and `span` runs synchronously inside it, so the hook keeps
the fiber in a module variable for that extent, the same way it already
sets the active Sentry span there. The flag is read only when an
external parent shows up.
These fixes apply to both trace lifecycles. One related limitation stays
and is specific to `traceLifecycle: 'static'`: a child span that ends
after its root span is dropped with the transaction. Long-running Effect
fibers, such as a background agent that outlives the request that
started it, lose those children unless `traceLifecycle: 'stream'` is
used, which sends every span on its own end and is the default since
v11.
The effect-3-node and effect-4-node e2e apps cover a
`Tracer.externalSpan` parent continued through the layer and a `root:
true` span inside a request.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>1 parent ca0837d commit df2b796
10 files changed
Lines changed: 531 additions & 55 deletions
File tree
- dev-packages/e2e-tests/test-applications
- effect-3-node
- src
- tests
- effect-4-node
- src
- tests
- packages/effect
- src
- client
- server
- test
Lines changed: 26 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| 9 | + | |
9 | 10 | | |
10 | 11 | | |
11 | 12 | | |
| |||
42 | 43 | | |
43 | 44 | | |
44 | 45 | | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
45 | 71 | | |
46 | 72 | | |
47 | 73 | | |
| |||
Lines changed: 38 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
65 | 65 | | |
66 | 66 | | |
67 | 67 | | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
Lines changed: 27 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
101 | 101 | | |
102 | 102 | | |
103 | 103 | | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
104 | 131 | | |
105 | 132 | | |
106 | 133 | | |
| |||
Lines changed: 38 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
65 | 65 | | |
66 | 66 | | |
67 | 67 | | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
| 11 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| 10 | + | |
10 | 11 | | |
11 | 12 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
7 | 8 | | |
8 | 9 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
11 | 15 | | |
12 | 16 | | |
13 | | - | |
| 17 | + | |
0 commit comments