From 1c529e9d186e6e0b169ca224d506146577c75651 Mon Sep 17 00:00:00 2001 From: Kev Date: Thu, 29 May 2025 12:44:53 -0400 Subject: [PATCH] feat(ourlogs): Add replay id to logs when available Logs can't lookup replays via traces since traces may be sampled whereas logs are (currently) unsampled, with their own traceids. --- packages/core/src/logs/exports.ts | 8 ++++ packages/core/test/lib/logs/exports.test.ts | 45 +++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/packages/core/src/logs/exports.ts b/packages/core/src/logs/exports.ts index 9a738d503a80..6a356af53678 100644 --- a/packages/core/src/logs/exports.ts +++ b/packages/core/src/logs/exports.ts @@ -10,6 +10,7 @@ import { timestampInSeconds } from '../utils-hoist/time'; import { GLOBAL_OBJ } from '../utils-hoist/worldwide'; import { SEVERITY_TEXT_TO_SEVERITY_NUMBER } from './constants'; import { createLogEnvelope } from './envelope'; +import { Integration } from '../types-hoist/integration'; const MAX_LOG_BUFFER_SIZE = 100; @@ -111,6 +112,9 @@ export function _INTERNAL_captureLog( return; } + + const replay = client.getIntegrationByName string }>('Replay'); + const replayId = replay?.getReplayId(); const [, traceContext] = _getTraceInfoFromScope(client, scope); const processedLogAttributes = { @@ -125,6 +129,10 @@ export function _INTERNAL_captureLog( processedLogAttributes['sentry.environment'] = environment; } + if (replayId) { + processedLogAttributes['sentry.replay_id'] = replayId; + } + const { sdk } = client.getSdkMetadata() ?? {}; if (sdk) { processedLogAttributes['sentry.sdk.name'] = sdk.name; diff --git a/packages/core/test/lib/logs/exports.test.ts b/packages/core/test/lib/logs/exports.test.ts index 1ae570bc5968..798804e9841a 100644 --- a/packages/core/test/lib/logs/exports.test.ts +++ b/packages/core/test/lib/logs/exports.test.ts @@ -9,6 +9,7 @@ import { import type { Log } from '../../../src/types-hoist/log'; import * as loggerModule from '../../../src/utils-hoist/logger'; import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; +import { Replay } from '../../../../replay-internal/src/integration'; const PUBLIC_DSN = 'https://username@domain/123'; @@ -155,6 +156,50 @@ describe('_INTERNAL_captureLog', () => { }); }); + it('includes replay id in log attributes when available', async () => { + + let _initialized = false; + class TestReplayIntegration extends Replay { + protected get _isInitialized(): boolean { + return _initialized; + } + protected set _isInitialized(value: boolean) { + _initialized = value; + } + + public afterAllSetup(): void { + // do nothing, we need to manually initialize this + } + } + const replayIntegration = new TestReplayIntegration(); + const options = getDefaultTestClientOptions({ + dsn: PUBLIC_DSN, + _experiments: { enableLogs: true }, + integrations: [replayIntegration], + }); + + const client = new TestClient(options); + + replayIntegration['_setup'](client as any); + replayIntegration['_initialize'](client as any); + + const scope = new Scope(); + scope.setPropagationContext({ + traceId: '3d9355f71e9c444b81161599adac6e29', + sampleRand: 1, + }); + + _INTERNAL_captureLog({ level: 'info', message: 'test log with replay id' }, client, scope); + + const logAttributes = _INTERNAL_getLogBuffer(client)?.[0]?.attributes; + expect(logAttributes).toEqual({ + 'sentry.replay_id': { + value: '123', + type: 'string', + }, + }); + }); + it('includes SDK metadata in log attributes when available', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN,