UNPKG

id-scanner-lib

Version:

Browser-based ID card, QR code, and face recognition scanner with liveness detection

669 lines (594 loc) 16.3 kB
/** * @file 日志系统 * @description 提供统一的日志记录与管理功能 * @module core/logger */ import { ConfigManager } from './config'; /** * 日志级别枚举 */ export enum LoggerLevel { DEBUG = 'debug', INFO = 'info', WARN = 'warn', ERROR = 'error' } /** * @deprecated 使用 LoggerLevel 代替 */ export const LogLevel = LoggerLevel; /** * 日志条目接口 */ export interface LogEntry { /** 日志级别 */ level: LoggerLevel; /** 日志标签 */ tag: string; /** 日志消息 */ message: string; /** 时间戳 */ timestamp: number; /** 额外数据 */ data?: any; /** 错误 */ error?: Error; } /** * 日志处理器接口 */ export interface LogHandler { /** * 处理日志条目 * @param entry 日志条目 */ handle(entry: LogEntry): void; } /** * 控制台日志处理器 * 将日志输出到浏览器控制台 */ export class ConsoleLogHandler implements LogHandler { /** * 处理日志条目 * @param entry 日志条目 */ handle(entry: LogEntry): void { const timestamp = new Date(entry.timestamp).toISOString(); const prefix = `[${timestamp}] [${entry.level.toUpperCase()}] [${entry.tag}]`; // 优先使用 error,其次使用 data const extra = entry.error || entry.data || ''; switch (entry.level) { case LoggerLevel.DEBUG: console.debug(prefix, entry.message, extra); break; case LoggerLevel.INFO: console.info(prefix, entry.message, extra); break; case LoggerLevel.WARN: console.warn(prefix, entry.message, extra); break; case LoggerLevel.ERROR: console.error(prefix, entry.message, extra); break; default: // 输出什么也不做 } } } /** * 内存日志处理器 * 将日志保存在内存中,用于后续分析或显示 */ export class MemoryLogHandler implements LogHandler { /** 日志条目数组 */ private entries: LogEntry[] = []; /** 最大日志条目数 */ private maxEntries: number; /** * 构造函数 * @param maxEntries 最大日志条目数,默认为1000 */ constructor(maxEntries: number = 1000) { this.maxEntries = maxEntries; } /** * 处理日志条目 * @param entry 日志条目 */ handle(entry: LogEntry): void { this.entries.push(entry); // 如果超过最大条目数,移除最老的 if (this.entries.length > this.maxEntries) { this.entries.shift(); } } /** * 获取所有日志条目 */ getEntries(): LogEntry[] { return [...this.entries]; } /** * 根据级别过滤日志条目 * @param level 日志级别 */ getEntriesByLevel(level: LoggerLevel): LogEntry[] { return this.entries.filter(entry => entry.level === level); } /** * 根据标签过滤日志条目 * @param tag 日志标签 */ getEntriesByTag(tag: string): LogEntry[] { return this.entries.filter(entry => entry.tag === tag); } /** * 清空日志 */ clear(): void { this.entries = []; } } /** * 远程日志处理器 * 将日志发送到远程服务器 */ export class RemoteLogHandler implements LogHandler { /** 远程服务器URL */ private endpoint: string; /** 批量发送的队列 */ private queue: LogEntry[] = []; /** 最大队列长度 */ private maxQueueSize: number; /** 发送间隔(毫秒) */ private flushInterval: number; /** 定时发送的计时器ID */ private timerId: ReturnType<typeof setInterval> | null = null; /** 是否在浏览器环境 */ private readonly isBrowser: boolean; /** 最大连续失败次数,超过则丢弃日志 */ private readonly maxConsecutiveFailures: number; /** 当前连续失败计数 */ private consecutiveFailures: number = 0; /** * 构造函数 * @param endpoint 远程服务器URL * @param maxQueueSize 最大队列长度,默认为100 * @param flushInterval 发送间隔(毫秒),默认为5000 */ constructor(endpoint: string, maxQueueSize: number = 100, flushInterval: number = 5000) { this.endpoint = endpoint; this.maxQueueSize = maxQueueSize; this.flushInterval = flushInterval; this.isBrowser = typeof window !== 'undefined' && typeof window.addEventListener === 'function'; this.maxConsecutiveFailures = 10; // 设置定时发送 this.startTimer(); // 页面卸载前尝试发送剩余日志 if (this.isBrowser) { window.addEventListener('beforeunload', () => { this.flush(); }); } } /** * 处理日志条目 * @param entry 日志条目 */ handle(entry: LogEntry): void { // 只处理INFO以上级别的日志 if (entry.level >= LoggerLevel.INFO) { this.queue.push(entry); // 如果队列满了,立即发送 if (this.queue.length >= this.maxQueueSize) { this.flush(); } } } /** * 发送队列中的日志 */ flush(): void { if (this.queue.length === 0) return; // 如果连续失败次数过多,停止发送以防止无限循环 if (this.consecutiveFailures >= this.maxConsecutiveFailures) { console.warn('RemoteLogHandler: Too many consecutive failures, stopping. Clear queue.'); this.queue = []; this.consecutiveFailures = 0; return; } const entriesToSend = [...this.queue]; this.queue = []; this.sendLogEntries(entriesToSend); } /** * 发送日志条目到远程服务器 * @param entries 日志条目数组 */ private sendLogEntries(entries: LogEntry[]): void { if (entries.length === 0) return; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); // 10s 超时 fetch(this.endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(entries), keepalive: true, signal: controller.signal }).then(() => { clearTimeout(timeoutId); this.consecutiveFailures = 0; // 发送成功,重置失败计数 }).catch((err: Error) => { clearTimeout(timeoutId); console.error('Failed to send logs to remote server:', err); this.consecutiveFailures++; // 如果失败次数过多,丢弃日志防止内存泄漏 if (this.consecutiveFailures >= this.maxConsecutiveFailures) { console.warn('RemoteLogHandler: Max consecutive failures exceeded, discarding logs'); this.queue = []; this.consecutiveFailures = 0; return; } // 失败时把日志放回队列,但防止无限增长 const maxReturn = Math.min(entries.length, this.maxQueueSize - this.queue.length); if (maxReturn > 0) { const returnedEntries = entries.slice(0, maxReturn); this.queue = [...returnedEntries, ...this.queue]; } }); } /** * 开始定时发送 */ startTimer(): void { if (!this.isBrowser) return; if (this.timerId !== null) return; this.timerId = setInterval(() => { this.flush(); }, this.flushInterval); } /** * 停止定时发送 */ stopTimer(): void { if (this.timerId !== null) { clearInterval(this.timerId); this.timerId = null; } } } /** * 日志管理类 * 中央日志管理器,提供统一的日志记录接口 */ export class Logger { /** 单例实例 */ private static instance: Logger; /** 配置管理器 */ private config: ConfigManager; /** 日志处理器 */ private handlers: LogHandler[] = []; /** 默认标签 */ private defaultTag: string = 'IDScanner'; /** 日志级别 */ private logLevel: LoggerLevel = LoggerLevel.INFO; /** * 私有构造函数,防止直接实例化 */ private constructor() { this.config = ConfigManager.getInstance(); // 默认添加控制台处理器 this.addHandler(new ConsoleLogHandler()); // 监听配置变化 this.config.onConfigChange('logLevel', (level: LoggerLevel) => { this.debug('Logger', `Log level changed to ${level}`); }); } /** * 获取单例实例 */ public static getInstance(): Logger { if (!Logger.instance) { Logger.instance = new Logger(); } return Logger.instance; } /** * 重置单例实例(主要用于测试) */ public static resetInstance(): void { Logger.instance = undefined as any; } /** * 添加日志处理器 * @param handler 日志处理器 */ addHandler(handler: LogHandler): void { this.handlers.push(handler); } /** * 移除日志处理器 * @param handler 要移除的处理器 */ removeHandler(handler: LogHandler): void { const index = this.handlers.indexOf(handler); if (index !== -1) { this.handlers.splice(index, 1); } } /** * 移除所有处理器 */ clearHandlers(): void { this.handlers = []; } /** * 设置默认标签 * @param tag 默认标签 */ setDefaultTag(tag: string): void { this.defaultTag = tag; } /** * 记录调试级别日志 * @param tag 标签 * @param message 消息 * @param errorOrData 错误对象或结构化数据 */ debug(tag: string, message: string, errorOrData?: Error | object): void { if (errorOrData instanceof Error) { this.log(LoggerLevel.DEBUG, tag, message, errorOrData); } else { this.logWithData(LoggerLevel.DEBUG, tag, message, errorOrData); } } /** * 记录信息级别日志 * @param tag 标签 * @param message 消息 * @param errorOrData 错误对象或结构化数据 */ info(tag: string, message: string, errorOrData?: Error | object): void { if (errorOrData instanceof Error) { this.log(LoggerLevel.INFO, tag, message, errorOrData); } else { this.logWithData(LoggerLevel.INFO, tag, message, errorOrData); } } /** * 记录警告级别日志 * @param tag 标签 * @param message 消息 * @param errorOrData 错误对象或结构化数据 */ warn(tag: string, message: string, errorOrData?: Error | object): void { if (errorOrData instanceof Error) { this.log(LoggerLevel.WARN, tag, message, errorOrData); } else { this.logWithData(LoggerLevel.WARN, tag, message, errorOrData); } } /** * 记录错误级别日志 * @param tag 标签 * @param message 消息 * @param errorOrData 错误对象或结构化数据 */ error(tag: string, message: string, errorOrData?: Error | object): void { if (errorOrData instanceof Error) { this.log(LoggerLevel.ERROR, tag, message, errorOrData); } else { this.logWithData(LoggerLevel.ERROR, tag, message, errorOrData); } } /** * 创建标记了特定标签的日志记录器 * @param tag 标签 */ getTaggedLogger(tag: string): TaggedLogger { return new TaggedLogger(this, tag); } /** * 记录日志 * @param level 日志级别 * @param tag 标签 * @param message 消息 * @param error 错误 */ private log(level: LoggerLevel, tag: string, message: string, error?: Error): void { // 检查日志级别 const levelValue = this.getLevelValue(level); const currentLevelValue = this.getLevelValue(this.logLevel); if (levelValue < currentLevelValue) { return; } // 创建日志条目 const entry: LogEntry = { timestamp: Date.now(), level: level, tag: tag || this.defaultTag, message, error }; // 分发到所有处理程序 for (const handler of this.handlers) { try { handler.handle(entry); } catch (handlerError) { console.error(`[Logger] 处理程序错误:`, handlerError); } } // 如果没有处理程序,使用控制台 if (this.handlers.length === 0) { this.consoleOutput(entry); } } /** * 记录日志(支持结构化数据) * @param level 日志级别 * @param tag 标签 * @param message 消息 * @param data 结构化数据 */ private logWithData(level: LoggerLevel, tag: string, message: string, data?: object): void { // 检查日志级别 const levelValue = this.getLevelValue(level); const currentLevelValue = this.getLevelValue(this.logLevel); if (levelValue < currentLevelValue) { return; } // 创建日志条目 const entry: LogEntry = { timestamp: Date.now(), level: level, tag: tag || this.defaultTag, message, data }; // 分发到所有处理程序 for (const handler of this.handlers) { try { handler.handle(entry); } catch (handlerError) { console.error(`[Logger] 处理程序错误:`, handlerError); } } // 如果没有处理程序,使用控制台 if (this.handlers.length === 0) { this.consoleOutput(entry); } } /** * 控制台输出 * @param entry 日志条目 */ private consoleOutput(entry: LogEntry): void { const timestamp = new Date(entry.timestamp).toISOString(); const prefix = `[${timestamp}] [${entry.level.toUpperCase()}] [${entry.tag}]`; // 构造日志内容:错误对象或数据对象 const extra = entry.error || entry.data || ''; switch (entry.level) { case LoggerLevel.DEBUG: console.debug(`${prefix} ${entry.message}`, extra); break; case LoggerLevel.INFO: console.info(`${prefix} ${entry.message}`, extra); break; case LoggerLevel.WARN: console.warn(`${prefix} ${entry.message}`, extra); break; case LoggerLevel.ERROR: console.error(`${prefix} ${entry.message}`, extra); break; } } /** * 获取日志级别值 * @param level 日志级别 */ private getLevelValue(level: LoggerLevel): number { switch (level) { case LoggerLevel.DEBUG: return 0; case LoggerLevel.INFO: return 1; case LoggerLevel.WARN: return 2; case LoggerLevel.ERROR: return 3; default: return 1; // 默认INFO级别 } } /** * 设置日志级别 * @param level 日志级别 */ public setLevel(level: LoggerLevel | string): void { if (typeof level === 'string') { switch (level) { case 'debug': this.logLevel = LoggerLevel.DEBUG; break; case 'info': this.logLevel = LoggerLevel.INFO; break; case 'warn': this.logLevel = LoggerLevel.WARN; break; case 'error': this.logLevel = LoggerLevel.ERROR; break; default: this.logLevel = LoggerLevel.INFO; } } else { this.logLevel = level; } this.debug('Logger', `日志级别已设置为 ${this.logLevel}`); } /** * 获取当前日志级别 * @returns 当前日志级别 */ public getLevel(): LoggerLevel { return this.logLevel; } } /** * 带标签的日志记录器 * 提供特定标签的简易日志接口 */ export class TaggedLogger { /** 所属的主日志记录器 */ private logger: Logger; /** 标签 */ private tag: string; /** * 构造函数 * @param logger 所属的主日志记录器 * @param tag 标签 */ constructor(logger: Logger, tag: string) { this.logger = logger; this.tag = tag; } /** * 记录调试级别日志 * @param message 消息 * @param error 错误 */ debug(message: string, error?: Error): void { this.logger.debug(this.tag, message, error); } /** * 记录信息级别日志 * @param message 消息 * @param error 错误 */ info(message: string, error?: Error): void { this.logger.info(this.tag, message, error); } /** * 记录警告级别日志 * @param message 消息 * @param error 错误 */ warn(message: string, error?: Error): void { this.logger.warn(this.tag, message, error); } /** * 记录错误级别日志 * @param message 消息 * @param error 错误 */ error(message: string, error?: Error): void { this.logger.error(this.tag, message, error); } } /** * 日志级别枚举 */