Skip to content

Abhi replay id in logs #16428

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

Closed
wants to merge 3 commits into from
Closed
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
22 changes: 18 additions & 4 deletions packages/replay-internal/src/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,20 @@ export class Replay implements Integration {
/** Setup the integration. */
private _setup(client: Client): void {
// Client is not available in constructor, so we need to wait until setupOnce
const finalOptions = loadReplayOptionsFromClient(this._initialOptions, client);
const clientOptions = client.getOptions();
const finalOptions = loadReplayOptionsFromClient(this._initialOptions, clientOptions as BrowserClientReplayOptions);

if (clientOptions._experiments?.enableLogs) {
client.on('beforeCaptureLog', log => {
const replayId = this.getReplayId();
if (replayId) {
log.attributes = {
...log.attributes,
'replay.id': replayId,
};
}
});
}

this._replay = new ReplayContainer({
options: finalOptions,
Expand Down Expand Up @@ -350,9 +363,10 @@ export class Replay implements Integration {
}

/** Parse Replay-related options from SDK options */
function loadReplayOptionsFromClient(initialOptions: InitialReplayPluginOptions, client: Client): ReplayPluginOptions {
const opt = client.getOptions() as BrowserClientReplayOptions;

function loadReplayOptionsFromClient(
initialOptions: InitialReplayPluginOptions,
opt: BrowserClientReplayOptions,
): ReplayPluginOptions {
const finalOptions: ReplayPluginOptions = {
sessionSampleRate: 0,
errorSampleRate: 0,
Expand Down
164 changes: 164 additions & 0 deletions packages/replay-internal/test/integration/logAttributes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/**
* @vitest-environment jsdom
*/

import type { Log } from '@sentry/core';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { mockSdk } from '../index';

describe('Integration | logAttributes', () => {
beforeEach(() => {
vi.resetModules();
});

afterEach(() => {
vi.clearAllMocks();
});

describe('log attributes with replay ID', () => {
it('adds replay ID to log attributes when replay is enabled and has a session', async () => {
const { replay, integration, client } = await mockSdk({
replayOptions: {},
sentryOptions: {
_experiments: { enableLogs: true },
replaysSessionSampleRate: 1.0,
},
});

// Start replay to create a session
replay.start();
expect(replay.isEnabled()).toBe(true);
expect(integration.getReplayId()).toBeDefined();

const replayId = integration.getReplayId();

// Mock the client and emit a log event
const log: Log = {
level: 'info',
message: 'test log message',
attributes: { 'existing.attr': 'value' },
};

client.emit('beforeCaptureLog', log);

expect(log.attributes).toEqual({
'existing.attr': 'value',
'replay.id': replayId,
});
});

it('preserves existing log attributes when adding replay ID', async () => {
const { replay, integration, client } = await mockSdk({
replayOptions: {},
sentryOptions: {
_experiments: { enableLogs: true },
replaysSessionSampleRate: 1.0,
},
});

// Start replay to create a session
replay.start();
const replayId = integration.getReplayId();

const log: Log = {
level: 'error',
message: 'error log message',
attributes: {
'user.id': 'test-user',
'request.id': 'req-123',
module: 'auth',
},
};

client.emit('beforeCaptureLog', log);

expect(log.attributes).toEqual({
'user.id': 'test-user',
'request.id': 'req-123',
module: 'auth',
'replay.id': replayId,
});
});

it('does not add replay ID when replay is not enabled', async () => {
const { replay, client } = await mockSdk({
replayOptions: {},
sentryOptions: {
_experiments: { enableLogs: true },
replaysSessionSampleRate: 0.0, // Disabled
},
});

// Replay should not be enabled
expect(replay.isEnabled()).toBe(false);

const log: Log = {
level: 'info',
message: 'test log message',
attributes: { 'existing.attr': 'value' },
};

client.emit('beforeCaptureLog', log);

// Replay ID should not be added
expect(log.attributes).toEqual({
'existing.attr': 'value',
});
});

it('does not register log handler when enableLogs experiment is disabled', async () => {
const { replay, client } = await mockSdk({
replayOptions: {},
sentryOptions: {
// enableLogs experiment is not set (defaults to false)
replaysSessionSampleRate: 1.0,
},
});

replay.start();

const log: Log = {
level: 'info',
message: 'test log message',
attributes: { 'existing.attr': 'value' },
};

client.emit('beforeCaptureLog', log);

// Replay ID should not be added since the handler wasn't registered
expect(log.attributes).toEqual({
'existing.attr': 'value',
});
});

it('works with buffer mode replay', async () => {
const { replay, integration, client } = await mockSdk({
replayOptions: {},
sentryOptions: {
_experiments: { enableLogs: true },
replaysSessionSampleRate: 0.0,
replaysOnErrorSampleRate: 1.0, // Buffer mode
},
});

// Start buffering mode
replay.startBuffering();
expect(integration.getRecordingMode()).toBe('buffer');

const replayId = integration.getReplayId();
expect(replayId).toBeDefined();

const log: Log = {
level: 'warn',
message: 'warning message',
attributes: {},
};

client.emit('beforeCaptureLog', log);

expect(log.attributes).toEqual({
'replay.id': replayId,
});
});
});
});
3 changes: 2 additions & 1 deletion packages/replay-internal/test/mocks/mockSdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class MockTransport implements Transport {
export async function mockSdk({ replayOptions, sentryOptions, autoStart = true }: MockSdkParams = {}): Promise<{
replay: ReplayContainer;
integration: ReplayIntegration;
client: ReturnType<typeof init>;
}> {
const { Replay } = await import('../../src/integration');

Expand Down Expand Up @@ -92,5 +93,5 @@ export async function mockSdk({ replayOptions, sentryOptions, autoStart = true }

const replay = replayIntegration['_replay']!;

return { replay, integration: replayIntegration };
return { replay, integration: replayIntegration, client };
}
Loading