Skip to content

Commit fcaeb0f

Browse files
authored
perf: switch from @sentry/bun to @sentry/node-core/light (~170ms startup savings) (#474)
## Summary Switch from `@sentry/bun` to `@sentry/node-core/light` to eliminate the OpenTelemetry dependency tree that a CLI tool never uses. Additionally, patch `@sentry/core` barrel to remove unused modules (AI tracing, MCP server, Supabase, feature flags). ## Changes ### Phase 1: SDK upgrade - Upgrade Sentry SDK `10.39.0` → `10.44.0` ### Phase 2: Import migration (~150ms savings) - Replace `@sentry/bun` + `@sentry/node` dependencies with `@sentry/node-core` - Replace all `import * as Sentry from "@sentry/bun"` → `"@sentry/node-core/light"` (15 source files, 4 test files) - Change `BunClient` → `LightNodeClient` in telemetry.ts - Export `Span` type from `@sentry/core` (canonical source) - Remove esbuild `@sentry/bun` → `@sentry/node` alias in bundle.ts (no longer needed) ### Phase 3: Barrel patching (~14ms savings) - Patch `@sentry/core` barrel to remove 32 export lines pulling in ~59 unused transitive modules (AI tracing integrations, MCP server, Supabase, feature flags, etc.) - Patch `@sentry/node-core/light` barrel to remove 11 matching re-exports ### Phase 4: Upstream issue drafts - Added `.github/upstream-issues/` with drafts for: - `@sentry/core/light` sub-path export request - `@sentry/bun/light` entry point request ## What we lose (nothing meaningful) | Feature | Why irrelevant | |---------|---------------| | 29 OpenTelemetry auto-instrumentations | CLI is not a web server | | `bunServerIntegration` | CLI does not call `Bun.serve()` | | `BunClient` class | Deprecated since v9; replaced by `LightNodeClient` | | Bun-native fetch transport | `makeNodeTransport` works fine in Bun | | OTEL context propagation | Light mode uses `AsyncLocalStorage` — same behavior | | SDK envelope metadata `"bun"` | Changes to `"node-light"` — cosmetic only. `cli.runtime` tag still distinguishes Bun vs Node | ## API compatibility verified Every API the CLI uses (`captureException`, `startSpan`, `setTag`, `metrics`, `logger`, `createConsolaReporter`, `getTraceData`, `isEnabled`, `flush`, `endSession`, etc.) is available in `@sentry/node-core/light`. ## Risk Light mode is labeled experimental, but we pin SDK versions exactly (`10.44.0`, not `^10.44.0`), so we control when SDK updates happen. ## Verification - ✅ `bun run typecheck` — clean - ✅ `bun run test:unit` — 3883/3885 pass (2 pre-existing flaky failures in upgrade tests) - ✅ `bun run test:isolated` — 126/126 pass - ✅ `bun run check:skill` — up to date - ✅ `bun run check:errors` — no anti-patterns - ✅ Patched SDK exports all needed symbols, removed symbols are `undefined` - 📊 SDK import time: ~114ms (down from ~285ms) Closes #472
1 parent 12b2c8b commit fcaeb0f

26 files changed

+457
-138
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# feat(bun): Add `@sentry/bun/light` entry point without OpenTelemetry
2+
3+
**Repo**: https://github.com/getsentry/sentry-javascript
4+
5+
## Problem
6+
7+
`@sentry/bun` imports `@sentry/node`, which eagerly loads the entire OpenTelemetry stack and **29+ auto-instrumentation modules** (Express, MongoDB, Redis, PostgreSQL, Kafka, Prisma, AI providers, etc.). For CLI tools and other non-server Bun applications, none of these instrumentations are relevant, yet they cost:
8+
9+
- **~150ms** additional import time
10+
- **~24MB** in `node_modules` (36 `@opentelemetry/*` packages)
11+
12+
## Measured Impact
13+
14+
Benchmarks from our Bun CLI project ([getsentry/cli](https://github.com/getsentry/cli)):
15+
16+
| Import | Time | What Loads |
17+
|--------|------|------------|
18+
| `@sentry/bun` | **~285ms** | Full OTel stack + 29 auto-instrumentations (36 OTel packages) |
19+
| `@sentry/node-core/light` | **~130ms** | Core SDK only, no OTel, AsyncLocalStorage for context |
20+
21+
The **~155ms difference** is entirely from OpenTelemetry packages that a CLI tool never uses. `@sentry/bun` adds only ~2ms on top of `@sentry/node` (it's a thin wrapper: `BunClient`, `bunServerIntegration`, `makeFetchTransport`).
22+
23+
## Proposal
24+
25+
Add `@sentry/bun/light` that mirrors what `@sentry/node-core/light` does for Node:
26+
27+
1. **Uses `@sentry/node-core/light` instead of `@sentry/node`** — no OpenTelemetry dependency
28+
2. **Keeps Bun-specific niceties**:
29+
- `makeFetchTransport` as default transport (uses global `fetch()`)
30+
- `runtime: { name: 'bun', version: Bun.version }` in SDK metadata
31+
- SDK metadata tagged as `"bun-light"` (or similar)
32+
3. **No `bunServerIntegration` in defaults** — it's for `Bun.serve()` which light-mode users (CLIs, scripts) likely don't need
33+
4. **Uses `AsyncLocalStorage`** for context propagation (same as `@sentry/node-core/light`)
34+
35+
### Usage
36+
37+
```typescript
38+
// Before (285ms):
39+
import * as Sentry from "@sentry/bun";
40+
41+
// After (130ms):
42+
import * as Sentry from "@sentry/bun/light";
43+
```
44+
45+
All existing APIs (`captureException`, `startSpan`, `setTag`, `metrics`, `logger`, `createConsolaReporter`, etc.) would be available — just without the OTel auto-instrumentation overhead.
46+
47+
## Current Workaround
48+
49+
We switched from `@sentry/bun` to `@sentry/node-core/light` directly. This works but:
50+
- Loses Bun-specific SDK metadata (runtime name shows as "node" instead of "bun")
51+
- Doesn't use `makeFetchTransport` by default (falls back to Node's `http` module — works in Bun but isn't native)
52+
- Requires users to know about the internal package structure
53+
54+
A first-party `@sentry/bun/light` would be more ergonomic and keep Bun users within the expected SDK.
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# feat(core): Add `@sentry/core/light` sub-path export to reduce import time for lightweight consumers
2+
3+
**Repo**: https://github.com/getsentry/sentry-javascript
4+
5+
## Problem
6+
7+
`@sentry/core` has a single barrel entry point (`index.js`) that eagerly loads **179 ESM modules** (~776 KB). This includes:
8+
9+
- AI tracing integrations: OpenAI, Anthropic, Google GenAI, LangChain, LangGraph, Vercel AI SDK (21 files, ~170 KB)
10+
- MCP server integration (14 files, ~160 KB)
11+
- Supabase integration (1 file, ~14 KB)
12+
- Feature flags integrations (3 files, ~8 KB)
13+
- Other unused server-framework-specific code: tRPC, fetch instrumentation, idle spans, etc.
14+
15+
Even `@sentry/node-core/light`, which was designed to be lightweight, re-exports from the full `@sentry/core` barrel, forcing consumers to pay the entire import cost.
16+
17+
## Measured Impact
18+
19+
On our Bun CLI project ([getsentry/cli](https://github.com/getsentry/cli)):
20+
21+
| Configuration | `@sentry/core` Import Time |
22+
|---|---|
23+
| Full barrel (current) | **~40ms** |
24+
| Barrel with 29 unused export lines removed (59 transitive modules, 310 KB) | **~27ms** |
25+
26+
That's a **33% improvement** just by removing modules that lightweight consumers never use.
27+
28+
## Proposal
29+
30+
Add a `@sentry/core/light` sub-path export (similar to `@sentry/node-core/light`) that excludes:
31+
32+
- AI tracing integrations (OpenAI, Anthropic, Google GenAI, LangChain, LangGraph, Vercel AI SDK)
33+
- MCP server integration
34+
- Supabase integration
35+
- Feature flags integrations (featureFlagsIntegration, growthbookIntegration)
36+
- Server-framework-specific code (tRPC middleware, fetch instrumentation, idle spans)
37+
- Other heavy modules not needed for basic error/tracing/session/logs usage
38+
39+
Then `@sentry/node-core/light` could import from `@sentry/core/light` instead of `@sentry/core`, further reducing the startup cost of lightweight mode.
40+
41+
## Our Patch (for reference)
42+
43+
We're currently removing these 32 export lines from the barrel via `bun patch`:
44+
45+
```
46+
export { TRACING_DEFAULTS, startIdleSpan } from './tracing/idleSpan.js';
47+
export { _INTERNAL_clearAiProviderSkips, ... } from './utils/ai/providerSkip.js';
48+
export { moduleMetadataIntegration } from './integrations/moduleMetadata.js';
49+
export { captureConsoleIntegration } from './integrations/captureconsole.js';
50+
export { dedupeIntegration } from './integrations/dedupe.js';
51+
export { extraErrorDataIntegration } from './integrations/extraerrordata.js';
52+
export { rewriteFramesIntegration } from './integrations/rewriteframes.js';
53+
export { instrumentSupabaseClient, supabaseIntegration } from './integrations/supabase.js';
54+
export { instrumentPostgresJsSql } from './integrations/postgresjs.js';
55+
export { zodErrorsIntegration } from './integrations/zoderrors.js';
56+
export { thirdPartyErrorFilterIntegration } from './integrations/third-party-errors-filter.js';
57+
export { featureFlagsIntegration } from './integrations/featureFlags/featureFlagsIntegration.js';
58+
export { growthbookIntegration } from './integrations/featureFlags/growthbook.js';
59+
export { conversationIdIntegration } from './integrations/conversationId.js';
60+
export { profiler } from './profiling.js';
61+
export { instrumentFetchRequest } from './fetch.js';
62+
export { trpcMiddleware } from './trpc.js';
63+
export { wrapMcpServerWithSentry } from './integrations/mcp-server/index.js';
64+
export { addVercelAiProcessors } from './tracing/vercel-ai/index.js';
65+
export { _INTERNAL_cleanupToolCallSpanContext, ... } from './tracing/vercel-ai/utils.js';
66+
export { toolCallSpanContextMap as ... } from './tracing/vercel-ai/constants.js';
67+
export { instrumentOpenAiClient } from './tracing/openai/index.js';
68+
export { OPENAI_INTEGRATION_NAME } from './tracing/openai/constants.js';
69+
export { instrumentAnthropicAiClient } from './tracing/anthropic-ai/index.js';
70+
export { ANTHROPIC_AI_INTEGRATION_NAME } from './tracing/anthropic-ai/constants.js';
71+
export { instrumentGoogleGenAIClient } from './tracing/google-genai/index.js';
72+
export { GOOGLE_GENAI_INTEGRATION_NAME } from './tracing/google-genai/constants.js';
73+
export { createLangChainCallbackHandler } from './tracing/langchain/index.js';
74+
export { LANGCHAIN_INTEGRATION_NAME } from './tracing/langchain/constants.js';
75+
export { instrumentLangGraph, instrumentStateGraphCompile } from './tracing/langgraph/index.js';
76+
export { LANGGRAPH_INTEGRATION_NAME } from './tracing/langgraph/constants.js';
77+
export { _INTERNAL_FLAG_BUFFER_SIZE, ... } from './utils/featureFlags.js';
78+
```
79+
80+
We also remove the corresponding re-exports from `@sentry/node-core/light/index.js`.
81+
82+
Happy to contribute a PR if the approach is accepted.

0 commit comments

Comments
 (0)