230 lines
5.9 KiB
TypeScript
230 lines
5.9 KiB
TypeScript
|
|
import { uuid4 } from "@sentry/utils";
|
||
|
|
import { Severity, SourceEntity } from "entities/AppsmithConsole";
|
||
|
|
import moment from "moment";
|
||
|
|
|
||
|
|
export type Methods =
|
||
|
|
| "log"
|
||
|
|
| "debug"
|
||
|
|
| "info"
|
||
|
|
| "warn"
|
||
|
|
| "error"
|
||
|
|
| "table"
|
||
|
|
| "clear"
|
||
|
|
| "time"
|
||
|
|
| "timeEnd"
|
||
|
|
| "count"
|
||
|
|
| "assert";
|
||
|
|
|
||
|
|
export type UserLogObject = { logObject: LogObject[]; source: SourceEntity };
|
||
|
|
|
||
|
|
// Type of the log object
|
||
|
|
export type LogObject = {
|
||
|
|
method: Methods | "result";
|
||
|
|
data: any[];
|
||
|
|
timestamp: string;
|
||
|
|
id: string;
|
||
|
|
severity: Severity;
|
||
|
|
};
|
||
|
|
|
||
|
|
const truncate = (input: string, suffix = "", truncLen = 100) => {
|
||
|
|
try {
|
||
|
|
if (!!input) {
|
||
|
|
return input.length > truncLen
|
||
|
|
? `${input.substring(0, truncLen)}...${suffix}`
|
||
|
|
: input;
|
||
|
|
} else {
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
return `Invalid log: ${JSON.stringify(error)}`;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Converts the data from the log object to a string
|
||
|
|
export function createLogTitleString(data: any[]) {
|
||
|
|
try {
|
||
|
|
// convert mixed array to string
|
||
|
|
return data.reduce((acc, curr) => {
|
||
|
|
// curr can be a string or an object
|
||
|
|
if (typeof curr === "boolean") {
|
||
|
|
return `${acc} ${curr}`;
|
||
|
|
}
|
||
|
|
if (curr === null || curr === undefined) {
|
||
|
|
return `${acc} undefined`;
|
||
|
|
}
|
||
|
|
if (curr instanceof Promise) {
|
||
|
|
return `${acc} Promise ${curr.constructor.name}`;
|
||
|
|
}
|
||
|
|
if (typeof curr === "string") {
|
||
|
|
return `${acc} ${truncate(curr)}`;
|
||
|
|
}
|
||
|
|
if (typeof curr === "number") {
|
||
|
|
return `${acc} ${truncate(curr.toString())}`;
|
||
|
|
}
|
||
|
|
if (typeof curr === "function") {
|
||
|
|
return `${acc} func() ${curr.name}`;
|
||
|
|
}
|
||
|
|
if (typeof curr === "object") {
|
||
|
|
let suffix = "}";
|
||
|
|
if (Array.isArray(curr)) {
|
||
|
|
suffix = "]";
|
||
|
|
}
|
||
|
|
return `${acc} ${truncate(JSON.stringify(curr, null, "\t"), suffix)}`;
|
||
|
|
}
|
||
|
|
acc = `${acc} -`;
|
||
|
|
}, "");
|
||
|
|
} catch (error) {
|
||
|
|
return `Error in parsing log: ${JSON.stringify(error)}`;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class UserLog {
|
||
|
|
constructor() {
|
||
|
|
this.initiate();
|
||
|
|
}
|
||
|
|
private logs: LogObject[] = [];
|
||
|
|
// initiates the log object with the default methods and their overrides
|
||
|
|
private initiate() {
|
||
|
|
const { debug, error, info, log, table, warn } = console;
|
||
|
|
console = {
|
||
|
|
...console,
|
||
|
|
table: (...args: any) => {
|
||
|
|
table.call(this, args);
|
||
|
|
const parsed = this.parseLogs("table", args);
|
||
|
|
if (parsed) {
|
||
|
|
this.logs.push(parsed);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
},
|
||
|
|
error: (...args: any) => {
|
||
|
|
error.apply(this, args);
|
||
|
|
const parsed = this.parseLogs("error", args);
|
||
|
|
if (parsed) {
|
||
|
|
this.logs.push(parsed);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
},
|
||
|
|
log: (...args: any) => {
|
||
|
|
log.apply(this, args);
|
||
|
|
const parsed = this.parseLogs("log", args);
|
||
|
|
if (parsed) {
|
||
|
|
this.logs.push(parsed);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
},
|
||
|
|
debug: (...args: any) => {
|
||
|
|
debug.apply(this, args);
|
||
|
|
const parsed = this.parseLogs("debug", args);
|
||
|
|
if (parsed) {
|
||
|
|
this.logs.push(parsed);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
},
|
||
|
|
warn: (...args: any) => {
|
||
|
|
warn.apply(this, args);
|
||
|
|
const parsed = this.parseLogs("warn", args);
|
||
|
|
if (parsed) {
|
||
|
|
this.logs.push(parsed);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
},
|
||
|
|
info: (...args: any) => {
|
||
|
|
info.apply(this, args);
|
||
|
|
const parsed = this.parseLogs("info", args);
|
||
|
|
if (parsed) {
|
||
|
|
this.logs.push(parsed);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
},
|
||
|
|
};
|
||
|
|
}
|
||
|
|
public getTimestamp() {
|
||
|
|
return moment().format("hh:mm:ss");
|
||
|
|
}
|
||
|
|
public replaceFunctionWithNamesFromObjects(data: any) {
|
||
|
|
if (typeof data === "object") {
|
||
|
|
for (const key in data) {
|
||
|
|
if (typeof data[key] === "function") {
|
||
|
|
data[key] = `func() ${data[key].name}`;
|
||
|
|
} else if (data[key] instanceof Promise) {
|
||
|
|
data[key] = "Promise";
|
||
|
|
} else {
|
||
|
|
this.replaceFunctionWithNamesFromObjects(data[key]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return data;
|
||
|
|
}
|
||
|
|
// iterates over the data and if data is object/array, then it will remove any functions from it
|
||
|
|
public sanitizeData(data: any): any {
|
||
|
|
let returnData = [];
|
||
|
|
|
||
|
|
try {
|
||
|
|
returnData = data.map((item: any) => {
|
||
|
|
if (typeof item === "object") {
|
||
|
|
return this.replaceFunctionWithNamesFromObjects(item);
|
||
|
|
}
|
||
|
|
|
||
|
|
// if the item is a function, then remove it from the data and return it as name of the function
|
||
|
|
if (typeof item === "function") {
|
||
|
|
return `func() item.name`;
|
||
|
|
}
|
||
|
|
return item;
|
||
|
|
});
|
||
|
|
} catch (e) {
|
||
|
|
returnData = [`There was some error: ${e} ${JSON.stringify(data)}`];
|
||
|
|
}
|
||
|
|
return returnData;
|
||
|
|
}
|
||
|
|
// returns the logs from the function execution after sanitising them and resets the logs object after that
|
||
|
|
public flushLogs(): LogObject[] {
|
||
|
|
const userLogs = this.logs;
|
||
|
|
this.resetLogs();
|
||
|
|
// sanitise the data key of the user logs
|
||
|
|
const sanitisedLogs = userLogs.map((log) => {
|
||
|
|
return {
|
||
|
|
...log,
|
||
|
|
data: this.sanitizeData(log.data),
|
||
|
|
};
|
||
|
|
});
|
||
|
|
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 = this.getTimestamp();
|
||
|
|
// 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: output,
|
||
|
|
timestamp,
|
||
|
|
severity,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
public resetLogs() {
|
||
|
|
this.logs = [];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const userLogs = new UserLog();
|
||
|
|
|
||
|
|
export default userLogs;
|