-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat(effect): Remove effectLayer auto composition #19823
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,18 +1,25 @@ | ||
| import * as Sentry from '@sentry/effect'; | ||
| import { HttpRouter, HttpServer, HttpServerResponse } from '@effect/platform'; | ||
| import { NodeHttpServer, NodeRuntime } from '@effect/platform-node'; | ||
| import { Cause, Effect, Layer, Logger, LogLevel } from 'effect'; | ||
| import * as Effect from 'effect/Effect'; | ||
| import * as Cause from 'effect/Cause'; | ||
| import * as Layer from 'effect/Layer'; | ||
| import * as Logger from 'effect/Logger'; | ||
| import * as LogLevel from 'effect/LogLevel'; | ||
| import { createServer } from 'http'; | ||
|
|
||
| const SentryLive = Sentry.effectLayer({ | ||
| dsn: process.env.E2E_TEST_DSN, | ||
| environment: 'qa', | ||
| debug: !!process.env.DEBUG, | ||
| tunnel: 'http://localhost:3031/', | ||
| tracesSampleRate: 1, | ||
| enableLogs: true, | ||
| enableEffectLogs: true, | ||
| }); | ||
| const SentryLive = Layer.mergeAll( | ||
| Sentry.effectLayer({ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is a fine setup and naming is also fine. To me (as an Effect noob), this signals that I have a question though: Does registering just the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Theoretically it would do everything a |
||
| dsn: process.env.E2E_TEST_DSN, | ||
| environment: 'qa', | ||
| debug: !!process.env.DEBUG, | ||
| tunnel: 'http://localhost:3031/', | ||
| tracesSampleRate: 1, | ||
| enableLogs: true, | ||
| }), | ||
| Layer.setTracer(Sentry.SentryEffectTracer), | ||
| Logger.replace(Logger.defaultLogger, Sentry.SentryEffectLogger), | ||
| ); | ||
|
|
||
| const router = HttpRouter.empty.pipe( | ||
| HttpRouter.get('/test-success', HttpServerResponse.json({ version: 'v1' })), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,89 +3,97 @@ import { waitForTransaction } from '@sentry-internal/test-utils'; | |
|
|
||
| test('Sends an HTTP transaction', async ({ baseURL }) => { | ||
| const transactionEventPromise = waitForTransaction('effect-node', transactionEvent => { | ||
| return ( | ||
| transactionEvent?.contexts?.trace?.op === 'http.server' && | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ambiguous E2E test transaction filters match wrong transactionsMedium Severity The "Sends an HTTP transaction" and "Sends transaction for error route" tests both use the identical filter Additional Locations (1) |
||
| transactionEvent?.transaction?.includes('/test-success') | ||
| ); | ||
| return transactionEvent?.transaction === 'http.server GET'; | ||
| }); | ||
|
|
||
| await fetch(`${baseURL}/test-success`); | ||
|
|
||
| const transactionEvent = await transactionEventPromise; | ||
|
|
||
| expect(transactionEvent.contexts?.trace?.op).toBe('http.server'); | ||
| expect(transactionEvent.transaction).toContain('/test-success'); | ||
| expect(transactionEvent.transaction).toBe('http.server GET'); | ||
| }); | ||
|
|
||
| test('Sends transaction with manual Effect span', async ({ baseURL }) => { | ||
| const transactionEventPromise = waitForTransaction('effect-node', transactionEvent => { | ||
| return ( | ||
| transactionEvent?.contexts?.trace?.op === 'http.server' && | ||
| transactionEvent?.transaction?.includes('/test-transaction') | ||
| transactionEvent?.transaction === 'http.server GET' && | ||
| transactionEvent?.spans?.some(span => span.description === 'test-span') | ||
| ); | ||
| }); | ||
|
|
||
| await fetch(`${baseURL}/test-transaction`); | ||
|
|
||
| const transactionEvent = await transactionEventPromise; | ||
|
|
||
| expect(transactionEvent.contexts?.trace?.op).toBe('http.server'); | ||
| expect(transactionEvent.transaction).toContain('/test-transaction'); | ||
| expect(transactionEvent.transaction).toBe('http.server GET'); | ||
|
|
||
| const spans = transactionEvent.spans || []; | ||
| expect(spans).toContainEqual( | ||
| expect(spans).toEqual([ | ||
| expect.objectContaining({ | ||
| description: 'test-span', | ||
| }), | ||
| ); | ||
| ]); | ||
| }); | ||
|
|
||
| test('Sends Effect spans with correct parent-child structure', async ({ baseURL }) => { | ||
| const transactionEventPromise = waitForTransaction('effect-node', transactionEvent => { | ||
| return ( | ||
| transactionEvent?.contexts?.trace?.op === 'http.server' && | ||
| transactionEvent?.transaction?.includes('/test-effect-span') | ||
| transactionEvent?.transaction === 'http.server GET' && | ||
| transactionEvent?.spans?.some(span => span.description === 'custom-effect-span') | ||
| ); | ||
| }); | ||
|
|
||
| await fetch(`${baseURL}/test-effect-span`); | ||
|
|
||
| const transactionEvent = await transactionEventPromise; | ||
|
|
||
| expect(transactionEvent.contexts?.trace?.op).toBe('http.server'); | ||
| expect(transactionEvent.transaction).toContain('/test-effect-span'); | ||
|
|
||
| const spans = transactionEvent.spans || []; | ||
| expect(transactionEvent.transaction).toBe('http.server GET'); | ||
|
|
||
| expect(spans).toContainEqual( | ||
| expect(transactionEvent).toEqual( | ||
| expect.objectContaining({ | ||
| description: 'custom-effect-span', | ||
| op: 'internal', | ||
| contexts: expect.objectContaining({ | ||
| trace: expect.objectContaining({ | ||
| origin: 'auto.http.effect', | ||
| }), | ||
| }), | ||
| spans: [ | ||
| expect.objectContaining({ | ||
| description: 'custom-effect-span', | ||
| origin: 'auto.function.effect', | ||
| }), | ||
| expect.objectContaining({ | ||
| description: 'nested-span', | ||
| origin: 'auto.function.effect', | ||
| }), | ||
| ], | ||
| sdk: expect.objectContaining({ | ||
| name: 'sentry.javascript.effect', | ||
| packages: [ | ||
| expect.objectContaining({ | ||
| name: 'npm:@sentry/effect', | ||
| }), | ||
| expect.objectContaining({ | ||
| name: 'npm:@sentry/node-light', | ||
| }), | ||
| ], | ||
| }), | ||
| }), | ||
| ); | ||
|
|
||
| expect(spans).toContainEqual( | ||
| expect.objectContaining({ | ||
| description: 'nested-span', | ||
| }), | ||
| ); | ||
| const parentSpan = transactionEvent.spans?.[0]?.span_id; | ||
| const nestedSpan = transactionEvent.spans?.[1]?.parent_span_id; | ||
|
|
||
| const parentSpan = spans.find(s => s.description === 'custom-effect-span'); | ||
| const nestedSpan = spans.find(s => s.description === 'nested-span'); | ||
| expect(nestedSpan?.parent_span_id).toBe(parentSpan?.span_id); | ||
| expect(nestedSpan).toBe(parentSpan); | ||
| }); | ||
|
|
||
| test('Sends transaction for error route', async ({ baseURL }) => { | ||
| const transactionEventPromise = waitForTransaction('effect-node', transactionEvent => { | ||
| return ( | ||
| transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent?.transaction?.includes('/test-error') | ||
| ); | ||
| return transactionEvent?.transaction === 'http.server GET'; | ||
| }); | ||
|
|
||
| await fetch(`${baseURL}/test-error`); | ||
|
|
||
| const transactionEvent = await transactionEventPromise; | ||
|
|
||
| expect(transactionEvent.contexts?.trace?.op).toBe('http.server'); | ||
| expect(transactionEvent.transaction).toContain('/test-error'); | ||
| expect(transactionEvent.transaction).toBe('http.server GET'); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,30 +15,29 @@ This SDK does not have docs yet. Stay tuned. | |
| ```typescript | ||
| import * as Sentry from '@sentry/effect/server'; | ||
| import { NodeRuntime } from '@effect/platform-node'; | ||
| import { Layer } from 'effect'; | ||
| import { Layer, Logger } from 'effect'; | ||
| import { HttpLive } from './Http.js'; | ||
|
|
||
| const MainLive = HttpLive.pipe( | ||
| Layer.provide( | ||
| Sentry.effectLayer({ | ||
| dsn: '__DSN__', | ||
| tracesSampleRate: 1.0, | ||
| enableLogs: true, | ||
| enableEffectLogs: true, | ||
| enableEffectMetrics: true, | ||
| }), | ||
| ), | ||
| const SentryLive = Layer.mergeAll( | ||
| Sentry.effectLayer({ | ||
| dsn: '__DSN__', | ||
| tracesSampleRate: 1.0, | ||
| enableLogs: true, | ||
| }), | ||
| Layer.setTracer(Sentry.SentryEffectTracer), | ||
| Logger.replace(Logger.defaultLogger, Sentry.SentryEffectLogger), | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. l: should we add the metrics layer here?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. well said. Will add |
||
| Sentry.SentryEffectMetricsLayer, | ||
| ); | ||
|
|
||
| const MainLive = HttpLive.pipe(Layer.provide(SentryLive)); | ||
| MainLive.pipe(Layer.launch, NodeRuntime.runMain); | ||
| ``` | ||
|
|
||
| The `effectLayer` function initializes Sentry and returns an Effect Layer that provides: | ||
| The `effectLayer` function initializes Sentry. To enable Effect instrumentation, compose with: | ||
|
|
||
| - Distributed tracing with automatic HTTP header extraction/injection | ||
| - Effect spans traced as Sentry spans | ||
| - Effect logs forwarded to Sentry (when `enableEffectLogs` is set) | ||
| - Effect metrics sent to Sentry (when `enableEffectMetrics` is set) | ||
| - `Layer.setTracer(Sentry.SentryEffectTracer)` - Effect spans traced as Sentry spans | ||
| - `Logger.replace(Logger.defaultLogger, Sentry.SentryEffectLogger)` - Effect logs forwarded to Sentry | ||
| - `Sentry.SentryEffectMetricsLayer` - Effect metrics sent to Sentry | ||
|
|
||
| ## Links | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,41 +1,43 @@ | ||
| import type { BrowserOptions } from '@sentry/browser'; | ||
| import type * as EffectLayer from 'effect/Layer'; | ||
| import { suspend as suspendLayer } from 'effect/Layer'; | ||
| import type { EffectLayerBaseOptions } from '../utils/buildEffectLayer'; | ||
| import { buildEffectLayer } from '../utils/buildEffectLayer'; | ||
| import { empty as emptyLayer, suspend as suspendLayer } from 'effect/Layer'; | ||
| import { init } from './sdk'; | ||
|
|
||
| export { init } from './sdk'; | ||
|
|
||
| /** | ||
| * Options for the Sentry Effect client layer. | ||
| */ | ||
| export type EffectClientLayerOptions = BrowserOptions & EffectLayerBaseOptions; | ||
| export type EffectClientLayerOptions = BrowserOptions; | ||
|
|
||
| /** | ||
| * Creates an Effect Layer that initializes Sentry for browser clients. | ||
| * | ||
| * This layer provides Effect applications with full Sentry instrumentation including: | ||
| * - Effect spans traced as Sentry spans | ||
| * - Effect logs forwarded to Sentry (when `enableEffectLogs` is set) | ||
| * - Effect metrics sent to Sentry (when `enableEffectMetrics` is set) | ||
| * To enable Effect tracing, logs, or metrics, compose with the respective layers: | ||
| * - `Layer.setTracer(Sentry.SentryEffectTracer)` for tracing | ||
| * - `Logger.replace(Logger.defaultLogger, Sentry.SentryEffectLogger)` for logs | ||
| * - `Sentry.SentryEffectMetricsLayer` for metrics | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * import * as Sentry from '@sentry/effect/client'; | ||
| * import { Layer, Effect } from 'effect'; | ||
| * import { Layer, Logger, LogLevel } from 'effect'; | ||
| * | ||
| * const ApiClientWithSentry = ApiClientLive.pipe( | ||
| * Layer.provide(Sentry.effectLayer({ | ||
| * const SentryLive = Layer.mergeAll( | ||
| * Sentry.effectLayer({ | ||
| * dsn: '__DSN__', | ||
| * integrations: [Sentry.browserTracingIntegration()], | ||
| * tracesSampleRate: 1.0, | ||
| * })), | ||
| * }), | ||
| * Layer.setTracer(Sentry.SentryEffectTracer), | ||
| * Logger.replace(Logger.defaultLogger, Sentry.SentryEffectLogger), | ||
| * ); | ||
| * | ||
| * Effect.runPromise(Effect.provide(myEffect, ApiClientWithSentry)); | ||
| * ``` | ||
| */ | ||
| export function effectLayer(options: EffectClientLayerOptions): EffectLayer.Layer<never, never, never> { | ||
| return suspendLayer(() => buildEffectLayer(options, init(options))); | ||
| return suspendLayer(() => { | ||
| init(options); | ||
|
|
||
| return emptyLayer; | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,42 +1,43 @@ | ||
| import type { NodeOptions } from '@sentry/node-core/light'; | ||
| import type * as EffectLayer from 'effect/Layer'; | ||
| import type { EffectLayerBaseOptions } from '../utils/buildEffectLayer'; | ||
| import { buildEffectLayer } from '../utils/buildEffectLayer'; | ||
| import { empty as emptyLayer, suspend as suspendLayer } from 'effect/Layer'; | ||
| import { init } from './sdk'; | ||
|
|
||
| export { init } from './sdk'; | ||
|
|
||
| /** | ||
| * Options for the Sentry Effect server layer. | ||
| */ | ||
| export type EffectServerLayerOptions = NodeOptions & EffectLayerBaseOptions; | ||
| export type EffectServerLayerOptions = NodeOptions; | ||
|
|
||
| /** | ||
| * Creates an Effect Layer that initializes Sentry for Node.js servers. | ||
| * | ||
| * This layer provides Effect applications with full Sentry instrumentation including: | ||
| * - Effect spans traced as Sentry spans | ||
| * - Effect logs forwarded to Sentry (when `enableEffectLogs` is set) | ||
| * - Effect metrics sent to Sentry (when `enableEffectMetrics` is set) | ||
| * To enable Effect tracing, logs, or metrics, compose with the respective layers: | ||
| * - `Layer.setTracer(Sentry.SentryEffectTracer)` for tracing | ||
| * - `Logger.replace(Logger.defaultLogger, Sentry.SentryEffectLogger)` for logs | ||
| * - `Sentry.SentryEffectMetricsLayer` for metrics | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * import * as Sentry from '@sentry/effect/server'; | ||
| * import { NodeRuntime } from '@effect/platform-node'; | ||
| * import { Layer } from 'effect'; | ||
| * import { Layer, Logger } from 'effect'; | ||
| * import { HttpLive } from './Http.js'; | ||
| * | ||
| * const MainLive = HttpLive.pipe( | ||
| * Layer.provide(Sentry.effectLayer({ | ||
| * dsn: '__DSN__', | ||
| * enableEffectLogs: true, | ||
| * enableEffectMetrics: true, | ||
| * })), | ||
| * const SentryLive = Layer.mergeAll( | ||
| * Sentry.effectLayer({ dsn: '__DSN__' }), | ||
| * Layer.setTracer(Sentry.SentryEffectTracer), | ||
| * Logger.replace(Logger.defaultLogger, Sentry.SentryEffectLogger), | ||
| * ); | ||
| * | ||
| * const MainLive = HttpLive.pipe(Layer.provide(SentryLive)); | ||
| * MainLive.pipe(Layer.launch, NodeRuntime.runMain); | ||
| * ``` | ||
| */ | ||
| export function effectLayer(options: EffectServerLayerOptions): EffectLayer.Layer<never, never, never> { | ||
| return buildEffectLayer(options, init(options)); | ||
| return suspendLayer(() => { | ||
| init(options); | ||
| return emptyLayer; | ||
| }); | ||
| } |


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
l: just checking, no action required: Does effect add any other attributes we could/should assert on?
(also, removing the op here sounds correct to me. Span kind doesn't map well to sentry ops and there's no
internalop defined)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added couple of more assertions to this specific test and removed the
.toContainEqualin the file completely.