工程化与框架系列(30)--前端日志系统实现
前端日志系统实现 📝
引言
前端日志系统是应用监控和问题诊断的重要工具。本文将深入探讨前端日志系统的设计与实现,包括日志收集、处理、存储和分析等方面,帮助开发者构建完整的前端日志解决方案。
日志系统概述
前端日志系统主要包括以下方面:
- 日志收集:用户行为、性能指标、错误信息等
- 日志处理:过滤、格式化、压缩等
- 日志存储:本地存储、远程上传等
- 日志分析:统计分析、可视化展示等
- 实时监控:告警、通知等
日志系统实现
日志管理器
// 日志管理器类
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();
}
// 生成会话ID
private 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);
最佳实践与建议
-
日志设计
- 分级管理
- 结构化数据
- 采样控制
- 安全考虑
-
性能优化
- 批量处理
- 压缩数据
- 限制频率
- 本地缓存
-
数据处理
- 过滤敏感信息
- 数据清洗
- 聚合分析
- 实时监控
-
存储策略
- 分级存储
- 定期清理
- 容量控制
- 备份恢复
总结
前端日志系统需要考虑以下方面:
- 日志收集与处理
- 性能监控与分析
- 用户行为跟踪
- 数据存储与管理
- 安全性与隐私
通过完善的日志系统,可以更好地监控和优化前端应用。
学习资源
- 日志系统设计指南
- 性能监控最佳实践
- 用户行为分析方法
- 数据可视化工具
- 监控平台搭建
如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻