diff --git a/packages/effect/README.md b/packages/effect/README.md index c04ff97446e4..e98bf76d8df0 100644 --- a/packages/effect/README.md +++ b/packages/effect/README.md @@ -22,6 +22,7 @@ const MainLive = HttpLive.pipe( Layer.provide( Sentry.effectLayer({ dsn: '__DSN__', + tracesSampleRate: 1.0, enableLogs: true, enableEffectLogs: true, enableEffectMetrics: true, diff --git a/packages/effect/src/index.client.ts b/packages/effect/src/index.client.ts index e13f1ddea09e..2df8a2548fb9 100644 --- a/packages/effect/src/index.client.ts +++ b/packages/effect/src/index.client.ts @@ -5,3 +5,7 @@ export * from '@sentry/browser'; export { effectLayer, init } from './client/index'; export type { EffectClientLayerOptions } from './client/index'; + +export { SentryEffectTracer } from './tracer'; +export { SentryEffectLogger } from './logger'; +export { SentryEffectMetricsLayer } from './metrics'; diff --git a/packages/effect/src/index.server.ts b/packages/effect/src/index.server.ts index a3f8e4f3766f..c66abbf43413 100644 --- a/packages/effect/src/index.server.ts +++ b/packages/effect/src/index.server.ts @@ -2,3 +2,7 @@ export * from '@sentry/node-core/light'; export { effectLayer, init } from './server/index'; export type { EffectServerLayerOptions } from './server/index'; + +export { SentryEffectTracer } from './tracer'; +export { SentryEffectLogger } from './logger'; +export { SentryEffectMetricsLayer } from './metrics'; diff --git a/packages/effect/src/tracer.ts b/packages/effect/src/tracer.ts index 116b7970a6ae..6a9c52b38ee6 100644 --- a/packages/effect/src/tracer.ts +++ b/packages/effect/src/tracer.ts @@ -8,8 +8,6 @@ import { } from '@sentry/core'; import type * as Context from 'effect/Context'; import * as Exit from 'effect/Exit'; -import type * as Layer from 'effect/Layer'; -import { setTracer } from 'effect/Layer'; import * as Option from 'effect/Option'; import * as EffectTracer from 'effect/Tracer'; @@ -198,4 +196,4 @@ const makeSentryTracer = (): EffectTracer.Tracer => /** * Effect Layer that sets up the Sentry tracer for Effect spans. */ -export const SentryEffectTracerLayer: Layer.Layer = setTracer(makeSentryTracer()); +export const SentryEffectTracer = makeSentryTracer(); diff --git a/packages/effect/src/utils/buildEffectLayer.ts b/packages/effect/src/utils/buildEffectLayer.ts index 6516b99b497a..42d46a91d305 100644 --- a/packages/effect/src/utils/buildEffectLayer.ts +++ b/packages/effect/src/utils/buildEffectLayer.ts @@ -1,10 +1,10 @@ -import type { Client } from '@sentry/core'; +import { hasSpansEnabled, type Client } from '@sentry/core'; import type * as EffectLayer from 'effect/Layer'; -import { empty as emptyLayer, provideMerge } from 'effect/Layer'; +import { empty as emptyLayer, provideMerge, setTracer } from 'effect/Layer'; import { defaultLogger, replace as replaceLogger } from 'effect/Logger'; import { SentryEffectLogger } from '../logger'; import { SentryEffectMetricsLayer } from '../metrics'; -import { SentryEffectTracerLayer } from '../tracer'; +import { SentryEffectTracer } from '../tracer'; export interface EffectLayerBaseOptions { enableEffectLogs?: boolean; @@ -27,10 +27,15 @@ export function buildEffectLayer( } const clientOptions = client.getOptions(); + const hasSpans = hasSpansEnabled(clientOptions); const enableMetrics = clientOptions.enableMetrics ?? clientOptions._experiments?.enableMetrics ?? true; const enableLogs = clientOptions.enableLogs ?? clientOptions._experiments?.enableLogs ?? false; const { enableEffectLogs = false, enableEffectMetrics = false } = options; - let layer: EffectLayer.Layer = SentryEffectTracerLayer; + let layer = emptyLayer; + + if (hasSpans) { + layer = layer.pipe(provideMerge(setTracer(SentryEffectTracer))); + } if (enableEffectLogs && enableLogs) { const effectLogger = replaceLogger(defaultLogger, SentryEffectLogger); diff --git a/packages/effect/test/buildEffectLayer.test.ts b/packages/effect/test/buildEffectLayer.test.ts index d74045d5cb99..e6f4e4c77819 100644 --- a/packages/effect/test/buildEffectLayer.test.ts +++ b/packages/effect/test/buildEffectLayer.test.ts @@ -136,7 +136,7 @@ describe('buildEffectLayer', () => { }), ); startInactiveSpanSpy.mockRestore(); - }).pipe(Effect.provide(buildEffectLayer({}, createClient()))), + }).pipe(Effect.provide(buildEffectLayer({}, createClient({ tracesSampleRate: 1.0 })))), ); }); diff --git a/packages/effect/test/tracer.test.ts b/packages/effect/test/tracer.test.ts index 8955200695fa..b8313ce6d421 100644 --- a/packages/effect/test/tracer.test.ts +++ b/packages/effect/test/tracer.test.ts @@ -2,10 +2,13 @@ import { describe, expect, it } from '@effect/vitest'; import * as sentryCore from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; import { Effect } from 'effect'; +import { setTracer } from 'effect/Layer'; import { afterEach, vi } from 'vitest'; -import { SentryEffectTracerLayer } from '../src/tracer'; +import { SentryEffectTracer } from '../src/tracer'; -describe('SentryEffectTracerLayer', () => { +const SentryTracerLayer = setTracer(SentryEffectTracer); + +describe('SentryEffectTracer', () => { afterEach(() => { vi.restoreAllMocks(); }); @@ -22,7 +25,7 @@ describe('SentryEffectTracerLayer', () => { ); expect(capturedSpanName).toBe('effect-span-executed'); - }).pipe(Effect.provide(SentryEffectTracerLayer)), + }).pipe(Effect.provide(SentryTracerLayer)), ); it.effect('creates spans with correct attributes', () => @@ -30,7 +33,7 @@ describe('SentryEffectTracerLayer', () => { const result = yield* Effect.withSpan('my-operation')(Effect.succeed('success')); expect(result).toBe('success'); - }).pipe(Effect.provide(SentryEffectTracerLayer)), + }).pipe(Effect.provide(SentryTracerLayer)), ); it.effect('handles nested spans', () => @@ -43,7 +46,7 @@ describe('SentryEffectTracerLayer', () => { ); expect(result).toBe('outer-inner-result'); - }).pipe(Effect.provide(SentryEffectTracerLayer)), + }).pipe(Effect.provide(SentryTracerLayer)), ); it.effect('propagates span context through Effect fibers', () => @@ -60,7 +63,7 @@ describe('SentryEffectTracerLayer', () => { ); expect(results).toEqual(['parent-start', 'child-1', 'child-2', 'parent-end']); - }).pipe(Effect.provide(SentryEffectTracerLayer)), + }).pipe(Effect.provide(SentryTracerLayer)), ); it.effect('handles span failures correctly', () => @@ -70,7 +73,7 @@ describe('SentryEffectTracerLayer', () => { ); expect(result).toBe('caught: expected-error'); - }).pipe(Effect.provide(SentryEffectTracerLayer)), + }).pipe(Effect.provide(SentryTracerLayer)), ); it.effect('handles span with defects (die)', () => @@ -80,7 +83,7 @@ describe('SentryEffectTracerLayer', () => { ); expect(result).toBe('caught-defect: defect-value'); - }).pipe(Effect.provide(SentryEffectTracerLayer)), + }).pipe(Effect.provide(SentryTracerLayer)), ); it.effect('works with Effect.all for parallel operations', () => @@ -94,7 +97,7 @@ describe('SentryEffectTracerLayer', () => { ); expect(results).toEqual([1, 2, 3]); - }).pipe(Effect.provide(SentryEffectTracerLayer)), + }).pipe(Effect.provide(SentryTracerLayer)), ); it.effect('supports span annotations', () => @@ -105,7 +108,7 @@ describe('SentryEffectTracerLayer', () => { ); expect(result).toBe('annotated'); - }).pipe(Effect.provide(SentryEffectTracerLayer)), + }).pipe(Effect.provide(SentryTracerLayer)), ); it.effect('sets span status to ok on success', () => @@ -128,7 +131,7 @@ describe('SentryEffectTracerLayer', () => { expect(setStatusCalls).toContainEqual({ code: 1 }); mockStartInactiveSpan.mockRestore(); - }).pipe(Effect.provide(SentryEffectTracerLayer)), + }).pipe(Effect.provide(SentryTracerLayer)), ); it.effect('sets span status to error on failure', () => @@ -151,7 +154,7 @@ describe('SentryEffectTracerLayer', () => { expect(setStatusCalls).toContainEqual({ code: 2, message: 'test-error' }); mockStartInactiveSpan.mockRestore(); - }).pipe(Effect.provide(SentryEffectTracerLayer)), + }).pipe(Effect.provide(SentryTracerLayer)), ); it.effect('sets span status to error on defect', () => @@ -174,7 +177,7 @@ describe('SentryEffectTracerLayer', () => { expect(setStatusCalls).toContainEqual({ code: 2, message: 'fatal-defect' }); mockStartInactiveSpan.mockRestore(); - }).pipe(Effect.provide(SentryEffectTracerLayer)), + }).pipe(Effect.provide(SentryTracerLayer)), ); it.effect('propagates Sentry span context via withActiveSpan', () => @@ -195,7 +198,7 @@ describe('SentryEffectTracerLayer', () => { expect(withActiveSpanCalls.length).toBeGreaterThan(0); mockWithActiveSpan.mockRestore(); - }).pipe(Effect.provide(SentryEffectTracerLayer)), + }).pipe(Effect.provide(SentryTracerLayer)), ); it.effect('sets origin to auto.function.effect for regular spans', () => @@ -220,7 +223,7 @@ describe('SentryEffectTracerLayer', () => { expect(capturedAttributes?.[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]).toBe('auto.function.effect'); mockStartInactiveSpan.mockRestore(); - }).pipe(Effect.provide(SentryEffectTracerLayer)), + }).pipe(Effect.provide(SentryTracerLayer)), ); it.effect('sets origin to auto.http.effect for http.server spans', () => @@ -245,7 +248,7 @@ describe('SentryEffectTracerLayer', () => { expect(capturedAttributes?.[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]).toBe('auto.http.effect'); mockStartInactiveSpan.mockRestore(); - }).pipe(Effect.provide(SentryEffectTracerLayer)), + }).pipe(Effect.provide(SentryTracerLayer)), ); it.effect('sets origin to auto.http.effect for http.client spans', () => @@ -270,7 +273,7 @@ describe('SentryEffectTracerLayer', () => { expect(capturedAttributes?.[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]).toBe('auto.http.effect'); mockStartInactiveSpan.mockRestore(); - }).pipe(Effect.provide(SentryEffectTracerLayer)), + }).pipe(Effect.provide(SentryTracerLayer)), ); it.effect('uses transaction name from isolation scope for http.server spans', () => @@ -301,6 +304,6 @@ describe('SentryEffectTracerLayer', () => { mockStartInactiveSpan.mockRestore(); mockGetIsolationScope.mockRestore(); - }).pipe(Effect.provide(SentryEffectTracerLayer)), + }).pipe(Effect.provide(SentryTracerLayer)), ); });