Skip to content

feat(effect): Add @sentry/effect SDK (alpha)#19644

Open
JPeer264 wants to merge 8 commits intodevelopfrom
jp/add-effect-sdk
Open

feat(effect): Add @sentry/effect SDK (alpha)#19644
JPeer264 wants to merge 8 commits intodevelopfrom
jp/add-effect-sdk

Conversation

@JPeer264
Copy link
Member

@JPeer264 JPeer264 commented Mar 5, 2026

closes #19641
closes JS-1864

This PR collects all Effect SDK integration PRs.

The Effect SDK provides first-class integration with Effect, enabling automatic tracing, logging, and metrics capture for Effect-based applications.

Features

  • Tracer: OpenTelemetry-compatible tracer that creates Sentry spans from Effect spans
  • Logger: Routes Effect logs to Sentry's logging system
  • Metrics: Captures Effect metrics (counters, gauges, histograms) as Sentry metrics
  • Layer: Composable Effect Layer for easy SDK integration
  • Client/Server entry points: Tree-shakeable entry points for browser and Node.js

Checklist of features

All items need to be checked before it goes to develop

  • Add base scaffolding for the Effect SDK
  • Support traces
  • Support logs
  • Support metrics
  • Add unit tests
  • Add Node E2E tests
  • Add browser E2E tests

Merge checklist

  • All items from the Checklist of features are checked
  • Create issue for the docs
  • Rebase merge to keep the history

@linear-code
Copy link

linear-code bot commented Mar 5, 2026

@JPeer264 JPeer264 self-assigned this Mar 5, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2026

size-limit report 📦

Path Size % Change Change
@sentry/browser 25.64 kB - -
@sentry/browser - with treeshaking flags 24.14 kB - -
@sentry/browser (incl. Tracing) 42.62 kB - -
@sentry/browser (incl. Tracing, Profiling) 47.28 kB - -
@sentry/browser (incl. Tracing, Replay) 81.42 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 71 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 86.12 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 98.37 kB - -
@sentry/browser (incl. Feedback) 42.45 kB - -
@sentry/browser (incl. sendFeedback) 30.31 kB - -
@sentry/browser (incl. FeedbackAsync) 35.36 kB - -
@sentry/browser (incl. Metrics) 26.92 kB - -
@sentry/browser (incl. Logs) 27.07 kB - -
@sentry/browser (incl. Metrics & Logs) 27.74 kB - -
@sentry/react 27.39 kB - -
@sentry/react (incl. Tracing) 44.95 kB - -
@sentry/vue 30.08 kB - -
@sentry/vue (incl. Tracing) 44.48 kB - -
@sentry/svelte 25.66 kB - -
CDN Bundle 28.27 kB - -
CDN Bundle (incl. Tracing) 43.5 kB - -
CDN Bundle (incl. Logs, Metrics) 29.13 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 44.34 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 68.2 kB - -
CDN Bundle (incl. Tracing, Replay) 80.32 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 81.22 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 85.86 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 86.76 kB - -
CDN Bundle - uncompressed 82.56 kB - -
CDN Bundle (incl. Tracing) - uncompressed 128.5 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 85.43 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 131.37 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 209.06 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 245.35 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 248.21 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 258.26 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 261.11 kB - -
@sentry/nextjs (client) 47.37 kB - -
@sentry/sveltekit (client) 43.07 kB - -
@sentry/node-core 56.34 kB +0.02% +10 B 🔺
@sentry/node 173.19 kB +0.01% +5 B 🔺
@sentry/node - without tracing 96.35 kB +0.01% +5 B 🔺
@sentry/aws-serverless 113.34 kB +0.01% +9 B 🔺

View base workflow run

@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2026

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.

Scenario Requests/s % of Baseline Prev. Requests/s Change %
GET Baseline 9,152 - 9,535 -4%
GET With Sentry 1,727 19% 1,753 -1%
GET With Sentry (error only) 6,145 67% 6,118 +0%
POST Baseline 1,204 - 1,200 +0%
POST With Sentry 588 49% 591 -1%
POST With Sentry (error only) 1,044 87% 1,065 -2%
MYSQL Baseline 3,179 - 3,261 -3%
MYSQL With Sentry 485 15% 478 +1%
MYSQL With Sentry (error only) 2,597 82% 2,660 -2%

View base workflow run

@JPeer264 JPeer264 changed the title feat(effect): Add base skaffolding for Effect.ts (#19622) feat(effect): Add @sentry/effect SDK Mar 5, 2026
@JPeer264 JPeer264 changed the title feat(effect): Add @sentry/effect SDK feat(effect): Add @sentry/effect SDK (alpha) Mar 5, 2026
@JPeer264 JPeer264 force-pushed the jp/add-effect-sdk branch 2 times, most recently from 2db4b85 to 3070535 Compare March 11, 2026 15:47
JPeer264 and others added 7 commits March 13, 2026 08:01
This is one of many PRs to create the effect SDK. Once this has been
merged I will open the draft PR for the effect sdk and create the plan
in there.

(the almost final SDK can be viewed here:
https://github.com/getsentry/sentry-javascript/tree/jp/effect-sdk. It
might be that some specifics change, especially when having browser +
server split, and with tracing)

---

This PR focuses on the base skaffolding of `@sentry/effect`. This on its
own is not really doing anything except setting up the skaffold. The
README already reflects the actual usage, while the export doesn't exist
yet, this will come in another PR (also `init` is exposed here, just for
the sake of completeness)

---------

Co-authored-by: Claude <[email protected]>
…9649)

That adds now the functionality to use the `Sentry.effectLayer`
properly. **But** it doesn't do anything, which means right now, to keep
the PRs small, it returns an empty layer.

Following can be used without any Sentry functionality:

```js
const MainLive = HttpLive.pipe(Layer.provide(Sentry.effectLayer({
  dsn: "",
  tracesSampleRate: 1.0,
  debug: true,
})))

MainLive.pipe(Layer.launch, NodeRuntime.runMain)
```
This adds tracing to the `Sentry.effectLayer`. By setting
`tracesSampleRate: 1.0` in the options tracing is enabled and spans can
be send to Sentry
This adds the functionality to send logs to Sentry by setting
`enableLogs: true` in the `Sentry.effectLayer`
This adds metrics to the `Sentry.effectLayer`. It is enabled when
`enableMetrics: true` is added as option
This PR is now adding a different naming schema for enabling logs and
metrics based on:
https://develop.sentry.dev/sdk/telemetry/metrics/#auto-emitted-metrics

For the logs I also added them, which might not make the most sense, as
`enableLogs` is now `false` by default, which means that there is a
double opt-in needed to make logs work via `Effect.log`.

The naming is TBD, but this is the best I came up with:
`enableEffectLogs` & `enableEffectMetrics`
This adds Node and Browser tests for the `@sentry/effect` SDK.

I am not sure what to do with the browser part, as there is I guess no
tree-shaking available right now.

The basic usage for node and browser are the exact same, only the
`effectLayer` has to be added into the runtime layer.
@JPeer264 JPeer264 force-pushed the jp/add-effect-sdk branch from 8c2378e to 6d004ec Compare March 13, 2026 07:01
@JPeer264 JPeer264 marked this pull request as ready for review March 13, 2026 07:01
default:
logLevel satisfies never;
}
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effect log annotations silently dropped by logger

High Severity

The SentryEffectLogger destructures only { logLevel, message } from the Effect Logger callback, completely ignoring annotations. When users call Effect.annotateLogs('userId', '12345'), those annotations are available in the callback params but are never forwarded as attributes to the Sentry logger functions (sentryLogger.info(msg, attributes)). All log context data is silently lost. The E2E tests for "logs with context" don't assert on the attributes either, so this bug goes undetected.

Additional Locations (1)
Fix in Cursor Fix in Web

Copy link
Member

@andreiborza andreiborza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

)

To not mess with `@sentry/core` while adding the new SDK this, the
options are directly moved into the Effect SDK. The main reason why this
is now moved is that mutating the options could lead to other issues,
and this is why I want to keep this in a separate and smaller PR.
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

attributes: { ...attributes, word },
});
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Frequency metrics double-count on periodic flush cycles

Medium Severity

Frequency metrics send total accumulated occurrence counts via sentryMetrics.count on every periodic flush, but sentryMetrics.count is additive. Unlike counter metrics which have delta tracking via sendDeltaMetricToSentry, frequency metrics always re-send the full cumulative count, causing values to inflate with each flush cycle. For example, if "apple" has 3 occurrences, after two flushes Sentry records 3+3=6 instead of 3.

Additional Locations (1)
Fix in Cursor Fix in Web

Comment on lines +35 to +42
if (enableEffectLogs && enableLogs) {
const effectLogger = replaceLogger(defaultLogger, SentryEffectLogger);
layer = layer.pipe(provideMerge(effectLogger));
}

if (enableEffectMetrics && enableMetrics) {
layer = layer.pipe(provideMerge(SentryEffectMetricsLayer));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

h: If we ship this, we cannot tree-shake logger and metrics-related code in the browser. This is not how we usually add features to browser SDKs. Was this a conscious decision? I might be missing a reason for this, so don't feel compelled to rework it instantly.

Have we explored tree-shakeable alternatives? Random thought coming from a person without context on Effect: Could we instruct users to manually register SentryEffectLogger and SentryEffectsMetricsLayer? This would also count as an opt in. We can leave the enableEffectMetrics flags for the server implementation.

I marked this as H but given the bundle size increase just concerns Effect, we can take some liberties here. I just wonder why we don't do the same for tracing then (?).

Copy link
Member

@Lms24 Lms24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we should add e2e tests for metrics but feel free to follow up on this later

Copy link
Member

@Lms24 Lms24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found some more things regarding the tracer. We can iterate on this post merging as well but maybe we can clear up some of the confusion before.

Comment on lines +46 to +50
const isolationScope = getIsolationScope();
const transactionName = isolationScope.getScopeData().transactionName;
if (transactionName) {
return transactionName;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m/h: Who sets the transaction name on the scope? We never derive a span name from the transactionName on the scope. It should be the other way around: We find a good span name (see below) and we set this as the transactionName on the scope.

For a span with op http.server, the span name should follow the {METHOD} {ROUTE} pattern. Is there a way we can get a proper route?

My concern here also is that if it's just the default Node http integration setting the scope's transactionName based on the incoming request URL, this is an unparameterized path. Again leading me to the question if we can find a better route name here

Comment on lines +29 to +31
if (name.startsWith('http.client')) {
return 'http.client';
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m: just to confirm, we only set this op for outgoing (fetch/xhr/http) requests, correct?

And Q: Effect emits http.server and http.client as span names, correct?

Comment on lines +48 to +53
HttpRouter.get(
'/test-exception/:id',
Effect.sync(() => {
throw new Error('This is an exception with id 123');
}),
),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we able to extract /test-exception/:id for this route and set it as the scope's transactionName (see other comment)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for Effect.ts via its own SDK

3 participants