chore: 添加应用诊断日志

This commit is contained in:
Wxw-Gu
2026-07-23 16:09:20 +08:00
parent 0d544275c0
commit 62f729d281
9 changed files with 258 additions and 10 deletions
+83
View File
@@ -0,0 +1,83 @@
import { app, shell } from 'electron'
import fs from 'fs-extra'
import path from 'path'
import type { AppLogEntry } from '../shared/app-log'
const MAX_LOG_BYTES = 5 * 1024 * 1024
const REDACTED_KEY = /(?:api[-_]?key|authorization|token|secret|password|database[-_]?key)/i
const sanitize = (value: unknown, depth = 0): unknown => {
if (depth > 4) return '[depth-limited]'
if (typeof value === 'string') {
return value
.replace(/\bsk-[a-z0-9_-]{8,}\b/gi, '***')
.replace(/\bBearer\s+[a-z0-9._~-]{8,}\b/gi, 'Bearer ***')
.slice(0, 2000)
}
if (Array.isArray(value)) return value.slice(0, 30).map((item) => sanitize(item, depth + 1))
if (value && typeof value === 'object') {
return Object.fromEntries(
Object.entries(value as Record<string, unknown>).map(([key, item]) => [
key,
REDACTED_KEY.test(key) ? '***' : sanitize(item, depth + 1)
])
)
}
return value
}
export class AppLogger {
private get logDir(): string {
return app.getPath('logs')
}
get logPath(): string {
return path.join(this.logDir, 'wechatexplorer.log')
}
private rotateIfNeeded(): void {
try {
if (!fs.existsSync(this.logPath) || fs.statSync(this.logPath).size < MAX_LOG_BYTES) return
const previous = `${this.logPath}.1`
if (fs.existsSync(previous)) fs.removeSync(previous)
fs.moveSync(this.logPath, previous)
} catch {
// Logging must never interrupt the application.
}
}
write(entry: AppLogEntry): void {
try {
fs.ensureDirSync(this.logDir)
this.rotateIfNeeded()
const record = {
timestamp: new Date().toISOString(),
mode: app.isPackaged ? 'packaged' : 'development',
level: entry.level,
scope: String(entry.scope || 'app').slice(0, 80),
message: String(entry.message || '').slice(0, 500),
details: sanitize(entry.details || {})
}
fs.appendFileSync(this.logPath, `${JSON.stringify(record)}\n`, { encoding: 'utf8' })
if (!app.isPackaged) {
const method =
entry.level === 'error'
? console.error
: entry.level === 'warn'
? console.warn
: console.log
method(`[${record.scope}] ${record.message}`, record.details)
}
} catch {
// Logging must never interrupt the application.
}
}
reveal(): void {
fs.ensureDirSync(this.logDir)
if (!fs.existsSync(this.logPath)) fs.writeFileSync(this.logPath, '', 'utf8')
shell.showItemInFolder(this.logPath)
}
}
export const appLogger = new AppLogger()
+30
View File
@@ -69,6 +69,8 @@ import {
} from './services/bootstrap-cache'
import { installSafeConsole } from './safe-log'
import { agentHubService } from './services/agent-hub-service'
import { appLogger } from './app-logger'
import type { AppLogEntry } from '../shared/app-log'
// electron-vite can close the child's stdout/stderr after spawning Electron.
// Plain console.error then throws EPIPE on a closed pipe and crashes the IPC
@@ -141,6 +143,31 @@ function createWindow(): void {
// 鏌愪簺 API 鍙兘鍦ㄦ浜嬩欢鍙戠敓鍚庝娇鐢?
app.whenReady().then(async () => {
console.log(`WechatExplorer main build: ${BUILD_MARK}`)
appLogger.write({
level: 'info',
scope: 'lifecycle',
message: 'WechatExplorer 启动',
details: { build: BUILD_MARK, platform: process.platform, version: app.getVersion() }
})
process.on('uncaughtException', (error) => {
appLogger.write({
level: 'error',
scope: 'main-process',
message: error.message,
details: { stack: error.stack }
})
})
process.on('unhandledRejection', (reason) => {
appLogger.write({
level: 'error',
scope: 'main-process',
message: reason instanceof Error ? reason.message : 'Promise 未处理拒绝',
details: {
stack: reason instanceof Error ? reason.stack : undefined,
reason: reason instanceof Error ? undefined : String(reason)
}
})
})
// WCDB's Windows runtime returns -1006 if wcdb_init is called more than once
// per process. Bootstrap native once here so any later Wcdb4Client instance
@@ -166,6 +193,9 @@ app.whenReady().then(async () => {
// IPC test
ipcMain.on('ping', () => console.log('pong'))
ipcMain.handle('app-log:write', (_, entry: AppLogEntry) => appLogger.write(entry))
ipcMain.handle('app-log:getPath', () => appLogger.logPath)
ipcMain.handle('app-log:reveal', () => appLogger.reveal())
ipcMain.handle('db:init', async (_, key: string) => {
if (dbInitInFlight) return dbInitInFlight