前端日志系统实现 📝
引言
前端日志系统是应用监控和问题诊断的重要工具。本文将深入探讨前端日志系统的设计与实现,包括日志收集、处理、存储和分析等方面,帮助开发者构建完整的前端日志解决方案。
日志系统概述
前端日志系统主要包括以下方面:
- 日志收集:用户行为、性能指标、错误信息等
- 日志处理:过滤、格式化、压缩等
- 日志存储:本地存储、远程上传等
- 日志分析:统计分析、可视化展示等
- 实时监控:告警、通知等
日志系统实现
日志管理器
// 日志管理器类
class LogManager {private static instance: LogManager;private config: LogConfig;private logQueue: LogEntry[];private timer: number | null;private storage: Storage;private constructor() {this.config = {appId: '',appVersion: '',maxQueueSize: 100,flushInterval: 5000,logLevel: LogLevel.INFO,uploadUrl: '',enableConsole: true,enableStorage: true,maxStorageSize: 5 * 1024 * 1024 // 5MB};this.logQueue = [];this.timer = null;this.storage = new Storage('logs', this.config.maxStorageSize);this.initialize();}// 获取单例实例static getInstance(): LogManager {if (!LogManager.instance) {LogManager.instance = new LogManager();}return LogManager.instance;}// 初始化日志管理器initialize(config?: Partial<LogConfig>): void {if (config) {this.config = { ...this.config, ...config };}// 加载本地存储的日志if (this.config.enableStorage) {this.loadStoredLogs();}// 启动定时上传this.startAutoUpload();// 注册页面卸载事件window.addEventListener('beforeunload', () => {this.flush();});}// 记录日志log(level: LogLevel,message: string,data?: any,tags?: string[]): void {// 检查日志级别if (level < this.config.logLevel) {return;}const logEntry = this.createLogEntry(level, message, data, tags);// 输出到控制台if (this.config.enableConsole) {this.printToConsole(logEntry);}// 添加到队列this.addToQueue(logEntry);}// 创建日志条目private createLogEntry(level: LogLevel,message: string,data?: any,tags?: string[]): LogEntry {return {appId: this.config.appId,appVersion: this.config.appVersion,timestamp: Date.now(),level,message,data,tags,url: window.location.href,userAgent: navigator.userAgent};}// 添加到日志队列private addToQueue(entry: LogEntry): void {this.logQueue.push(entry);// 保存到本地存储if (this.config.enableStorage) {this.storage.append(entry);}// 队列超出限制时立即上传if (this.logQueue.length >= this.config.maxQueueSize) {this.flush();}}// 输出到控制台private printToConsole(entry: LogEntry): void {const { level, message, data } = entry;const timestamp = new Date(entry.timestamp).toISOString();const style = this.getConsoleStyle(level);const prefix = `%c[${timestamp}][${LogLevel[level]}]`;if (data) {console.log(prefix, style, message, data);} else {console.log(prefix, style, message);}}// 获取控制台样式private getConsoleStyle(level: LogLevel): string {switch (level) {case LogLevel.ERROR:return 'color: #ff4444; font-weight: bold';case LogLevel.WARN:return 'color: #ffbb33; font-weight: bold';case LogLevel.INFO:return 'color: #33b5e5';case LogLevel.DEBUG:return 'color: #999999';default:return '';}}// 启动自动上传private startAutoUpload(): void {if (this.timer !== null) {return;}this.timer = window.setInterval(() => {this.flush();}, this.config.flushInterval);}// 停止自动上传private stopAutoUpload(): void {if (this.timer === null) {return;}window.clearInterval(this.timer);this.timer = null;}// 立即上传日志async flush(): Promise<void> {if (this.logQueue.length === 0) {return;}const logs = [...this.logQueue];this.logQueue = [];try {await this.uploadLogs(logs);// 清理已上传的本地存储日志if (this.config.enableStorage) {this.storage.clear();}} catch (error) {console.error('Failed to upload logs:', error);// 重新加入队列this.logQueue.push(...logs);}}// 上传日志到服务器private async uploadLogs(logs: LogEntry[]): Promise<void> {const response = await fetch(this.config.uploadUrl, {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify(logs)});if (!response.ok) {throw new Error('Failed to upload logs');}}// 加载存储的日志private loadStoredLogs(): void {const logs = this.storage.getAll();this.logQueue.push(...logs);}
}// 日志级别枚举
enum LogLevel {DEBUG,INFO,WARN,ERROR
}// 日志配置接口
interface LogConfig {appId: string;appVersion: string;maxQueueSize: number;flushInterval: number;logLevel: LogLevel;uploadUrl: string;enableConsole: boolean;enableStorage: boolean;maxStorageSize: number;
}// 日志条目接口
interface LogEntry {appId: string;appVersion: string;timestamp: number;level: LogLevel;message: string;data?: any;tags?: string[];url: string;userAgent: string;
}// 本地存储类
class Storage {private key: string;private maxSize: number;constructor(key: string, maxSize: number) {this.key = key;this.maxSize = maxSize;}// 追加日志append(entry: LogEntry): void {const logs = this.getAll();logs.push(entry);// 检查存储大小while (this.getStorageSize(logs) > this.maxSize) {logs.shift();}localStorage.setItem(this.key, JSON.stringify(logs));}// 获取所有日志getAll(): LogEntry[] {const data = localStorage.getItem(this.key);return data ? JSON.parse(data) : [];}// 清空日志clear(): void {localStorage.removeItem(this.key);}// 获取存储大小private getStorageSize(data: any): number {return new Blob([JSON.stringify(data)]).size;}
}// 使用示例
const logger = LogManager.getInstance();// 初始化日志系统
logger.initialize({appId: 'my-app',appVersion: '1.0.0',uploadUrl: '/api/logs',logLevel: LogLevel.DEBUG
});// 记录不同级别的日志
logger.log(LogLevel.DEBUG, 'Debug message', { detail: 'debug info' });
logger.log(LogLevel.INFO, 'Info message', { user: 'John' });
logger.log(LogLevel.WARN, 'Warning message', null, ['auth']);
logger.log(LogLevel.ERROR, 'Error message', new Error('Something went wrong'));
性能监控
// 性能监控类
class PerformanceMonitor {private static instance: PerformanceMonitor;private logger: LogManager;private metrics: Map<string, number>;private constructor() {this.logger = LogManager.getInstance();this.metrics = new Map();this.initialize();}// 获取单例实例static getInstance(): PerformanceMonitor {if (!PerformanceMonitor.instance) {PerformanceMonitor.instance = new PerformanceMonitor();}return PerformanceMonitor.instance;}// 初始化监控器private initialize(): void {// 监听性能时间this.observePerformanceTimings();// 监听资源加载this.observeResourceTimings();// 监听长任务this.observeLongTasks();// 监听首次绘制this.observePaintTimings();}// 开始计时startTimer(name: string): void {this.metrics.set(name, performance.now());}// 结束计时endTimer(name: string): void {const startTime = this.metrics.get(name);if (startTime) {const duration = performance.now() - startTime;this.metrics.delete(name);this.logger.log(LogLevel.INFO, `Timer: ${name}`, {duration,type: 'timer'});}}// 记录自定义指标recordMetric(name: string,value: number,tags?: string[]): void {this.logger.log(LogLevel.INFO, `Metric: ${name}`, {value,type: 'metric'}, tags);}// 监听性能时间private observePerformanceTimings(): void {window.addEventListener('load', () => {// 等待所有资源加载完成setTimeout(() => {const timing = performance.timing;const metrics = {dns: timing.domainLookupEnd - timing.domainLookupStart,tcp: timing.connectEnd - timing.connectStart,request: timing.responseEnd - timing.requestStart,response: timing.responseEnd - timing.responseStart,dom: timing.domComplete - timing.domLoading,load: timing.loadEventEnd - timing.navigationStart};Object.entries(metrics).forEach(([name, value]) => {this.recordMetric(name, value, ['timing']);});}, 0);});}// 监听资源加载private observeResourceTimings(): void {const observer = new PerformanceObserver((list) => {list.getEntries().forEach(entry => {if (entry.entryType === 'resource') {const resource = entry as PerformanceResourceTiming;this.logger.log(LogLevel.INFO, `Resource: ${resource.name}`, {duration: resource.duration,transferSize: resource.transferSize,type: resource.initiatorType}, ['resource']);}});});observer.observe({ entryTypes: ['resource'] });}// 监听长任务private observeLongTasks(): void {const observer = new PerformanceObserver((list) => {list.getEntries().forEach(entry => {if (entry.entryType === 'longtask') {this.logger.log(LogLevel.WARN, 'Long task detected', {duration: entry.duration,type: 'longtask'});}});});observer.observe({ entryTypes: ['longtask'] });}// 监听首次绘制private observePaintTimings(): void {const observer = new PerformanceObserver((list) => {list.getEntries().forEach(entry => {if (entry.entryType === 'paint') {this.recordMetric(entry.name, entry.startTime, ['paint']);}});});observer.observe({ entryTypes: ['paint'] });}
}// 使用示例
const monitor = PerformanceMonitor.getInstance();// 记录自定义计时
monitor.startTimer('operation');
// ... 执行操作
monitor.endTimer('operation');// 记录自定义指标
monitor.recordMetric('memory_usage', performance.memory?.usedJSHeapSize || 0);
用户行为跟踪
// 用户行为跟踪类
class UserBehaviorTracker {private static instance: UserBehaviorTracker;private logger: LogManager;private sessionId: string;private pageStartTime: number;private constructor() {this.logger = LogManager.getInstance();this.sessionId = this.generateSessionId();this.pageStartTime = Date.now();this.initialize();}// 获取单例实例static getInstance(): UserBehaviorTracker {if (!UserBehaviorTracker.instance) {UserBehaviorTracker.instance = new UserBehaviorTracker();}return UserBehaviorTracker.instance;}// 初始化跟踪器private initialize(): void {// 记录页面访问this.trackPageView();// 监听用户交互this.trackUserInteractions();// 监听页面可见性this.trackPageVisibility();// 监听页面离开this.trackPageLeave();}// 生成会话IDprivate generateSessionId(): string {return `${Date.now()}-${Math.random().toString(36).slice(2)}`;}// 跟踪页面访问private trackPageView(): void {this.logger.log(LogLevel.INFO, 'Page view', {sessionId: this.sessionId,title: document.title,referrer: document.referrer,type: 'pageview'});}// 跟踪用户交互private trackUserInteractions(): void {// 点击事件document.addEventListener('click', (event) => {const target = event.target as HTMLElement;this.logger.log(LogLevel.INFO, 'User click', {sessionId: this.sessionId,element: target.tagName.toLowerCase(),id: target.id,class: target.className,text: target.textContent?.slice(0, 100),type: 'click'});});// 表单提交document.addEventListener('submit', (event) => {const form = event.target as HTMLFormElement;this.logger.log(LogLevel.INFO, 'Form submit', {sessionId: this.sessionId,formId: form.id,action: form.action,type: 'form'});});// 页面滚动let scrollTimeout: number;window.addEventListener('scroll', () => {clearTimeout(scrollTimeout);scrollTimeout = window.setTimeout(() => {const scrollDepth = Math.round((window.scrollY + window.innerHeight) /document.documentElement.scrollHeight * 100);this.logger.log(LogLevel.INFO, 'Page scroll', {sessionId: this.sessionId,depth: scrollDepth,type: 'scroll'});}, 500);});}// 跟踪页面可见性private trackPageVisibility(): void {document.addEventListener('visibilitychange', () => {const isVisible = document.visibilityState === 'visible';this.logger.log(LogLevel.INFO, 'Visibility change', {sessionId: this.sessionId,visible: isVisible,type: 'visibility'});if (!isVisible) {this.trackEngagementTime();}});}// 跟踪页面离开private trackPageLeave(): void {window.addEventListener('beforeunload', () => {this.trackEngagementTime();});}// 跟踪页面参与时间private trackEngagementTime(): void {const engagementTime = Date.now() - this.pageStartTime;this.logger.log(LogLevel.INFO, 'Engagement time', {sessionId: this.sessionId,duration: engagementTime,type: 'engagement'});}// 跟踪自定义事件trackEvent(category: string,action: string,label?: string,value?: number): void {this.logger.log(LogLevel.INFO, 'Custom event', {sessionId: this.sessionId,category,action,label,value,type: 'event'});}
}// 使用示例
const tracker = UserBehaviorTracker.getInstance();// 跟踪自定义事件
tracker.trackEvent('video', 'play', 'intro-video', 30);
最佳实践与建议
-
日志设计
- 分级管理
- 结构化数据
- 采样控制
- 安全考虑
-
性能优化
- 批量处理
- 压缩数据
- 限制频率
- 本地缓存
-
数据处理
- 过滤敏感信息
- 数据清洗
- 聚合分析
- 实时监控
-
存储策略
- 分级存储
- 定期清理
- 容量控制
- 备份恢复
总结
前端日志系统需要考虑以下方面:
- 日志收集与处理
- 性能监控与分析
- 用户行为跟踪
- 数据存储与管理
- 安全性与隐私
通过完善的日志系统,可以更好地监控和优化前端应用。
学习资源
- 日志系统设计指南
- 性能监控最佳实践
- 用户行为分析方法
- 数据可视化工具
- 监控平台搭建
如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻