Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/core/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,10 +256,10 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {

// todo(v11): Remove the experimental flag
// eslint-disable-next-line deprecation/deprecation
const enableMetrics = this._options.enableMetrics ?? this._options._experiments?.enableMetrics ?? true;
this._options.enableMetrics = this._options.enableMetrics ?? this._options._experiments?.enableMetrics ?? true;

// Setup metric flushing with weight and timeout tracking
if (enableMetrics) {
if (this._options.enableMetrics) {
setupWeightBasedFlushing(
this,
'afterCaptureMetric',
Expand Down
7 changes: 4 additions & 3 deletions packages/effect/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ const MainLive = HttpLive.pipe(
Sentry.effectLayer({
dsn: '__DSN__',
enableLogs: true,
enableMetrics: true,
enableEffectLogs: true,
enableEffectMetrics: true,
}),
),
);
Expand All @@ -35,8 +36,8 @@ The `effectLayer` function initializes Sentry and returns an Effect Layer that p

- Distributed tracing with automatic HTTP header extraction/injection
- Effect spans traced as Sentry spans
- Effect logs forwarded to Sentry (when `enableLogs` is set)
- Effect metrics sent to Sentry (when `enableMetrics` is set)
- Effect logs forwarded to Sentry (when `enableEffectLogs` is set)
- Effect metrics sent to Sentry (when `enableEffectMetrics` is set)

## Links

Expand Down
7 changes: 4 additions & 3 deletions packages/effect/src/client/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
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 { init } from './sdk';

Expand All @@ -9,15 +10,15 @@ export { init } from './sdk';
/**
* Options for the Sentry Effect client layer.
*/
export type EffectClientLayerOptions = BrowserOptions;
export type EffectClientLayerOptions = BrowserOptions & EffectLayerBaseOptions;

/**
* 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 `enableLogs` is set)
* - Effect metrics sent to Sentry (when `enableMetrics` is set)
* - Effect logs forwarded to Sentry (when `enableEffectLogs` is set)
* - Effect metrics sent to Sentry (when `enableEffectMetrics` is set)
*
* @example
* ```typescript
Expand Down
11 changes: 6 additions & 5 deletions packages/effect/src/server/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
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 { init } from './sdk';

Expand All @@ -8,15 +9,15 @@ export { init } from './sdk';
/**
* Options for the Sentry Effect server layer.
*/
export type EffectServerLayerOptions = NodeOptions;
export type EffectServerLayerOptions = NodeOptions & EffectLayerBaseOptions;

/**
* 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 `enableLogs` is set)
* - Effect metrics sent to Sentry (when `enableMetrics` is set)
* - Effect logs forwarded to Sentry (when `enableEffectLogs` is set)
* - Effect metrics sent to Sentry (when `enableEffectMetrics` is set)
*
* @example
* ```typescript
Expand All @@ -28,8 +29,8 @@ export type EffectServerLayerOptions = NodeOptions;
* const MainLive = HttpLive.pipe(
* Layer.provide(Sentry.effectLayer({
* dsn: '__DSN__',
* enableLogs: true,
* enableMetrics: true,
* enableEffectLogs: true,
Copy link
Member

Choose a reason for hiding this comment

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

m: We should probably keep enableLogs in the example too, otherwise you wouldn't get logs just with this right?

Copy link
Member Author

Choose a reason for hiding this comment

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

That is true, let's keep that in.

* enableEffectMetrics: true,
* })),
* );
*
Expand Down
14 changes: 8 additions & 6 deletions packages/effect/src/utils/buildEffectLayer.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Client } from '@sentry/core';
import type * as EffectLayer from 'effect/Layer';
import { empty as emptyLayer, provideMerge } from 'effect/Layer';
import { defaultLogger, replace as replaceLogger } from 'effect/Logger';
Expand All @@ -6,8 +7,8 @@ import { SentryEffectMetricsLayer } from '../metrics';
import { SentryEffectTracerLayer } from '../tracer';

export interface EffectLayerBaseOptions {
enableLogs?: boolean;
enableMetrics?: boolean;
enableEffectLogs?: boolean;
enableEffectMetrics?: boolean;
}

/**
Expand All @@ -19,21 +20,22 @@ export interface EffectLayerBaseOptions {
*/
export function buildEffectLayer<T extends EffectLayerBaseOptions>(
options: T,
client: unknown,
client: Client | undefined,
): EffectLayer.Layer<never, never, never> {
if (!client) {
return emptyLayer;
}

const { enableLogs = false, enableMetrics = true } = options;
const clientOptions = client.getOptions();
const { enableEffectLogs = false, enableEffectMetrics = false } = options;
let layer: EffectLayer.Layer<never, never, never> = SentryEffectTracerLayer;

if (enableLogs) {
if (enableEffectLogs && clientOptions.enableLogs) {
const effectLogger = replaceLogger(defaultLogger, SentryEffectLogger);
layer = layer.pipe(provideMerge(effectLogger));
}

if (enableMetrics) {
if (enableEffectMetrics && clientOptions.enableMetrics) {
layer = layer.pipe(provideMerge(SentryEffectMetricsLayer));
}

Expand Down
76 changes: 51 additions & 25 deletions packages/effect/test/buildEffectLayer.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
import { describe, expect, it, vi } from '@effect/vitest';
import * as sentryCore from '@sentry/core';
import { logger as sentryLogger } from '@sentry/core';
import type { NodeOptions } from '@sentry/node-core';
import { Effect, Layer } from 'effect';
import { empty as emptyLayer } from 'effect/Layer';
import { init } from '../src/index.server';
import { buildEffectLayer } from '../src/utils/buildEffectLayer';

function getMockTransport() {
return () => ({
send: vi.fn().mockResolvedValue({}),
flush: vi.fn().mockResolvedValue(true),
});
}

function createClient(options: NodeOptions = {}) {
return init({
dsn: 'https://username@domain/123',
transport: getMockTransport(),
...options,
});
}

describe('buildEffectLayer', () => {
describe('when client is falsy', () => {
it('returns empty layer when client is null', () => {
const layer = buildEffectLayer({}, null);
const layer = buildEffectLayer({}, undefined);

expect(layer).toBeDefined();
expect(Layer.isLayer(layer)).toBe(true);
Expand All @@ -25,45 +42,49 @@ describe('buildEffectLayer', () => {
});

describe('when client is truthy', () => {
const mockClient = { mock: true };

it('returns a valid layer with default options', () => {
const layer = buildEffectLayer({}, mockClient);
const client = createClient();
const layer = buildEffectLayer({}, client);

expect(layer).toBeDefined();
expect(Layer.isLayer(layer)).toBe(true);
});

it('returns a valid layer with enableLogs: false', () => {
const layer = buildEffectLayer({ enableLogs: false }, mockClient);
it('returns a valid layer with enableEffectLogs: false', () => {
const client = createClient();
const layer = buildEffectLayer({ enableEffectLogs: false }, client);

expect(layer).toBeDefined();
expect(Layer.isLayer(layer)).toBe(true);
});

it('returns a valid layer with enableLogs: true', () => {
const layer = buildEffectLayer({ enableLogs: true }, mockClient);
it('returns a valid layer with enableEffectLogs: true', () => {
const client = createClient();
const layer = buildEffectLayer({ enableEffectLogs: true }, client);

expect(layer).toBeDefined();
expect(Layer.isLayer(layer)).toBe(true);
});

it('returns a valid layer with enableMetrics: false', () => {
const layer = buildEffectLayer({ enableMetrics: false }, mockClient);
it('returns a valid layer with enableEffectMetrics: false', () => {
const client = createClient();
const layer = buildEffectLayer({ enableEffectMetrics: false }, client);

expect(layer).toBeDefined();
expect(Layer.isLayer(layer)).toBe(true);
});

it('returns a valid layer with enableMetrics: true', () => {
const layer = buildEffectLayer({ enableMetrics: true }, mockClient);
it('returns a valid layer with enableEffectMetrics: true', () => {
const client = createClient();
const layer = buildEffectLayer({ enableEffectMetrics: true }, client);

expect(layer).toBeDefined();
expect(Layer.isLayer(layer)).toBe(true);
});

it('returns a valid layer with all features enabled', () => {
const layer = buildEffectLayer({ enableLogs: true, enableMetrics: true }, mockClient);
const client = createClient();
const layer = buildEffectLayer({ enableEffectLogs: true, enableEffectMetrics: true }, client);

expect(layer).toBeDefined();
expect(Layer.isLayer(layer)).toBe(true);
Expand All @@ -73,7 +94,7 @@ describe('buildEffectLayer', () => {
Effect.gen(function* () {
const result = yield* Effect.succeed('test-result');
expect(result).toBe('test-result');
}).pipe(Effect.provide(buildEffectLayer({}, mockClient))),
}).pipe(Effect.provide(buildEffectLayer({}, createClient()))),
);

it.effect('layer with logs enabled routes Effect logs to Sentry logger', () =>
Expand All @@ -82,12 +103,13 @@ describe('buildEffectLayer', () => {
yield* Effect.log('test log message');
expect(infoSpy).toHaveBeenCalledWith('test log message');
infoSpy.mockRestore();
}).pipe(Effect.provide(buildEffectLayer({ enableLogs: true }, mockClient))),
}).pipe(Effect.provide(buildEffectLayer({ enableEffectLogs: true }, createClient({ enableLogs: true })))),
);

it('returns different layer when enableMetrics is true vs false', () => {
const layerWithMetrics = buildEffectLayer({ enableMetrics: true }, mockClient);
const layerWithoutMetrics = buildEffectLayer({ enableMetrics: false }, mockClient);
it('returns different layer when enableEffectMetrics is true vs false', () => {
const client = createClient();
const layerWithMetrics = buildEffectLayer({ enableEffectMetrics: true }, client);
const layerWithoutMetrics = buildEffectLayer({ enableEffectMetrics: false }, client);

expect(layerWithMetrics).not.toBe(layerWithoutMetrics);
});
Expand All @@ -96,7 +118,11 @@ describe('buildEffectLayer', () => {
Effect.gen(function* () {
const result = yield* Effect.succeed('all-features');
expect(result).toBe('all-features');
}).pipe(Effect.provide(buildEffectLayer({ enableLogs: true, enableMetrics: true }, mockClient))),
}).pipe(
Effect.provide(
buildEffectLayer({ enableEffectLogs: true, enableEffectMetrics: true }, createClient({ enableLogs: true })),
),
),
);

it.effect('layer enables tracing for Effect spans via Sentry tracer', () =>
Expand All @@ -110,22 +136,22 @@ describe('buildEffectLayer', () => {
}),
);
startInactiveSpanSpy.mockRestore();
}).pipe(Effect.provide(buildEffectLayer({}, mockClient))),
}).pipe(Effect.provide(buildEffectLayer({}, createClient()))),
);
});

describe('with additional options', () => {
const mockClient = { mock: true };
const client = createClient({ enableLogs: true });

it('accepts options with additional properties', () => {
const layer = buildEffectLayer(
{
enableLogs: true,
enableMetrics: true,
enableEffectLogs: true,
enableEffectMetrics: true,
dsn: 'https://test@sentry.io/123',
debug: true,
} as { enableLogs?: boolean; enableMetrics?: boolean; dsn?: string; debug?: boolean },
mockClient,
} as { enableEffectLogs?: boolean; enableEffectMetrics?: boolean; dsn?: string; debug?: boolean },
client,
);

expect(layer).toBeDefined();
Expand Down
6 changes: 3 additions & 3 deletions packages/effect/test/layer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ describe.each([
const layer = effectLayer({
dsn: TEST_DSN,
transport: getMockTransport(),
enableLogs: true,
enableEffectLogs: true,
});

expect(layer).toBeDefined();
Expand All @@ -73,8 +73,8 @@ describe.each([
const layer = effectLayer({
dsn: TEST_DSN,
transport: getMockTransport(),
enableLogs: true,
enableMetrics: true,
enableEffectLogs: true,
enableEffectMetrics: true,
});

expect(layer).toBeDefined();
Expand Down
Loading