From fe1554fefc235fa937f899b196dd8813c283eb8e Mon Sep 17 00:00:00 2001 From: Peter Savchenko Date: Tue, 23 Dec 2025 23:40:34 +0300 Subject: [PATCH 1/4] add logs to sentry worker --- workers/sentry/src/index.ts | 54 ++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/workers/sentry/src/index.ts b/workers/sentry/src/index.ts index 42c5bdfa..0af9bef5 100644 --- a/workers/sentry/src/index.ts +++ b/workers/sentry/src/index.ts @@ -39,9 +39,24 @@ export default class SentryEventWorker extends Worker { const [headers, items] = envelope; + if (items.length === 0) { + this.logger.warn('Received envelope with no items'); + return; + } + + let processedCount = 0; + let skippedCount = 0; + for (const item of items) { - await this.handleEnvelopeItem(headers, item, event.projectId); + const result = await this.handleEnvelopeItem(headers, item, event.projectId); + if (result === 'processed') { + processedCount++; + } else if (result === 'skipped') { + skippedCount++; + } } + + this.logger.verbose(`Processed ${processedCount} events, skipped ${skippedCount} non-event items from envelope`); } catch (error) { this.logger.error(`Error handling Sentry event task:`, error); this.logger.info('👇 Here is the problematic event:'); @@ -99,8 +114,9 @@ export default class SentryEventWorker extends Worker { * @param envelopeHeaders - The whole envelope headers * @param item - Sentry item * @param projectId - Sentry project ID + * @returns 'processed' if event was sent, 'skipped' if non-event item, throws error on failure */ - private async handleEnvelopeItem(envelopeHeaders: Envelope[0], item: EnvelopeItem, projectId: string): Promise { + private async handleEnvelopeItem(envelopeHeaders: Envelope[0], item: EnvelopeItem, projectId: string): Promise<'processed' | 'skipped'> { try { const [itemHeader, itemPayload] = item; @@ -112,7 +128,8 @@ export default class SentryEventWorker extends Worker { * Skip non-event items */ if (itemHeader.type !== 'event') { - return; + this.logger.verbose(`Skipping non-event item of type: ${itemHeader.type}`); + return 'skipped'; } const payloadHasSDK = typeof itemPayload === 'object' && 'sdk' in itemPayload; @@ -121,18 +138,37 @@ export default class SentryEventWorker extends Worker { */ const sentryJsSDK = ['browser', 'react', 'vue', 'angular', 'capacirtor', 'electron']; - const isJsSDK = payloadHasSDK && sentryJsSDK.includes(itemPayload.sdk.name); + /** + * Safely check if SDK name exists and is in the list + */ + const sdkName = payloadHasSDK && itemPayload.sdk && typeof itemPayload.sdk === 'object' && 'name' in itemPayload.sdk + ? itemPayload.sdk.name + : undefined; + + const isJsSDK = sdkName !== undefined && sentryJsSDK.includes(sdkName); const hawkEvent = this.transformToHawkFormat(envelopeHeaders as EventEnvelope[0], item as EventItem, projectId, isJsSDK); /** - * If we have release attached to the event + * Send task to appropriate worker and check if it was successfully queued */ - if (isJsSDK) { - await this.addTask(WorkerNames.JAVASCRIPT, hawkEvent as JavaScriptEventWorkerTask); - } else { - await this.addTask(WorkerNames.DEFAULT, hawkEvent as DefaultEventWorkerTask); + const workerName = isJsSDK ? WorkerNames.JAVASCRIPT : WorkerNames.DEFAULT; + const taskSent = await this.addTask(workerName, hawkEvent as JavaScriptEventWorkerTask | DefaultEventWorkerTask); + + if (!taskSent) { + /** + * If addTask returns false, the message was not queued (queue full or channel closed) + * This is a critical error that should be logged and thrown + */ + const error = new Error(`Failed to queue event to ${workerName} worker. Queue may be full or channel closed.`); + this.logger.error(error.message); + this.logger.info('👇 Here is the event that failed to queue:'); + this.logger.json(hawkEvent); + throw error; } + + this.logger.verbose(`Successfully queued event to ${workerName} worker`); + return 'processed'; } catch (error) { this.logger.error('Error handling envelope item:', error); this.logger.info('👇 Here is the problematic item:'); From 0315a2b24a724341679ea099838679a1f46d6be7 Mon Sep 17 00:00:00 2001 From: Peter Savchenko Date: Wed, 24 Dec 2025 00:17:27 +0300 Subject: [PATCH 2/4] tune logs --- lib/worker.ts | 4 +++- workers/sentry/src/index.ts | 44 ++++++++++++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/lib/worker.ts b/lib/worker.ts index f606ef03..5c535b8a 100644 --- a/lib/worker.ts +++ b/lib/worker.ts @@ -330,7 +330,9 @@ export abstract class Worker { console.log('handle error'); console.log(e); - HawkCatcher.send(e, context); + HawkCatcher.send(e, Object.assign(context, { + worker: this.type, + })); switch (e.constructor) { case CriticalError: diff --git a/workers/sentry/src/index.ts b/workers/sentry/src/index.ts index 0af9bef5..6a641d6a 100644 --- a/workers/sentry/src/index.ts +++ b/workers/sentry/src/index.ts @@ -140,12 +140,30 @@ export default class SentryEventWorker extends Worker { /** * Safely check if SDK name exists and is in the list + * SDK name can be either a simple name like "react" or a full name like "sentry.javascript.react" */ const sdkName = payloadHasSDK && itemPayload.sdk && typeof itemPayload.sdk === 'object' && 'name' in itemPayload.sdk ? itemPayload.sdk.name : undefined; - const isJsSDK = sdkName !== undefined && sentryJsSDK.includes(sdkName); + /** + * Check if SDK is a JavaScript-related SDK + * Supports both simple names (e.g., "react") and full names (e.g., "sentry.javascript.react") + */ + const isJsSDK = sdkName !== undefined && typeof sdkName === 'string' && ( + /** + * Exact match for simple SDK names (e.g., "react", "browser") + */ + sentryJsSDK.includes(sdkName) || + /** + * Check if SDK name contains one of the JS SDK names + * Examples: + * - "sentry.javascript.react" matches "react" + * - "sentry.javascript.browser" matches "browser" + * - "@sentry/react" matches "react" + */ + sentryJsSDK.some((jsSDK) => sdkName.includes(jsSDK)) + ); const hawkEvent = this.transformToHawkFormat(envelopeHeaders as EventEnvelope[0], item as EventItem, projectId, isJsSDK); @@ -158,7 +176,6 @@ export default class SentryEventWorker extends Worker { if (!taskSent) { /** * If addTask returns false, the message was not queued (queue full or channel closed) - * This is a critical error that should be logged and thrown */ const error = new Error(`Failed to queue event to ${workerName} worker. Queue may be full or channel closed.`); this.logger.error(error.message); @@ -167,7 +184,6 @@ export default class SentryEventWorker extends Worker { throw error; } - this.logger.verbose(`Successfully queued event to ${workerName} worker`); return 'processed'; } catch (error) { this.logger.error('Error handling envelope item:', error); @@ -198,7 +214,14 @@ export default class SentryEventWorker extends Worker { * convert sent_at from ISO 8601 to Unix timestamp */ const msInSecond = 1000; - const sentAtUnix = Math.floor(new Date(sent_at).getTime() / msInSecond); + const sentAtDate = new Date(sent_at); + const sentAtTime = sentAtDate.getTime(); + + if (isNaN(sentAtTime)) { + throw new Error(`Invalid sent_at timestamp: ${sent_at}`); + } + + const sentAtUnix = Math.floor(sentAtTime / msInSecond); /* eslint-enable @typescript-eslint/naming-convention */ // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars @@ -211,9 +234,18 @@ export default class SentryEventWorker extends Worker { * We need to decode it to JSON */ if (eventPayload instanceof Uint8Array) { - const textDecoder = new TextDecoder(); + try { + const textDecoder = new TextDecoder(); + const decoded = textDecoder.decode(eventPayload as Uint8Array); - eventPayload = JSON.parse(textDecoder.decode(eventPayload as Uint8Array)); + try { + eventPayload = JSON.parse(decoded); + } catch (parseError) { + throw new Error(`Failed to parse event payload JSON: ${parseError instanceof Error ? parseError.message : String(parseError)}`); + } + } catch (decodeError) { + throw new Error(`Failed to decode Uint8Array event payload: ${decodeError instanceof Error ? decodeError.message : String(decodeError)}`); + } } const title = composeTitle(eventPayload); From c07a7b495eb27f6999a170662e7599a4cfaab4fa Mon Sep 17 00:00:00 2001 From: Peter Savchenko Date: Wed, 24 Dec 2025 00:20:29 +0300 Subject: [PATCH 3/4] Update index.ts --- workers/sentry/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workers/sentry/src/index.ts b/workers/sentry/src/index.ts index 6a641d6a..b0594094 100644 --- a/workers/sentry/src/index.ts +++ b/workers/sentry/src/index.ts @@ -128,7 +128,7 @@ export default class SentryEventWorker extends Worker { * Skip non-event items */ if (itemHeader.type !== 'event') { - this.logger.verbose(`Skipping non-event item of type: ${itemHeader.type}`); + this.logger.info(`Skipping non-event item of type: ${itemHeader.type}`); return 'skipped'; } const payloadHasSDK = typeof itemPayload === 'object' && 'sdk' in itemPayload; From a186d0c8d904e5faf3f9d7a4fbc847f07d8a1e44 Mon Sep 17 00:00:00 2001 From: Peter Savchenko Date: Wed, 24 Dec 2025 00:24:28 +0300 Subject: [PATCH 4/4] fix tests --- jest.setup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jest.setup.js b/jest.setup.js index ba2fc239..280382c3 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -10,7 +10,7 @@ const mockedAmqpChannel = { close: jest.fn(), assertQueue: jest.fn(), prefetch: jest.fn(), - sendToQueue: jest.fn(), + sendToQueue: jest.fn().mockReturnValue(true), on: jest.fn(), consume: jest.fn().mockReturnValue('mockedTag'), };