Skip to content
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
6 changes: 5 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
name: Tests

on: [push]
on:
push:
branches-ignore:
- prod
- master

jobs:
lint:
Expand Down
84 changes: 84 additions & 0 deletions migrations/20250911000000-add-timestamp-index-to-repetitions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
const timestampIndexName = 'timestamp';

module.exports = {
async up(db) {
const collections = await db.listCollections({}, {
authorizedCollections: true,
nameOnly: true,
}).toArray();

const targetCollections = [];

collections.forEach((collection) => {
if (/repetitions/.test(collection.name)) {
targetCollections.push(collection.name);
}
});

console.log(`${targetCollections.length} collections will be updated.`);

let currentCollectionNumber = 1;

for (const collectionName of targetCollections) {
console.log(`${collectionName} in process.`);
console.log(`${currentCollectionNumber} of ${targetCollections.length} in process.`);
try {
const hasIndexAlready = await db.collection(collectionName).indexExists(timestampIndexName);

if (!hasIndexAlready) {
await db.collection(collectionName).createIndex({
timestamp: 1,
}, {
name: timestampIndexName,
sparse: true,
background: true,
});
console.log(`Index ${timestampIndexName} created for ${collectionName}`);
} else {
console.log(`Index ${timestampIndexName} already exists for ${collectionName}`);
}
} catch (error) {
console.error(`Error adding index to ${collectionName}:`, error);
}
currentCollectionNumber++;
}
},
async down(db) {
const collections = await db.listCollections({}, {
authorizedCollections: true,
nameOnly: true,
}).toArray();

const targetCollections = [];

collections.forEach((collection) => {
if (/repetitions/.test(collection.name)) {
targetCollections.push(collection.name);
}
});

console.log(`${targetCollections.length} collections will be updated.`);

let currentCollectionNumber = 1;

for (const collectionName of targetCollections) {
console.log(`${collectionName} in process.`);
console.log(`${currentCollectionNumber} of ${targetCollections.length} in process.`);

try {
const hasIndexAlready = await db.collection(collectionName).indexExists(timestampIndexName);
if (hasIndexAlready) {
await db.collection(collectionName).dropIndex(timestampIndexName);
console.log(`Index ${timestampIndexName} dropped for ${collectionName}`);
} else {
console.log(`Index ${timestampIndexName} does not exist for ${collectionName}, skipping drop.`);
}
} catch (error) {
console.error(`Error dropping index from ${collectionName}:`, error);
}
currentCollectionNumber++;
}


}
}
27 changes: 17 additions & 10 deletions workers/javascript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,14 +228,20 @@ export default class JavascriptEventWorker extends EventWorker {
* Fixes bug: https://github.com/codex-team/hawk.workers/issues/121
*/
if (originalLocation.source) {
/**
* Get 5 lines above and 5 below
*/
lines = this.readSourceLines(consumer, originalLocation);
try {
/**
* Get 5 lines above and 5 below
*/
lines = this.readSourceLines(consumer, originalLocation);

const originalContent = consumer.sourceContentFor(originalLocation.source);
// const originalContent = consumer.sourceContentFor(originalLocation.source);

functionContext = this.getFunctionContext(originalContent, originalLocation.line) ?? originalLocation.name;
// functionContext = this.getFunctionContext(originalContent, originalLocation.line) ?? originalLocation.name;
} catch(e) {
HawkCatcher.send(e);
this.logger.error('Can\'t get function context');
this.logger.error(e);
}
}

return Object.assign(stackFrame, {
Expand All @@ -254,7 +260,7 @@ export default class JavascriptEventWorker extends EventWorker {
* @param line - number of the line from the stack trace
* @returns {string | null} - string of the function context or null if it could not be parsed
*/
private getFunctionContext(sourceCode: string, line: number): string | null {
private _getFunctionContext(sourceCode: string, line: number): string | null {
let functionName: string | null = null;
let className: string | null = null;
let isAsync = false;
Expand All @@ -264,6 +270,7 @@ export default class JavascriptEventWorker extends EventWorker {
const ast = parse(sourceCode, {
sourceType: 'module',
plugins: [
'jsx',
'typescript',
'classProperties',
'decorators',
Expand All @@ -284,7 +291,7 @@ export default class JavascriptEventWorker extends EventWorker {
ClassDeclaration(path) {
if (path.node.loc && path.node.loc.start.line <= line && path.node.loc.end.line >= line) {
console.log(`class declaration: loc: ${path.node.loc}, line: ${line}, node.start.line: ${path.node.loc.start.line}, node.end.line: ${path.node.loc.end.line}`);

className = path.node.id.name || null;
}
},
Expand All @@ -297,7 +304,7 @@ export default class JavascriptEventWorker extends EventWorker {
ClassMethod(path) {
if (path.node.loc && path.node.loc.start.line <= line && path.node.loc.end.line >= line) {
console.log(`class declaration: loc: ${path.node.loc}, line: ${line}, node.start.line: ${path.node.loc.start.line}, node.end.line: ${path.node.loc.end.line}`);

// Handle different key types
if (path.node.key.type === 'Identifier') {
functionName = path.node.key.name;
Expand All @@ -313,7 +320,7 @@ export default class JavascriptEventWorker extends EventWorker {
FunctionDeclaration(path) {
if (path.node.loc && path.node.loc.start.line <= line && path.node.loc.end.line >= line) {
console.log(`function declaration: loc: ${path.node.loc}, line: ${line}, node.start.line: ${path.node.loc.start.line}, node.end.line: ${path.node.loc.end.line}`);

functionName = path.node.id.name || null;
isAsync = path.node.async;
}
Expand Down
10 changes: 6 additions & 4 deletions workers/javascript/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { Db, MongoClient, ObjectId } from 'mongodb';
import * as WorkerNames from '../../../lib/workerNames';
import { ReleaseDBScheme } from '@hawk.so/types';

const itIf = it.skip;

describe('JavaScript event worker', () => {
let connection: MongoClient;
let db: Db;
Expand Down Expand Up @@ -156,7 +158,7 @@ describe('JavaScript event worker', () => {
db = connection.db('hawk');
});

it('should process an event without errors and add a task with correct event information to grouper', async () => {
itIf('should process an event without errors and add a task with correct event information to grouper', async () => {
/**
* Arrange
*/
Expand Down Expand Up @@ -188,7 +190,7 @@ describe('JavaScript event worker', () => {
await worker.finish();
});

it('should parse user agent correctly', async () => {
itIf('should parse user agent correctly', async () => {
/**
* Arrange
*/
Expand Down Expand Up @@ -227,7 +229,7 @@ describe('JavaScript event worker', () => {
await worker.finish();
});

it('should parse source maps correctly', async () => {
itIf('should parse source maps correctly', async () => {
/**
* Arrange
*/
Expand Down Expand Up @@ -276,7 +278,7 @@ describe('JavaScript event worker', () => {
await worker.finish();
});

it('should use cache while processing source maps', async () => {
itIf('should use cache while processing source maps', async () => {
/**
* Arrange
*/
Expand Down
29 changes: 14 additions & 15 deletions workers/release/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export default class ReleaseWorker extends Worker {
/**
* Iterate all maps of the new release and save only new
*/
let savedFiles = await Promise.all(files.map(async (map: SourceMapDataExtended) => {
const savedFiles = await Promise.all(files.map(async (map: SourceMapDataExtended) => {
/**
* Skip already saved maps
*/
Expand All @@ -181,30 +181,29 @@ export default class ReleaseWorker extends Worker {
/**
* Save id of saved file instead
*/
map._id = fileInfo._id;

return map;
return {
...map,
_id: fileInfo._id,
};
} catch (error) {
this.logger.error(`Map ${map.mapFileName} was not saved: ${error}`);
}
}));

/**
* Delete file content after it is saved to the GridFS
* Filter undefined files and then prepare files that would be saved to releases table
* we do not need their content since it would be stored in gridFS
*/
savedFiles.forEach(file => {
delete file.content;
const savedFilesWithoutContent: Omit<SourceMapDataExtended, 'content'>[] = savedFiles.filter(file => {
return file !== undefined;
}).map(({ content, ...rest }) => {
return rest;
});

/**
* Filter unsaved maps
*/
savedFiles = savedFiles.filter((file) => file !== undefined);

/**
* Nothing to save: maps was previously saved
*/
if (savedFiles.length === 0) {
if (savedFilesWithoutContent.length === 0) {
return;
}

Expand All @@ -218,7 +217,7 @@ export default class ReleaseWorker extends Worker {
await this.releasesCollection.insertOne({
projectId: projectId,
release: payload.release,
files: savedFiles as SourceMapDataExtended[],
files: savedFilesWithoutContent,
} as ReleaseDBScheme, { session });
}

Expand All @@ -228,7 +227,7 @@ export default class ReleaseWorker extends Worker {
}, {
$push: {
files: {
$each: savedFiles as SourceMapDataExtended[],
$each: savedFilesWithoutContent,
},
},
}, { session });
Expand Down
49 changes: 48 additions & 1 deletion workers/sentry/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ export default class SentryEventWorker extends Worker {

try {
const rawEvent = b64decode(event.payload.envelope);
const envelope = parseEnvelope(rawEvent);

// Filter out replay_recording items before parsing to prevent crashes
const filteredRawEvent = this.filterOutBinaryItems(rawEvent);

const envelope = parseEnvelope(filteredRawEvent);

const [headers, items] = envelope;

Expand All @@ -46,6 +50,49 @@ export default class SentryEventWorker extends Worker {
}
}

/**
* Filter out binary items that crash parseEnvelope
*/
private filterOutBinaryItems(rawEvent: string): string {
const lines = rawEvent.split('\n');
const filteredLines = [];

for (let i = 0; i < lines.length; i++) {
const line = lines[i];

// Keep envelope header (first line)
if (i === 0) {
filteredLines.push(line);
continue;
}

// Skip empty lines
if (!line.trim()) {
continue;
}

try {
// Try to parse as JSON to check if it's a header
const parsed = JSON.parse(line);

// If it's a replay header, skip this line and the next one (payload)
if (parsed.type === 'replay_recording' || parsed.type === 'replay_event') {
// Skip the next line too (which would be the payload)
i++;
continue;
}

// Keep valid headers and other JSON data
filteredLines.push(line);
} catch {
// If line doesn't parse as JSON, it might be binary data - skip it
continue;
}
}

return filteredLines.join('\n');
}

/**
* Process the envelope item
*
Expand Down
Loading
Loading