logger.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import { store } from "@/store/store";
  2. import moment from "moment";
  3. import { omit } from "lodash-es";
  4. import SlsWebLogger from "js-sls-logger";
  5. import { VITE_SLS_STORE_NAME } from "@/constants/constants";
  6. import { electronLog } from "./electronLog";
  7. import getDeviceInfos from "./deviceInfo";
  8. import { isElectron } from "./nativeMethods";
  9. const aliLogger = new SlsWebLogger({
  10. host: "cn-shenzhen.log.aliyuncs.com",
  11. project: "examcloud",
  12. logstore: VITE_SLS_STORE_NAME,
  13. });
  14. type LogDetail = {
  15. /** default log */
  16. lvl?: "debug" | "log" | "warn" | "error";
  17. /** channels. 往哪些渠道放日志? default: ['console'] */
  18. cnl: ("console" | "local" | "server" | "bd")[];
  19. /** 操作的类型,方便在日志里面查找相同类型的操作 */
  20. key?: string;
  21. /** page name */
  22. pgn?: string;
  23. /** page url: AUTO => 自动从location.path获取 */
  24. pgu?: "AUTO" | `/${string}`;
  25. /** action 引发本次日志的操作 */
  26. act?: string;
  27. /** statck 错误信息的stack,单条错误信息也放此处 */
  28. stk?: string;
  29. /** detail 错误详细信息 */
  30. dtl?: string;
  31. /** api url */
  32. aul?: string;
  33. /** api status */
  34. aus?: string;
  35. /** error json */
  36. ejn?: string;
  37. /** 扩展字段的集合。TODO: 提示不允许出去前面的字段 */
  38. // "not in keyof T" - exclude keyof T from string #42315
  39. // https://github.com/microsoft/TypeScript/issues/42315
  40. ext?: Record<string, any>;
  41. };
  42. /** 记录重要日志到多个source
  43. * 示例:
  44. * @param detail.key - '本地人脸比对'
  45. * @param detail.pgn - '登录页面' | '在线考试列表页面'
  46. * @param detail.act - '点击登录按钮'
  47. * @param detail.cnl - ['local', 'console']
  48. * @param detail.stk - error.stack
  49. * @param detail.dtl - '第一次点击登录' | '第二次点击登录' // 方便查询
  50. * @param detail.aul - '/api/login'
  51. * @param detail.aus - '500'
  52. * @param detail.ejn - JSON.stringify({a: 0})
  53. * @param detail.ext - {UA: 'chrome 99'}
  54. */
  55. export default function createLog(detail: LogDetail) {
  56. const user = store.user;
  57. const defaultFileds = {
  58. /** 供灰度发布识别日志 */
  59. __ver: "3.0.0",
  60. lvl: "log",
  61. uuidForEcs: localStorage.getItem("uuidForEcs"),
  62. clientDate: moment().format("YYYY-MM-DD HH:mm:ss.SSS"),
  63. ...(user?.id ? { userId: user.id } : {}),
  64. ...(detail?.cnl ? { cnl: detail.cnl } : { cnl: ["console"] }),
  65. };
  66. const newDetail = Object.assign(
  67. defaultFileds,
  68. omit(detail, "ext"),
  69. detail.ext
  70. );
  71. // FIXME: 后期设置条件开启非log级别的日志,此时全部打回。
  72. if (newDetail.lvl !== "log") {
  73. return;
  74. }
  75. if (detail.cnl?.includes("console")) {
  76. if (import.meta.env.DEV) {
  77. console.log(
  78. omit(newDetail, ["__ver", "cnl", "lvl", "uuidForEcs", "clientDate"])
  79. );
  80. } else {
  81. console.log(newDetail);
  82. }
  83. }
  84. if (!import.meta.env.DEV && detail.cnl?.includes("server")) {
  85. aliLogger.send(newDetail);
  86. }
  87. if (detail.cnl?.includes("local") && electronLog) {
  88. void electronLog(newDetail);
  89. }
  90. }
  91. /** 要在用户登录后调用,仅需调用一次 */
  92. export function createUserDetailLog() {
  93. const ext: Record<string, any> = {};
  94. const user = store.user;
  95. const deviceInfos = getDeviceInfos();
  96. const { displayName, identityNumber, rootOrgName, rootOrgId } = store.user;
  97. Object.assign(
  98. ext,
  99. { displayName, identityNumber, rootOrgName, rootOrgId },
  100. { userStudentCodeList: user.studentCodeList.join(",") },
  101. { UA: navigator.userAgent },
  102. deviceInfos
  103. );
  104. createLog({ cnl: ["server"], pgn: "登录页面", act: "登录成功日志", ext });
  105. }
  106. export function createEncryptLog() {
  107. // 非 electron 返回
  108. if (!isElectron()) return;
  109. try {
  110. const uuidForEcs = localStorage.getItem("uuidForEcs");
  111. // 没有 uuidForEcs 日志没法查询
  112. if (!uuidForEcs) return;
  113. let log = null;
  114. let lastLogIndex: number = +(localStorage.getItem("lastLogIndex") || 0);
  115. log = window.nodeRequire("electron-log");
  116. // const filePath = log.getFile().path;
  117. // eslint-disable-next-line @typescript-eslint/no-unsafe-call
  118. const filePath: string = log.transports.file.findLogPath();
  119. const fs: typeof import("fs") = window.nodeRequire("fs");
  120. const content = fs.readFileSync(filePath, "utf-8");
  121. const ary = content.toString().split("\r\n").join("\n").split("\n");
  122. // 重复上传没有时间的行:跨过非时间戳的行; 错误识别:全部重新执行
  123. // let lastIndex = ary.findIndex(v => v === lastLog);
  124. // console.log({ lastIndex });
  125. if (ary.length < lastLogIndex) {
  126. lastLogIndex = 0;
  127. }
  128. const logLen = 10;
  129. const newAry = ary
  130. .slice(lastLogIndex, lastLogIndex + logLen)
  131. .filter((v) => v);
  132. // 如果没有上传的内容,则不修改lastLog, 也不上传
  133. if (!newAry.length) return;
  134. lastLogIndex = lastLogIndex + newAry.length;
  135. localStorage.setItem("lastLogIndex", lastLogIndex + "");
  136. createLog({ cnl: ["server"], ext: { encryptLog: newAry.join("\n") } });
  137. } catch (error) {
  138. console.debug(error);
  139. return;
  140. }
  141. }