id-scanner-lib
Version:
Browser-based ID card, QR code, and face recognition scanner with liveness detection
669 lines (594 loc) • 16.3 kB
text/typescript
/**
* @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);
}
}
/**
* 日志级别枚举
*/