Track bundled vendor runtime sources
This commit is contained in:
73
vendor/aigcpanel/electron/mapi/log/beacon-render.ts
vendored
Normal file
73
vendor/aigcpanel/electron/mapi/log/beacon-render.ts
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 渲染进程异常上报(HTTP Beacon)
|
||||
* 仅在 isPackaged(非开发)模式下上报,批量异步发送。
|
||||
*/
|
||||
import { AppConfig } from "../../../src/config";
|
||||
|
||||
declare const __BUILD_ID__: string;
|
||||
|
||||
const BEACON_URL = "https://g.tecmz.com/grow/load.gif";
|
||||
const BEACON_APP = "aigcpanel";
|
||||
const isPackaged = process.env["IS_PACKAGED"] === "true";
|
||||
const sessionId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
const buildId = typeof __BUILD_ID__ !== "undefined" ? __BUILD_ID__ : "unknown";
|
||||
|
||||
interface BeaconEvent {
|
||||
et: "error";
|
||||
path: string;
|
||||
did: string;
|
||||
sid: string;
|
||||
ts: number;
|
||||
type: string;
|
||||
bid: string;
|
||||
props: {
|
||||
msg: string;
|
||||
stack?: string;
|
||||
src?: string;
|
||||
line?: number;
|
||||
col?: number;
|
||||
};
|
||||
}
|
||||
|
||||
let pending: BeaconEvent[] = [];
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const flush = () => {
|
||||
if (!pending.length) return;
|
||||
const events = pending.splice(0);
|
||||
try {
|
||||
const encoded = encodeURIComponent(btoa(JSON.stringify(events)));
|
||||
const url = `${BEACON_URL}?app=${BEACON_APP}&data=${encoded}`;
|
||||
fetch(url).catch(() => {});
|
||||
} catch {}
|
||||
};
|
||||
|
||||
const schedule = () => {
|
||||
if (timer) return;
|
||||
timer = setTimeout(() => {
|
||||
timer = null;
|
||||
flush();
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
export const reportErrorRender = (
|
||||
msg: string,
|
||||
stack?: string,
|
||||
src?: string,
|
||||
line?: number,
|
||||
col?: number,
|
||||
path = "/renderer",
|
||||
) => {
|
||||
if (!isPackaged) return;
|
||||
pending.push({
|
||||
et: "error",
|
||||
path,
|
||||
did: "renderer",
|
||||
sid: sessionId,
|
||||
ts: Date.now(),
|
||||
type: `app-${AppConfig.version}`,
|
||||
bid: buildId,
|
||||
props: { msg, stack, src, line, col },
|
||||
});
|
||||
schedule();
|
||||
};
|
||||
74
vendor/aigcpanel/electron/mapi/log/beacon.ts
vendored
Normal file
74
vendor/aigcpanel/electron/mapi/log/beacon.ts
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 主进程异常上报(HTTP Beacon)
|
||||
* 仅在 isPackaged(非开发)模式下上报,批量异步发送。
|
||||
*/
|
||||
import https from "node:https";
|
||||
import { isPackaged, platformUUID } from "../../lib/env";
|
||||
import { AppConfig } from "../../../src/config";
|
||||
|
||||
declare const __BUILD_ID__: string;
|
||||
|
||||
const BEACON_URL = "https://g.tecmz.com/grow/load.gif";
|
||||
const BEACON_APP = "aigcpanel";
|
||||
const sessionId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
const buildId = typeof __BUILD_ID__ !== "undefined" ? __BUILD_ID__ : "unknown";
|
||||
|
||||
interface BeaconEvent {
|
||||
et: "error";
|
||||
path: string;
|
||||
did: string;
|
||||
sid: string;
|
||||
ts: number;
|
||||
type: string;
|
||||
bid: string;
|
||||
props: { msg: string; stack?: string };
|
||||
}
|
||||
|
||||
let pending: BeaconEvent[] = [];
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
let _did: string | null = null;
|
||||
|
||||
const getDid = (): string => {
|
||||
if (_did) return _did;
|
||||
try {
|
||||
_did = platformUUID() || "unknown";
|
||||
} catch {
|
||||
_did = "unknown";
|
||||
}
|
||||
return _did;
|
||||
};
|
||||
|
||||
const flush = () => {
|
||||
if (!pending.length) return;
|
||||
const events = pending.splice(0);
|
||||
try {
|
||||
const encoded = encodeURIComponent(
|
||||
Buffer.from(JSON.stringify(events)).toString("base64"),
|
||||
);
|
||||
const url = `${BEACON_URL}?app=${BEACON_APP}&data=${encoded}`;
|
||||
https.get(url).on("error", () => {});
|
||||
} catch {}
|
||||
};
|
||||
|
||||
const schedule = () => {
|
||||
if (timer) return;
|
||||
timer = setTimeout(() => {
|
||||
timer = null;
|
||||
flush();
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
export const reportError = (msg: string, stack?: string, path = "/main") => {
|
||||
if (!isPackaged) return;
|
||||
pending.push({
|
||||
et: "error",
|
||||
path,
|
||||
did: getDid(),
|
||||
sid: sessionId,
|
||||
ts: Date.now(),
|
||||
type: `app-${AppConfig.version}`,
|
||||
bid: buildId,
|
||||
props: { msg, stack },
|
||||
});
|
||||
schedule();
|
||||
};
|
||||
319
vendor/aigcpanel/electron/mapi/log/index.ts
vendored
Normal file
319
vendor/aigcpanel/electron/mapi/log/index.ts
vendored
Normal file
@@ -0,0 +1,319 @@
|
||||
import electron from "electron";
|
||||
import date from "date-and-time";
|
||||
import path from "node:path";
|
||||
import { AppEnv } from "../env";
|
||||
import fs from "node:fs";
|
||||
import dayjs from "dayjs";
|
||||
import FileIndex from "../file";
|
||||
|
||||
let fileName = null;
|
||||
let fileStream = null;
|
||||
let appFileNames = {};
|
||||
let appFileStreams = {};
|
||||
|
||||
const stringDatetime = () => {
|
||||
return date.format(new Date(), "YYYYMMDD");
|
||||
};
|
||||
|
||||
const jsonStringifyLogData = (data: any) => {
|
||||
return JSON.stringify(data, (key, value) => {
|
||||
if (typeof value === "string" && value.length > 200) {
|
||||
if (
|
||||
value.startsWith("data:") ||
|
||||
value.substring(0, 190).match(/^[a-zA-Z0-9+/=]+\s*$/)
|
||||
) {
|
||||
return (
|
||||
value.substring(0, 100) + "...(length=" + value.length + ")"
|
||||
);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
};
|
||||
|
||||
const logsDir = () => {
|
||||
return path.join(AppEnv.userData, "logs");
|
||||
};
|
||||
|
||||
const appLogsDir = () => {
|
||||
return path.join(AppEnv.dataRoot, "logs");
|
||||
};
|
||||
|
||||
const root = () => {
|
||||
return logsDir();
|
||||
};
|
||||
|
||||
const file = () => {
|
||||
return path.join(logsDir(), "log_" + stringDatetime() + ".log");
|
||||
};
|
||||
|
||||
const appFile = (name: string) => {
|
||||
return path.join(appLogsDir(), name + "_" + stringDatetime() + ".log");
|
||||
};
|
||||
|
||||
const cleanOldLogs = (keepDays: number) => {
|
||||
const logDirs = [
|
||||
// 系统日志
|
||||
logsDir(),
|
||||
// 应用日志
|
||||
appLogsDir(),
|
||||
];
|
||||
for (const logDir of logDirs) {
|
||||
if (!fs.existsSync(logDir)) {
|
||||
return;
|
||||
}
|
||||
const files = fs.readdirSync(logDir);
|
||||
const now = new Date();
|
||||
// console.log('cleanOldLogs', logDir, files)
|
||||
for (let file of files) {
|
||||
const filePath = path.join(logDir, file);
|
||||
let date = null;
|
||||
for (let s of file.split(/[_\\.]/)) {
|
||||
// 匹配 YYYYMMDD
|
||||
if (s.match(/^\d{8}$/)) {
|
||||
date = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!date) {
|
||||
continue;
|
||||
}
|
||||
const fileDate = new Date(
|
||||
parseInt(date.substring(0, 4)),
|
||||
parseInt(date.substring(4, 6)) - 1,
|
||||
parseInt(date.substring(6, 8)),
|
||||
);
|
||||
const diff = Math.abs(now.getTime() - fileDate.getTime());
|
||||
const diffDays = Math.ceil(diff / (1000 * 3600 * 24));
|
||||
// console.log('fileDate', file, fileDate, diffDays)
|
||||
if (diffDays > keepDays) {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const log = (level: "INFO" | "ERROR", label: string, data: any = null) => {
|
||||
if (fileName !== file()) {
|
||||
fileName = file();
|
||||
const logDir = logsDir();
|
||||
if (!fs.existsSync(logDir)) {
|
||||
fs.mkdirSync(logDir);
|
||||
}
|
||||
if (fileStream) {
|
||||
fileStream.end();
|
||||
}
|
||||
fileStream = fs.createWriteStream(fileName, { flags: "a" });
|
||||
cleanOldLogs(14);
|
||||
}
|
||||
let line = [];
|
||||
line.push(date.format(new Date(), "YYYY-MM-DD HH:mm:ss"));
|
||||
line.push(level);
|
||||
line.push(label);
|
||||
if (data) {
|
||||
if (!["number", "string"].includes(typeof data)) {
|
||||
data = jsonStringifyLogData(data);
|
||||
}
|
||||
line.push(data);
|
||||
}
|
||||
console.log(line.join(" - "));
|
||||
fileStream.write(line.join(" - ") + "\n");
|
||||
};
|
||||
|
||||
const info = (label: string, data: any = null) => {
|
||||
return log("INFO", label, data);
|
||||
};
|
||||
const error = (label: string, data: any = null) => {
|
||||
return log("ERROR", label, data);
|
||||
};
|
||||
|
||||
const appLog = (
|
||||
name: string,
|
||||
level: "INFO" | "ERROR",
|
||||
label: string,
|
||||
data: any = null,
|
||||
) => {
|
||||
let fileChanged = false;
|
||||
if (appFileNames[name] !== appFile(name)) {
|
||||
appFileNames[name] = appFile(name);
|
||||
fileChanged = true;
|
||||
}
|
||||
if (fileChanged || !appFileStreams[name]) {
|
||||
if (appFileStreams[name]) {
|
||||
appFileStreams[name].end();
|
||||
}
|
||||
const logDir = appLogsDir();
|
||||
if (!fs.existsSync(logDir)) {
|
||||
fs.mkdirSync(logDir);
|
||||
}
|
||||
appFileStreams[name] = fs.createWriteStream(appFileNames[name], {
|
||||
flags: "a",
|
||||
});
|
||||
}
|
||||
let line = [];
|
||||
line.push(date.format(new Date(), "YYYY-MM-DD HH:mm:ss"));
|
||||
line.push(level);
|
||||
line.push(label);
|
||||
if (data) {
|
||||
if (!["number", "string"].includes(typeof data)) {
|
||||
data = JSON.stringify(data);
|
||||
}
|
||||
line.push(data);
|
||||
}
|
||||
console.log(`[APP:${name}] - ` + line.join(" - "));
|
||||
appFileStreams[name].write(line.join(" - ") + "\n");
|
||||
};
|
||||
|
||||
const appPath = (name: string) => {
|
||||
if (!appFileNames[name]) {
|
||||
appFileNames[name] = appFile(name);
|
||||
}
|
||||
return appFileNames[name];
|
||||
};
|
||||
|
||||
const appInfo = (name: string, label: string, data: any = null) => {
|
||||
return appLog(name, "INFO", label, data);
|
||||
};
|
||||
const appError = (name: string, label: string, data: any = null) => {
|
||||
return appLog(name, "ERROR", label, data);
|
||||
};
|
||||
|
||||
const infoRenderOrMain = (label: string, data: any = null) => {
|
||||
if (electron.ipcRenderer) {
|
||||
console.log("Log.info", label, data);
|
||||
return electron.ipcRenderer.invoke("log:info", label, data);
|
||||
} else {
|
||||
return info(label, data);
|
||||
}
|
||||
};
|
||||
const errorRenderOrMain = (label: string, data: any = null) => {
|
||||
if (electron.ipcRenderer) {
|
||||
console.error("Log.error", label, data);
|
||||
return electron.ipcRenderer.invoke("log:error", label, data);
|
||||
} else {
|
||||
return error(label, data);
|
||||
}
|
||||
};
|
||||
|
||||
const appInfoRenderOrMain = (name: string, label: string, data: any = null) => {
|
||||
if (electron.ipcRenderer) {
|
||||
console.log("Log.appInfo", name, label, data);
|
||||
return electron.ipcRenderer.invoke("log:appInfo", name, label, data);
|
||||
} else {
|
||||
return appInfo(name, label, data);
|
||||
}
|
||||
};
|
||||
|
||||
const appErrorRenderOrMain = (
|
||||
name: string,
|
||||
label: string,
|
||||
data: any = null,
|
||||
) => {
|
||||
if (electron.ipcRenderer) {
|
||||
console.error("Log.appError", name, label, data);
|
||||
return electron.ipcRenderer.invoke("log:appError", name, label, data);
|
||||
} else {
|
||||
return appError(name, label, data);
|
||||
}
|
||||
};
|
||||
|
||||
const collectRenderOrMain = async (option?: {
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
limit?: number;
|
||||
}) => {
|
||||
option = Object.assign(
|
||||
{
|
||||
startTime: dayjs().subtract(1, "day").format("YYYY-MM-DD HH:mm:ss"),
|
||||
endTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
|
||||
limit: 10 * 10000,
|
||||
},
|
||||
option,
|
||||
);
|
||||
let startMs = dayjs(option.startTime).valueOf();
|
||||
let endMs = dayjs(option.endTime).valueOf();
|
||||
let startDayMs = dayjs(option.startTime).startOf("day").valueOf();
|
||||
let endDayMs = dayjs(option.endTime).endOf("day").valueOf();
|
||||
let resultLines = [];
|
||||
let logFiles = [];
|
||||
logFiles = logFiles.concat(
|
||||
await FileIndex.list(logsDir(), { isDataPath: false }),
|
||||
);
|
||||
logFiles = logFiles.concat(
|
||||
await FileIndex.list(appLogsDir(), { isDataPath: false }),
|
||||
);
|
||||
// console.log('logFiles', logFiles)
|
||||
logFiles = logFiles.filter((logFile) => {
|
||||
if (logFile.isDirectory) {
|
||||
return false;
|
||||
}
|
||||
let date = null;
|
||||
for (let s of logFile.name.split(/[_\\.]/)) {
|
||||
// 匹配 YYYYMMDD
|
||||
if (s.match(/^\d{8}$/)) {
|
||||
date = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!date) {
|
||||
return false;
|
||||
}
|
||||
const fileDate = new Date(
|
||||
parseInt(date.substring(0, 4)),
|
||||
parseInt(date.substring(4, 6)) - 1,
|
||||
parseInt(date.substring(6, 8)),
|
||||
);
|
||||
if (fileDate.getTime() < startDayMs || fileDate.getTime() > endDayMs) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
// console.log('collectRenderOrMain', {
|
||||
// ...option,
|
||||
// logFiles, startMs, endMs, startDayMs, endDayMs
|
||||
// })
|
||||
for (const logFile of logFiles) {
|
||||
await FileIndex.readLine(
|
||||
logFile.pathname,
|
||||
(line) => {
|
||||
const lineParts = line.split(" - ");
|
||||
const lineTime = dayjs(lineParts[0]);
|
||||
// console.log('lineTime', lineParts[0], lineTime.isBefore(startMs) || lineTime.isAfter(endMs))
|
||||
if (lineTime.isBefore(startMs) || lineTime.isAfter(endMs)) {
|
||||
return;
|
||||
}
|
||||
resultLines.push(line);
|
||||
},
|
||||
{ isDataPath: false },
|
||||
);
|
||||
}
|
||||
return {
|
||||
startTime: option.startTime,
|
||||
endTime: option.endTime,
|
||||
logs: resultLines.join("\n"),
|
||||
};
|
||||
};
|
||||
|
||||
export default {
|
||||
root,
|
||||
info,
|
||||
error,
|
||||
infoRenderOrMain,
|
||||
errorRenderOrMain,
|
||||
appPath,
|
||||
appInfo,
|
||||
appError,
|
||||
appInfoRenderOrMain,
|
||||
appErrorRenderOrMain,
|
||||
collectRenderOrMain,
|
||||
jsonStringifyLogData,
|
||||
};
|
||||
|
||||
export const Log = {
|
||||
jsonStringifyLogData,
|
||||
info: infoRenderOrMain,
|
||||
error: errorRenderOrMain,
|
||||
appPath,
|
||||
appInfo: appInfoRenderOrMain,
|
||||
appError: appErrorRenderOrMain,
|
||||
};
|
||||
37
vendor/aigcpanel/electron/mapi/log/main.ts
vendored
Normal file
37
vendor/aigcpanel/electron/mapi/log/main.ts
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
import { ipcMain } from "electron";
|
||||
import logIndex from "./index";
|
||||
|
||||
ipcMain.handle("log:info", (event, label: string, data: any) => {
|
||||
logIndex.info(label, data);
|
||||
});
|
||||
ipcMain.handle("log:error", (event, label: string, data: any) => {
|
||||
logIndex.error(label, data);
|
||||
});
|
||||
ipcMain.handle(
|
||||
"log:appInfo",
|
||||
(event, name: string, label: string, data: any) => {
|
||||
logIndex.appInfo(name, label, data);
|
||||
},
|
||||
);
|
||||
ipcMain.handle(
|
||||
"log:appError",
|
||||
(event, name: string, label: string, data: any) => {
|
||||
logIndex.appError(name, label, data);
|
||||
},
|
||||
);
|
||||
|
||||
export default {
|
||||
info: logIndex.info,
|
||||
error: logIndex.error,
|
||||
appInfo: logIndex.appInfo,
|
||||
appError: logIndex.appError,
|
||||
};
|
||||
|
||||
export const Log = {
|
||||
info: logIndex.info,
|
||||
error: logIndex.error,
|
||||
appPath: logIndex.appPath,
|
||||
appInfo: logIndex.appInfo,
|
||||
appError: logIndex.appError,
|
||||
jsonStringifyLogData: logIndex.jsonStringifyLogData,
|
||||
};
|
||||
10
vendor/aigcpanel/electron/mapi/log/render.ts
vendored
Normal file
10
vendor/aigcpanel/electron/mapi/log/render.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import logIndex from "./index";
|
||||
|
||||
export default {
|
||||
root: logIndex.root,
|
||||
info: logIndex.infoRenderOrMain,
|
||||
error: logIndex.errorRenderOrMain,
|
||||
appInfo: logIndex.appInfoRenderOrMain,
|
||||
appError: logIndex.appErrorRenderOrMain,
|
||||
collect: logIndex.collectRenderOrMain,
|
||||
};
|
||||
Reference in New Issue
Block a user