312 lines
9.2 KiB
TypeScript
312 lines
9.2 KiB
TypeScript
import electron from "electron";
|
|
import * as 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 debug = (label: string, data: any = null) => {
|
|
// debug 级别等同于 info,但在开发环境中可能显示更多细节
|
|
return log("INFO", 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 debugRenderOrMain = (label: string, data: any = null) => {
|
|
if (electron.ipcRenderer) {
|
|
console.log("Log.debug", label, data);
|
|
return electron.ipcRenderer.invoke("log:debug", label, data);
|
|
} else {
|
|
return debug(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,
|
|
debug,
|
|
infoRenderOrMain,
|
|
errorRenderOrMain,
|
|
debugRenderOrMain,
|
|
appPath,
|
|
appInfo,
|
|
appError,
|
|
appInfoRenderOrMain,
|
|
appErrorRenderOrMain,
|
|
collectRenderOrMain,
|
|
jsonStringifyLogData,
|
|
};
|
|
|
|
export const Log = {
|
|
jsonStringifyLogData,
|
|
info: infoRenderOrMain,
|
|
error: errorRenderOrMain,
|
|
debug: debugRenderOrMain,
|
|
warn: infoRenderOrMain, // warn 使用 info 实现
|
|
appPath,
|
|
appInfo: appInfoRenderOrMain,
|
|
appError: appErrorRenderOrMain,
|
|
};
|