PromucFlow_constructor/app/client/src/workers/UserLog.ts

148 lines
4.3 KiB
TypeScript

import { uuid4 } from "@sentry/utils";
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
import { LogObject, Methods, Severity } from "entities/AppsmithConsole";
import { klona } from "klona/lite";
import moment from "moment";
import { TriggerMeta } from "sagas/ActionExecution/ActionExecutionSagas";
import { _internalClearTimeout, _internalSetTimeout } from "./TimeoutOverride";
class UserLog {
private flushLogsTimerDelay = 0;
private logs: LogObject[] = [];
private flushLogTimerId: number | undefined;
private requestInfo: {
requestId?: string;
eventType?: EventType;
triggerMeta?: TriggerMeta;
} | null = null;
public setCurrentRequestInfo(requestInfo: {
requestId?: string;
eventType?: EventType;
triggerMeta?: TriggerMeta;
}) {
this.requestInfo = requestInfo;
}
private resetFlushTimer() {
if (this.flushLogTimerId) _internalClearTimeout(this.flushLogTimerId);
this.flushLogTimerId = _internalSetTimeout(() => {
const logs = this.flushLogs();
self.postMessage({
promisified: true,
responseData: {
logs,
eventType: this.requestInfo?.eventType,
triggerMeta: this.requestInfo?.triggerMeta,
},
requestId: this.requestInfo?.requestId,
});
}, this.flushLogsTimerDelay);
}
private saveLog(method: Methods, data: any[]) {
const parsed = this.parseLogs(method, data);
this.logs.push(parsed);
this.resetFlushTimer();
}
public overrideConsoleAPI() {
const { debug, error, info, log, table, warn } = console;
console = {
...console,
table: (...args: any) => {
table.call(this, args);
this.saveLog("table", args);
},
error: (...args: any) => {
error.apply(this, args);
this.saveLog("error", args);
},
log: (...args: any) => {
log.apply(this, args);
this.saveLog("log", args);
},
debug: (...args: any) => {
debug.apply(this, args);
this.saveLog("debug", args);
},
warn: (...args: any) => {
warn.apply(this, args);
this.saveLog("warn", args);
},
info: (...args: any) => {
info.apply(this, args);
this.saveLog("info", args);
},
};
}
private replaceFunctionWithNamesFromObjects(data: any) {
if (typeof data === "function") return `func() ${data.name}`;
if (!data || typeof data !== "object") return data;
if (data instanceof Promise) return "Promise";
const acc: any =
Object.prototype.toString.call(data) === "[object Array]" ? [] : {};
return Object.keys(data).reduce((acc, key) => {
acc[key] = this.replaceFunctionWithNamesFromObjects(data[key]);
return acc;
}, acc);
}
// iterates over the data and if data is object/array, then it will remove any functions from it
private sanitizeData(data: any): any {
try {
const returnData = this.replaceFunctionWithNamesFromObjects(data);
return returnData;
} catch (e) {
return [`There was some error: ${e} ${JSON.stringify(data)}`];
}
}
// returns the logs from the function execution after sanitising them and resets the logs object after that
public flushLogs(): LogObject[] {
const sanitisedLogs = this.logs.map((log) => {
return {
...log,
data: this.sanitizeData(log.data),
};
});
this.resetLogs();
return sanitisedLogs;
}
// parses the incoming log and converts it to the log object
public parseLogs(method: Methods, data: any[]): LogObject {
// Create an ID
const id = uuid4();
const timestamp = moment().format("hh:mm:ss");
// Parse the methods
let output = data;
// For logs UI we only keep 3 levels of severity, info, warn, error
let severity = Severity.INFO;
if (method === "error") {
severity = Severity.ERROR;
output = data.map((error) => {
try {
return error.stack || error;
} catch (e) {
return error;
}
});
} else if (method === "warn") {
severity = Severity.WARNING;
}
return {
method,
id,
data: klona(output),
timestamp,
severity,
};
}
public resetLogs() {
this.logs = [];
}
}
const userLogs = new UserLog();
export default userLogs;