Forráskód Böngészése

feat(logging): implement logging functionality compatible with pkg, electron and browser environments

dengdx 1 hónapja
szülő
commit
1fbec7a122
6 módosított fájl, 102 hozzáadás és 0 törlés
  1. 5 0
      main.js
  2. 14 0
      server.js
  3. 4 0
      src/app.tsx
  4. 16 0
      src/log/log-path.js
  5. 30 0
      src/log/log-writer.js
  6. 33 0
      src/log/logger.js

+ 5 - 0
main.js

@@ -1,5 +1,6 @@
 const { app, BrowserWindow, Menu } = require('electron');
 const path = require('path');
+const {writeLog} = require('./src/log/log-writer.js')
 
 function createWindow() {
   const isMac = process.platform === 'darwin';
@@ -36,4 +37,8 @@ app.on('window-all-closed', () => {
 
 app.on('activate', () => {
   if (BrowserWindow.getAllWindows().length === 0) createWindow();
+});
+
+ipcMain.handle('write-log', (_, level, msg) => {
+  writeLog(level, msg);
 });

+ 14 - 0
server.js

@@ -65,6 +65,20 @@ function serveStatic(req, res) {
 
 /* ------- 主入口 ------- */
 http.createServer((req, res) => {
+    // 处理日志请求--纪录
+    if (req.method === 'POST' && req.url === '/log') {
+        let body = '';
+        req.on('data', chunk => body += chunk);
+        req.on('end', () => {
+            try {
+                const { level, msg } = JSON.parse(body);
+                writeLog(level, msg);
+            } catch { }
+            res.writeHead(204);
+            res.end();
+        });
+        return;
+    }
     // 1. 预检直接 200
     if (req.method === 'OPTIONS') { res.writeHead(200); res.end(); return; }
 

+ 4 - 0
src/app.tsx

@@ -14,6 +14,10 @@ const locale = (window.navigator.language || 'en').toLowerCase().split('-')[0];
 import messages_en from './assets/i18n/messages/en';
 import messages_zh from './assets/i18n/messages/zh';
 import AcquisitionTracer from './pages/exam/components/acquisitionTracer';
+import { logger } from './log/logger';
+console.log = logger.log;
+console.warn = logger.warn;
+console.error = logger.error;
 
 const messages = locale === 'zh' ? messages_zh : messages_en;
 console.log(`process.env.USE_MSW: ${process.env.USE_MSW}`);

+ 16 - 0
src/log/log-path.js

@@ -0,0 +1,16 @@
+import { fileURLToPath } from 'url';
+import path from 'path';
+import fs from 'fs';
+
+// ① 兼容 pkg:__dirname 会被重写成 /snapshot/...,需要 process.execPath
+const IS_PKG = process.pkg !== undefined;
+const EXE_DIR = IS_PKG
+  ? path.dirname(process.execPath)          // pkg 运行时
+  : path.dirname(process.argv[1]);          // 普通 node 启动
+
+export const LOG_DIR = path.join(EXE_DIR, 'logs');
+
+/* 确保目录存在 */
+if (!fs.existsSync(LOG_DIR)) {
+  fs.mkdirSync(LOG_DIR, { recursive: true });
+}

+ 30 - 0
src/log/log-writer.js

@@ -0,0 +1,30 @@
+import { LOG_DIR } from './log-path.js';
+import path from 'path';
+import fs from 'fs';
+
+const MAX_SIZE = 2 * 1024 * 1024; // 2 MB
+
+function getBaseName() {
+  return new Date().toISOString().slice(0, 10);
+}
+function pad(n) { return n.toString().padStart(3, '0'); }
+
+function currentLogFile() {
+  let idx = 0;
+  while (true) {
+    const name = `${getBaseName()}_${pad(idx)}.log`;
+    const p = path.join(LOG_DIR, name);
+    try {
+      const stat = fs.statSync(p);
+      if (stat.size < MAX_SIZE) return p;
+      idx++;
+    } catch {
+      return p; // 文件不存在,直接用它
+    }
+  }
+}
+
+export function writeLog(level, msg) {
+  const line = `[${new Date().toISOString()}] [${level}] ${msg}\n`;
+  fs.appendFileSync(currentLogFile(), line);
+}

+ 33 - 0
src/log/logger.js

@@ -0,0 +1,33 @@
+const isElectron = () =>
+  typeof window !== 'undefined' &&
+  window.require &&
+  window.require('electron')?.ipcRenderer;
+
+let ipcRenderer = null;
+if (isElectron()) {
+  try { ipcRenderer = window.require('electron').ipcRenderer; } catch {}
+}
+
+function proxyLog(level) {
+  const original = console[level];
+  return (...args) => {
+    const msg = args.map(v => typeof v === 'object' ? JSON.stringify(v) : String(v)).join(' ');
+    original(...args);                       // ① 控制台始终打印
+    if (ipcRenderer) {                       // ② Electron 环境
+      ipcRenderer.invoke('write-log', level, msg).catch(() => {});
+    } else if (typeof fetch !== 'undefined') {
+      // ③ 纯 Web 或 pkg-static:发一条异步 POST /log,不阻塞、不报错
+      fetch('/log', {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify({ level, msg })
+      }).catch(() => {}); // 404 也不抛错
+    }
+  };
+}
+
+export const logger = {
+  log: proxyLog('log'),
+  warn: proxyLog('warn'),
+  error: proxyLog('error')
+};