Files
ChatLab/electron/main/ai/logger.ts
T
2025-12-20 12:47:09 +08:00

179 lines
3.9 KiB
TypeScript

/**
* AI 日志模块
* 将 AI 相关操作日志写入本地文件
*/
import * as fs from 'fs'
import * as path from 'path'
import { app } from 'electron'
// 日志目录
let LOG_DIR: string | null = null
let LOG_FILE: string | null = null
let logStream: fs.WriteStream | null = null
/**
* 获取日志目录
*/
function getLogDir(): string {
if (LOG_DIR) return LOG_DIR
try {
const docPath = app.getPath('documents')
LOG_DIR = path.join(docPath, 'ChatLab', 'logs', 'ai')
} catch {
LOG_DIR = path.join(process.cwd(), 'logs', 'ai')
}
return LOG_DIR
}
/**
* 确保日志目录存在
*/
function ensureLogDir(): void {
const dir = getLogDir()
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true })
}
}
/**
* 获取当前日志文件路径
* 日志文件名格式:ai_YYYY-MM-DD_HH-mm.log
*/
function getLogFilePath(): string {
if (LOG_FILE) return LOG_FILE
ensureLogDir()
const now = new Date()
const date = now.toISOString().split('T')[0]
const hours = String(now.getHours()).padStart(2, '0')
const minutes = String(now.getMinutes()).padStart(2, '0')
LOG_FILE = path.join(getLogDir(), `ai_${date}_${hours}-${minutes}.log`)
return LOG_FILE
}
/**
* 获取日志写入流
*/
function getLogStream(): fs.WriteStream {
if (logStream) return logStream
const filePath = getLogFilePath()
logStream = fs.createWriteStream(filePath, { flags: 'a', encoding: 'utf-8' })
return logStream
}
/**
* 格式化时间戳
*/
function formatTimestamp(): string {
return new Date().toISOString()
}
/**
* 日志级别
*/
type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR'
/**
* 写入日志
* @param level 日志级别
* @param category 分类
* @param message 消息
* @param data 附加数据
* @param toConsole 是否输出到控制台(默认只有 WARN/ERROR 输出)
*/
function writeLog(level: LogLevel, category: string, message: string, data?: any, toConsole: boolean = false): void {
const timestamp = formatTimestamp()
let logLine = `[${timestamp}] [${level}] [${category}] ${message}`
if (data !== undefined) {
try {
const dataStr = typeof data === 'string' ? data : JSON.stringify(data, null, 2)
// 限制数据长度,避免日志过大
const maxLength = 2000
if (dataStr.length > maxLength) {
logLine += `\n${dataStr.slice(0, maxLength)}...[截断,共 ${dataStr.length} 字符]`
} else {
logLine += `\n${dataStr}`
}
} catch {
logLine += `\n[无法序列化的数据]`
}
}
logLine += '\n'
// 写入文件
try {
const stream = getLogStream()
stream.write(logLine)
} catch (error) {
console.error('[AILogger] 写入日志失败:', error)
}
// 只在需要时输出到控制台(WARN/ERROR 或明确指定)
if (toConsole || level === 'WARN' || level === 'ERROR') {
console.log(`[AI] ${message}`)
}
}
/**
* AI 日志对象
*/
export const aiLogger = {
debug(category: string, message: string, data?: any) {
writeLog('DEBUG', category, message, data)
},
info(category: string, message: string, data?: any) {
writeLog('INFO', category, message, data)
},
warn(category: string, message: string, data?: any) {
writeLog('WARN', category, message, data)
},
error(category: string, message: string, data?: any) {
writeLog('ERROR', category, message, data)
},
/**
* 关闭日志流
*/
close() {
if (logStream) {
logStream.end()
logStream = null
}
},
/**
* 获取日志文件路径
*/
getLogPath(): string {
return getLogFilePath()
},
}
// 导出便捷函数
export function logAI(message: string, data?: any) {
aiLogger.info('AI', message, data)
}
export function logLLM(message: string, data?: any) {
aiLogger.info('LLM', message, data)
}
export function logSearch(message: string, data?: any) {
aiLogger.info('Search', message, data)
}
export function logRAG(message: string, data?: any) {
aiLogger.info('RAG', message, data)
}