Skip to content

Commit 5c549bf

Browse files
authored
Merge pull request #116 from codex-team/feat/console-catcher
consoleCatcher init
2 parents 4467c5e + 286f16f commit 5c549bf

File tree

2 files changed

+100
-38
lines changed

2 files changed

+100
-38
lines changed

src/addons/consoleCatcher.ts

Lines changed: 86 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,96 @@
11
/**
2-
* @file Integration for catching console logs and other info
2+
* @file Module for intercepting console logs with stack trace capture
33
*/
44

5-
interface ConsoleLogEvent {
6-
/**
7-
* Window.console object method (i.e. log, info, warn)
8-
*/
9-
method: string;
5+
import type { ConsoleLogEvent } from '@hawk.so/types';
106

11-
/**
12-
* Time when the log was occurred
13-
*/
14-
timestamp: Date;
7+
const createConsoleCatcher = (): {
8+
initConsoleCatcher: () => void;
9+
addErrorEvent: (event: ErrorEvent | PromiseRejectionEvent) => void;
10+
getConsoleLogStack: () => ConsoleLogEvent[];
11+
} => {
12+
const MAX_LOGS = 20;
13+
const consoleOutput: ConsoleLogEvent[] = [];
14+
let isInitialized = false;
1515

16-
/**
17-
* Log argument
18-
*/
19-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
20-
args: any;
21-
}
22-
23-
/**
24-
* Contains all data that will be logged by window.console
25-
*/
26-
const consoleOutput: ConsoleLogEvent[] = [];
16+
const addToConsoleOutput = (logEvent: ConsoleLogEvent): void => {
17+
if (consoleOutput.length >= MAX_LOGS) {
18+
consoleOutput.shift();
19+
}
20+
consoleOutput.push(logEvent);
21+
};
2722

28-
// Override console methods
29-
Object.keys(window.console).forEach(key => {
30-
const oldFunction = window.console[key];
23+
const createConsoleEventFromError = (
24+
event: ErrorEvent | PromiseRejectionEvent
25+
): ConsoleLogEvent => {
26+
if (event instanceof ErrorEvent) {
27+
return {
28+
method: 'error',
29+
timestamp: new Date(),
30+
type: event.error?.name || 'Error',
31+
message: event.error?.message || event.message,
32+
stack: event.error?.stack || '',
33+
fileLine: event.filename
34+
? `${event.filename}:${event.lineno}:${event.colno}`
35+
: '',
36+
};
37+
}
3138

32-
window.console[key] = function (...args): void {
33-
consoleOutput.push({
34-
method: key,
39+
return {
40+
method: 'error',
3541
timestamp: new Date(),
36-
args,
37-
});
38-
oldFunction.apply(window.console, args);
42+
type: 'UnhandledRejection',
43+
message: event.reason?.message || String(event.reason),
44+
stack: event.reason?.stack || '',
45+
fileLine: '',
46+
};
3947
};
40-
});
4148

42-
/**
43-
* @param event - event to modify
44-
* @param data - event data
45-
*/
46-
export default function (event, data): void {
47-
data.payload.consoleOutput = consoleOutput;
48-
}
49+
return {
50+
initConsoleCatcher(): void {
51+
if (isInitialized) {
52+
return;
53+
}
54+
55+
isInitialized = true;
56+
const consoleMethods: string[] = ['log', 'warn', 'error', 'info', 'debug'];
57+
58+
consoleMethods.forEach((method) => {
59+
if (typeof window.console[method] !== 'function') {
60+
return;
61+
}
62+
63+
const oldFunction = window.console[method].bind(window.console);
64+
65+
window.console[method] = function (...args: unknown[]): void {
66+
const stack = new Error().stack?.split('\n').slice(2).join('\n') || '';
67+
68+
const logEvent: ConsoleLogEvent = {
69+
method,
70+
timestamp: new Date(),
71+
type: method,
72+
message: args.map((arg) => typeof arg === 'string' ? arg : JSON.stringify(arg)).join(' '),
73+
stack,
74+
fileLine: stack.split('\n')[0]?.trim(),
75+
};
76+
77+
addToConsoleOutput(logEvent);
78+
oldFunction(...args);
79+
};
80+
});
81+
},
82+
83+
addErrorEvent(event: ErrorEvent | PromiseRejectionEvent): void {
84+
const logEvent = createConsoleEventFromError(event);
85+
86+
addToConsoleOutput(logEvent);
87+
},
88+
89+
getConsoleLogStack(): ConsoleLogEvent[] {
90+
return [ ...consoleOutput ];
91+
},
92+
};
93+
};
94+
95+
const consoleCatcher = createConsoleCatcher();
96+
export const { initConsoleCatcher, getConsoleLogStack, addErrorEvent } = consoleCatcher;

src/catcher.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type { JavaScriptCatcherIntegrations } from './types/integrations';
1616
import { EventRejectedError } from './errors';
1717
import type { HawkJavaScriptEvent } from './types';
1818
import { isErrorProcessed, markErrorAsProcessed } from './utils/event';
19+
import { addErrorEvent, getConsoleLogStack, initConsoleCatcher } from './addons/consoleCatcher';
1920

2021
/**
2122
* Allow to use global VERSION, that will be overwritten by Webpack
@@ -135,6 +136,8 @@ export default class Catcher {
135136
},
136137
});
137138

139+
initConsoleCatcher();
140+
138141
/**
139142
* Set global handlers
140143
*/
@@ -230,6 +233,12 @@ export default class Catcher {
230233
* @param {ErrorEvent|PromiseRejectionEvent} event — (!) both for Error and Promise Rejection
231234
*/
232235
private async handleEvent(event: ErrorEvent | PromiseRejectionEvent): Promise<void> {
236+
/**
237+
* Add error to console logs
238+
*/
239+
240+
addErrorEvent(event);
241+
233242
/**
234243
* Promise rejection reason is recommended to be an Error, but it can be a string:
235244
* - Promise.reject(new Error('Reason message')) ——— recommended
@@ -494,6 +503,7 @@ export default class Catcher {
494503
const userAgent = window.navigator.userAgent;
495504
const location = window.location.href;
496505
const getParams = this.getGetParams();
506+
const consoleLogs = getConsoleLogStack();
497507

498508
const addons: JavaScriptAddons = {
499509
window: {
@@ -512,6 +522,10 @@ export default class Catcher {
512522
addons.RAW_EVENT_DATA = this.getRawData(error);
513523
}
514524

525+
if (consoleLogs.length > 0) {
526+
addons.consoleOutput = consoleLogs;
527+
}
528+
515529
return addons;
516530
}
517531

0 commit comments

Comments
 (0)