Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
102 changes: 67 additions & 35 deletions src/addons/consoleCatcher.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,80 @@
/**
* @file Integration for catching console logs and other info
* @file Module for intercepting console logs with stack trace capture
*/

interface ConsoleLogEvent {
/**
* Window.console object method (i.e. log, info, warn)
*/
method: string;

/**
* Time when the log was occurred
*/
timestamp: Date;

/**
* Log argument
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args: any;
}
import type { ConsoleLogEvent } from '@hawk.so/types/build/src/base/event/addons/javascript';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import type { ConsoleLogEvent } from '@hawk.so/types/build/src/base/event/addons/javascript';
import type { ConsoleLogEvent } from '@hawk.so/types';


const MAX_LOGS = 20;
const consoleOutput: ConsoleLogEvent[] = [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use scoped variable instead of global?


let isInitialized = false;

/**
* Contains all data that will be logged by window.console
* Initializes the console catcher by overriding console methods
* to capture logs with stack traces.
*/
const consoleOutput: ConsoleLogEvent[] = [];
export function initConsoleCatcher(): void {
if (isInitialized) {
return;
};

isInitialized = true;
const consoleMethods = ['log', 'warn', 'error', 'info', 'debug'];

consoleMethods.forEach((method) => {
if (typeof window.console[method] !== 'function') {
return;
};

// Override console methods
Object.keys(window.console).forEach(key => {
const oldFunction = window.console[key];
const oldFunction = window.console[method].bind(window.console);

window.console[key] = function (...args): void {
consoleOutput.push({
method: key,
window.console[method] = function (...args): void {
if (consoleOutput.length >= MAX_LOGS) {
consoleOutput.shift();
}

const stack = new Error().stack?.split('\n').slice(2).join('\n') || '';

Check warning on line 36 in src/addons/consoleCatcher.ts

View workflow job for this annotation

GitHub Actions / lint

Expected line break before `.join`

const logEvent: ConsoleLogEvent = {
method,
timestamp: new Date(),
type: method,
message: args
.map((arg) => (typeof arg === 'string' ? arg : JSON.stringify(arg))).join(' '),
stack,
fileLine: stack.split('\n')[0]?.trim(),
};

consoleOutput.push(logEvent);
oldFunction(...args);
};
});

window.addEventListener('error', function (event) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

think how we can reuse existing catcher errors

if (consoleOutput.length >= MAX_LOGS) {
consoleOutput.shift();
}

const logEvent: ConsoleLogEvent = {
method: 'error',
timestamp: new Date(),
args,
});
oldFunction.apply(window.console, args);
};
});
type: event.error?.name || 'Error',
message: event.error?.message || event.message,
stack: event.error?.stack || '',
fileLine: event.filename
? `${event.filename}:${event.lineno}:${event.colno}`
: '',
};

consoleOutput.push(logEvent);
});
}

/**
* @param event - event to modify
* @param data - event data
* Returns the stack of captured console logs.
*
* @returns {ConsoleLogEvent[]} Array of logged console events.
*/
export default function (event, data): void {
data.payload.consoleOutput = consoleOutput;
export function getConsoleLogStack(): ConsoleLogEvent[] {
return [ ...consoleOutput ];
}
8 changes: 8 additions & 0 deletions src/catcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type { JavaScriptCatcherIntegrations } from './types/integrations';
import { EventRejectedError } from './errors';
import type { HawkJavaScriptEvent } from './types';
import { isErrorProcessed, markErrorAsProcessed } from './utils/event';
import { getConsoleLogStack, initConsoleCatcher } from './addons/consoleCatcher';

/**
* Allow to use global VERSION, that will be overwritten by Webpack
Expand Down Expand Up @@ -130,6 +131,8 @@ export default class Catcher {
},
});

initConsoleCatcher();

/**
* Set global handlers
*/
Expand Down Expand Up @@ -489,6 +492,7 @@ export default class Catcher {
const userAgent = window.navigator.userAgent;
const location = window.location.href;
const getParams = this.getGetParams();
const consoleLogs = getConsoleLogStack();

const addons: JavaScriptAddons = {
window: {
Expand All @@ -507,6 +511,10 @@ export default class Catcher {
addons.RAW_EVENT_DATA = this.getRawData(error);
}

if (consoleLogs.length > 0) {
addons.consoleOutput = consoleLogs;
}

return addons;
}

Expand Down