Skip to content

Commit e8bf60d

Browse files
committed
fix sentry replay skipping
1 parent 8844840 commit e8bf60d

File tree

2 files changed

+109
-6
lines changed

2 files changed

+109
-6
lines changed

workers/sentry/src/index.ts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,12 @@ export default class SentryEventWorker extends Worker {
6767

6868
/**
6969
* Filter out binary items that crash parseEnvelope
70+
* Also filters out all Sentry Replay events (replay_event and replay_recording)
7071
*/
7172
private filterOutBinaryItems(rawEvent: string): string {
7273
const lines = rawEvent.split('\n');
7374
const filteredLines = [];
75+
let isInReplayBlock = false;
7476

7577
for (let i = 0; i < lines.length; i++) {
7678
const line = lines[i];
@@ -90,17 +92,42 @@ export default class SentryEventWorker extends Worker {
9092
// Try to parse as JSON to check if it's a header
9193
const parsed = JSON.parse(line);
9294

93-
// If it's a replay header, skip this line and the next one (payload)
95+
// Check if this is a replay event type
9496
if (parsed.type === 'replay_recording' || parsed.type === 'replay_event') {
95-
// Skip the next line too (which would be the payload)
96-
i++;
97+
// Mark that we're in a replay block and skip this line
98+
isInReplayBlock = true;
9799
continue;
98100
}
99101

100-
// Keep valid headers and other JSON data
101-
filteredLines.push(line);
102+
// If we're in a replay block, check if this is still part of it
103+
if (isInReplayBlock) {
104+
// Check if this line is part of replay data (segment_id, length, etc.)
105+
if ('segment_id' in parsed || ('length' in parsed && parsed.type !== 'event') || 'replay_id' in parsed) {
106+
// Still in replay block, skip this line
107+
continue;
108+
}
109+
110+
// If it's a new envelope item (like event), we've exited the replay block
111+
if (parsed.type === 'event' || parsed.type === 'transaction' || parsed.type === 'session') {
112+
isInReplayBlock = false;
113+
} else {
114+
// Unknown type, assume we're still in replay block
115+
continue;
116+
}
117+
}
118+
119+
// Keep valid headers and other JSON data (not in replay block)
120+
if (!isInReplayBlock) {
121+
filteredLines.push(line);
122+
}
102123
} catch {
103-
// If line doesn't parse as JSON, it might be binary data - skip it
124+
// If line doesn't parse as JSON, it might be binary data
125+
// If we're in a replay block, skip it (it's part of replay recording)
126+
if (isInReplayBlock) {
127+
continue;
128+
}
129+
130+
// If not in replay block and not JSON, it might be corrupted data - skip it
104131
continue;
105132
}
106133
}

workers/sentry/tests/index.test.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,82 @@ describe('SentryEventWorker', () => {
852852
}),
853853
});
854854
});
855+
856+
it('should ignore envelope with only replay_event and replay_recording items', async () => {
857+
/**
858+
* Test case based on real-world scenario where envelope contains only replay data
859+
* This should not crash with "Unexpected end of JSON input" error
860+
*/
861+
const envelopeLines = [
862+
// Envelope header
863+
JSON.stringify({
864+
/* eslint-disable @typescript-eslint/naming-convention */
865+
event_id: '62680958b3ab4497886375e06533d86a',
866+
sent_at: '2025-12-24T13:16:34.580Z',
867+
/* eslint-enable @typescript-eslint/naming-convention */
868+
sdk: { name: 'sentry.javascript.react', version: '10.22.0' },
869+
}),
870+
// Replay event item header - should be filtered out
871+
JSON.stringify({ type: 'replay_event' }),
872+
// Replay event item payload (large JSON) - should be filtered out
873+
JSON.stringify({
874+
/* eslint-disable @typescript-eslint/naming-convention */
875+
type: 'replay_event',
876+
replay_start_timestamp: 1766582182.757,
877+
timestamp: 1766582194.579,
878+
error_ids: [],
879+
trace_ids: [],
880+
urls: ['https://my.huntio.ru/applicants', 'https://my.huntio.ru/applicants/1270067'],
881+
replay_id: '62680958b3ab4497886375e06533d86a',
882+
segment_id: 1,
883+
replay_type: 'session',
884+
request: {
885+
url: 'https://my.huntio.ru/applicants/1270067',
886+
headers: {
887+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
888+
},
889+
},
890+
event_id: '62680958b3ab4497886375e06533d86a',
891+
environment: 'production',
892+
release: '1.0.7',
893+
sdk: {
894+
integrations: ['InboundFilters', 'FunctionToString', 'BrowserApiErrors', 'Breadcrumbs'],
895+
name: 'sentry.javascript.react',
896+
version: '10.22.0',
897+
settings: { infer_ip: 'auto' },
898+
},
899+
user: {
900+
id: 487,
901+
902+
username: 'Прохорова Наталья',
903+
},
904+
contexts: { react: { version: '19.1.0' } },
905+
transaction: '/applicants/1270067',
906+
platform: 'javascript',
907+
/* eslint-enable @typescript-eslint/naming-convention */
908+
}),
909+
// Replay recording item header - should be filtered out
910+
JSON.stringify({ type: 'replay_recording', length: 16385 }),
911+
// Segment ID - should be filtered out
912+
JSON.stringify({ segment_id: 1 }),
913+
// Binary data (simulated) - should be filtered out
914+
'xnFWy@v$xAlJ=&fS~¾˶IJ<Dڒ%8yX]]ˣ·9V|JGd!+%fF',
915+
];
916+
917+
const envelopeString = envelopeLines.join('\n');
918+
919+
// Should not throw "Unexpected end of JSON input" error
920+
await worker.handle({
921+
payload: {
922+
envelope: b64encode(envelopeString),
923+
},
924+
projectId: '123',
925+
catcherType: 'external/sentry',
926+
});
927+
928+
// Should not send any tasks since all items were replay-related and filtered out
929+
expect(mockedAmqpChannel.sendToQueue).not.toHaveBeenCalled();
930+
});
855931
});
856932

857933
describe('envelope parsing', () => {

0 commit comments

Comments
 (0)