import { store } from "@/store/store"; import moment from "moment"; import { omit } from "lodash-es"; import SlsWebLogger from "js-sls-logger"; import { VITE_SLS_STORE_NAME } from "@/constants/constants"; import { electronLog } from "./electronLog"; import getDeviceInfos from "./deviceInfo"; import { isElectron } from "./nativeMethods"; const aliLogger = new SlsWebLogger({ host: "cn-shenzhen.log.aliyuncs.com", project: "examcloud", logstore: VITE_SLS_STORE_NAME, }); type LogDetail = { /** default log */ lvl?: "debug" | "log" | "warn" | "error"; /** channels. 往哪些渠道放日志? default: ['console'] */ cnl: ("console" | "local" | "server" | "bd")[]; /** 操作的类型,方便在日志里面查找相同类型的操作 */ key?: string; /** page name */ pgn?: string; /** page url: AUTO => 自动从location.path获取 */ pgu?: "AUTO" | `/${string}`; /** action 引发本次日志的操作 */ act?: string; /** statck 错误信息的stack,单条错误信息也放此处 */ stk?: string; /** detail 错误详细信息 */ dtl?: string; /** api url */ aul?: string; /** api status */ aus?: string; /** error json */ ejn?: string; /** 扩展字段的集合。TODO: 提示不允许出去前面的字段 */ // "not in keyof T" - exclude keyof T from string #42315 // https://github.com/microsoft/TypeScript/issues/42315 ext?: Record; }; /** 记录重要日志到多个source * 示例: * @param detail.key - '本地人脸比对' * @param detail.pgn - '登录页面' | '在线考试列表页面' * @param detail.act - '点击登录按钮' * @param detail.cnl - ['local', 'console'] * @param detail.stk - error.stack * @param detail.dtl - '第一次点击登录' | '第二次点击登录' // 方便查询 * @param detail.aul - '/api/login' * @param detail.aus - '500' * @param detail.ejn - JSON.stringify({a: 0}) * @param detail.ext - {UA: 'chrome 99'} */ export default function createLog(detail: LogDetail) { const user = store.user; const defaultFileds = { /** 供灰度发布识别日志 */ __ver: "3.0.0", lvl: "log", uuidForEcs: localStorage.getItem("uuidForEcs"), clientDate: moment().format("YYYY-MM-DD HH:mm:ss.SSS"), ...(user?.id ? { userId: user.id } : {}), ...(detail?.cnl ? { cnl: detail.cnl } : { cnl: ["console"] }), }; const newDetail = Object.assign( defaultFileds, omit(detail, "ext"), detail.ext ); // FIXME: 后期设置条件开启非log级别的日志,此时全部打回。 if (newDetail.lvl !== "log") { return; } if (detail.cnl?.includes("console")) { if (import.meta.env.DEV) { console.log( omit(newDetail, ["__ver", "cnl", "lvl", "uuidForEcs", "clientDate"]) ); } else { console.log(newDetail); } } if (!import.meta.env.DEV && detail.cnl?.includes("server")) { aliLogger.send(newDetail); } if (detail.cnl?.includes("local") && electronLog) { void electronLog(newDetail); } } /** 要在用户登录后调用,仅需调用一次 */ export function createUserDetailLog() { const ext: Record = {}; const user = store.user; const deviceInfos = getDeviceInfos(); const { displayName, identityNumber, rootOrgName, rootOrgId } = store.user; Object.assign( ext, { displayName, identityNumber, rootOrgName, rootOrgId }, { userStudentCodeList: user.studentCodeList.join(",") }, { UA: navigator.userAgent }, deviceInfos ); createLog({ cnl: ["server"], pgn: "登录页面", act: "登录成功日志", ext }); } export function createEncryptLog() { // 非 electron 返回 if (!isElectron()) return; try { const uuidForEcs = localStorage.getItem("uuidForEcs"); // 没有 uuidForEcs 日志没法查询 if (!uuidForEcs) return; let log = null; let lastLogIndex: number = +(localStorage.getItem("lastLogIndex") || 0); log = window.nodeRequire("electron-log"); // const filePath = log.getFile().path; // eslint-disable-next-line @typescript-eslint/no-unsafe-call const filePath: string = log.transports.file.findLogPath(); const fs: typeof import("fs") = window.nodeRequire("fs"); const content = fs.readFileSync(filePath, "utf-8"); const ary = content.toString().split("\r\n").join("\n").split("\n"); // 重复上传没有时间的行:跨过非时间戳的行; 错误识别:全部重新执行 // let lastIndex = ary.findIndex(v => v === lastLog); // console.log({ lastIndex }); if (ary.length < lastLogIndex) { lastLogIndex = 0; } const logLen = 10; const newAry = ary .slice(lastLogIndex, lastLogIndex + logLen) .filter((v) => v); // 如果没有上传的内容,则不修改lastLog, 也不上传 if (!newAry.length) return; lastLogIndex = lastLogIndex + newAry.length; localStorage.setItem("lastLogIndex", lastLogIndex + ""); createLog({ cnl: ["server"], ext: { encryptLog: newAry.join("\n") } }); } catch (error) { console.debug(error); return; } }