网站首页 > 技术文章 正文
一、埋点系统设计与实现(文章最后有如何回答)
1. 埋点分类
1.1 手动埋点(代码埋点)
// 业务代码中主动调用
tracker.track('button_click', {
button_id: 'submit_btn',
page: 'checkout',
timestamp: Date.now()
});
// 封装更友好的API
tracker.clickTracker('submit_btn', 'checkout');
1.2 自动埋点(无痕埋点)
// 全局点击监听
document.addEventListener('click', (e) => {
const target = e.target;
const xpath = getXPath(target); // 生成元素XPath
const data = {
event: 'click',
xpath,
text: target.innerText?.slice(0, 20),
href: target.href,
page: location.pathname,
timestamp: Date.now()
};
tracker.track(data);
}, true);
// 获取元素XPath
function getXPath(element) {
if (element.id) return `//*[@id="${element.id}"]`;
if (element === document.body) return '/html/body';
let ix = 0;
const siblings = element.parentNode.childNodes;
for (let i = 0; i < siblings.length; i++) {
const sibling = siblings[i];
if (sibling === element) {
return `${getXPath(element.parentNode)}/${element.tagName.toLowerCase()}[${ix + 1}]`;
}
if (sibling.nodeType === 1 && sibling.tagName === element.tagName) {
ix++;
}
}
}
1.3 可视化埋点
通过管理后台圈选页面元素
生成对应配置规则
SDK根据配置自动监听指定元素
2. 埋点优化策略
2.1 防抖节流
const throttle = (fn, delay) => {
let last = 0;
return (...args) => {
const now = Date.now();
if (now - last > delay) {
fn.apply(this, args);
last = now;
}
};
};
// 滚动事件节流处理
window.addEventListener('scroll', throttle(() => {
tracker.track('page_scroll', {
scrollY: window.scrollY,
scrollPercent: (window.scrollY / (document.body.scrollHeight - window.innerHeight))
* 100
});
}, 1000));
2.2 数据压缩
// 使用JSON简写格式
const compressData = (data) => {
return {
e: data.event, // event
t: data.timestamp, // timestamp
p: data.page, // page
d: data.data // data
};
};
2.3 本地缓存
class CacheManager {
constructor(maxSize = 50) {
this.maxSize = maxSize;
this.cacheKey = 'monitor_cache';
}
add(data) {
let cache = this.getAll();
cache.push(data);
// 超出限制移除最早的数据
if (cache.length > this.maxSize) {
cache = cache.slice(cache.length - this.maxSize);
}
localStorage.setItem(this.cacheKey, JSON.stringify(cache));
}
getAll() {
const data = localStorage.getItem(this.cacheKey);
return data ? JSON.parse(data) : [];
}
clear() {
localStorage.removeItem(this.cacheKey);
}
}
二、错误监控深度实现
1. JavaScript错误捕获
1.1 同步错误捕获
window.onerror = (message, source, lineno, colno, error) => {
tracker.trackError({
type: 'js_error',
message,
source,
lineno,
colno,
stack: error?.stack,
timestamp: Date.now()
});
};
1.2 异步错误捕获
window.addEventListener('error', (event) => {
// 过滤掉非JS错误(如资源加载错误)
if (event.error) {
tracker.trackError({
type: 'js_error',
message: event.message,
stack: event.error.stack,
timestamp: Date.now()
});
}
}, true);
// Promise未捕获异常
window.addEventListener('unhandledrejection', (event) => {
tracker.trackError({
type: 'promise_error',
message: event.reason?.message || String(event.reason),
stack: event.reason?.stack,
timestamp: Date.now()
});
});
2. 资源加载错误监控
// 方式1:通过error事件捕获
window.addEventListener('error', (e) => {
const target = e.target;
if (target && (target.tagName === 'LINK' || target.tagName === 'SCRIPT' || target.tagName
=== 'IMG')) {
tracker.trackError({
type: 'resource_error',
tag: target.tagName,
url: target.src || target.href,
timestamp: Date.now()
});
}
}, true);
// 方式2:通过PerformanceObserver
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
if (entry.initiatorType !== 'navigation' && entry.duration === 0) {
tracker.trackError({
type: 'resource_error',
url: entry.name,
initiatorType: entry.initiatorType,
timestamp: Date.now()
});
}
});
});
observer.observe({ entryTypes: ['resource'] });
3. API请求错误监控
3.1 Fetch拦截
const originalFetch = window.fetch;
window.fetch = async (...args) => {
const start = Date.now();
try {
const response = await originalFetch(...args);
if (!response.ok) {
tracker.trackError({
type: 'api_error',
url: args[0],
status: response.status,
duration: Date.now() - start,
timestamp: Date.now()
});
}
return response;
} catch (error) {
tracker.trackError({
type: 'api_error',
url: args[0],
status: 0,
message: error.message,
duration: Date.now() - start,
timestamp: Date.now()
});
throw error;
}
};
3.2 XMLHttpRequest拦截
const originalXHROpen = XMLHttpRequest.prototype.open;
const originalXHRSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(method, url) {
this._startTime = Date.now();
this._method = method;
this._url = url;
return originalXHROpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function(body) {
this.addEventListener('loadend', () => {
if (this.status >= 400) {
tracker.trackError({
type: 'api_error',
url: this._url,
method: this._method,
status: this.status,
duration: Date.now() - this._startTime,
timestamp: Date.now()
});
}
});
return originalXHRSend.apply(this, arguments);
};
4. 错误聚合与指纹生成
function generateErrorFingerprint(error) {
// 基础信息
const { message, stack } = error;
// 提取堆栈关键信息
let stackTrace = '';
if (stack) {
const stackLines = stack.split('\n');
// 取前3个堆栈帧
for (let i = 0; i < Math.min(3, stackLines.length); i++) {
const line = stackLines[i];
// 提取文件名和行号
const match = line.match(/\(?(.+):(\d+):(\d+)\)?/);
if (match) {
const file = match[1].split('/').pop();
stackTrace += `${file}:${match[2]}|`;
}
}
}
// 生成指纹
return md5(`${message}|${stackTrace}`);
}
三、性能指标深度采集
1. 关键性能指标采集
1.1 使用Performance API
// 获取Navigation Timing数据
const getNavigationTiming = () => {
const [entry] = performance.getEntriesByType('navigation');
return {
dns: entry.domainLookupEnd - entry.domainLookupStart,
tcp: entry.connectEnd - entry.connectStart,
ssl: entry.secureConnectionStart > 0 ? entry.connectEnd - entry.secureConnectionStart
: 0,
ttfb: entry.responseStart - entry.requestStart,
transfer: entry.responseEnd - entry.responseStart,
domReady: entry.domComplete - entry.domInteractive,
load: entry.loadEventEnd - entry.loadEventStart
};
};
1.2 使用PerformanceObserver
// 监听LCP (Largest Contentful Paint)
const lcpObserver = new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
tracker.trackPerformance({
type: 'LCP',
value: lastEntry.renderTime || lastEntry.loadTime,
timestamp: Date.now()
});
});
lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true });
// 监听CLS (Cumulative Layout Shift)
let clsValue = 0;
let clsEntries = [];
const clsObserver = new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (!entry.hadRecentInput) {
clsEntries.push(entry);
clsValue += entry.value;
}
}
});
clsObserver.observe({ type: 'layout-shift', buffered: true });
// 页面卸载前上报CLS
window.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
tracker.trackPerformance({
type: 'CLS',
value: clsValue,
entries: clsEntries,
timestamp: Date.now()
});
clsValue = 0;
clsEntries = [];
}
});
2. 自定义性能指标
2.1 首屏时间计算
function getFirstScreenTime() {
// 方法1:通过MutationObserver监听DOM变化
return new Promise((resolve) => {
const ignoreTags = ['SCRIPT', 'STYLE', 'LINK', 'META'];
const observer = new MutationObserver(() => {
const viewportHeight = window.innerHeight;
const images = Array.from(document.images);
// 检查首屏内图片是否加载完成
const loaded = images.filter(img => {
const rect = img.getBoundingClientRect();
return rect.top < viewportHeight && img.complete;
});
if (loaded.length === images.length) {
observer.disconnect();
resolve(Date.now() - performance.timing.navigationStart);
}
});
observer.observe(document, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['src']
});
// 超时处理
setTimeout(() => {
observer.disconnect();
resolve(Date.now() - performance.timing.navigationStart);
}, 10000);
});
}
2.2 卡顿检测
// 通过长任务API检测卡顿
const longTaskObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 100) { // 超过100ms视为长任务
tracker.trackPerformance({
type: 'long_task',
duration: entry.duration,
startTime: entry.startTime,
timestamp: Date.now()
});
}
}
});
longTaskObserver.observe({ entryTypes: ['longtask'] });
// 通过FPS检测卡顿
let lastTime = performance.now();
let frameCount = 0;
let fps = 60;
const checkFPS = () => {
frameCount++;
const now = performance.now();
if (now - lastTime >= 1000) {
fps = Math.round((frameCount * 1000) / (now - lastTime));
frameCount = 0;
lastTime = now;
if (fps < 30) { // FPS低于30视为卡顿
tracker.trackPerformance({
type: 'low_fps',
value: fps,
timestamp: Date.now()
});
}
}
requestAnimationFrame(checkFPS);
};
checkFPS();
3. 资源性能分析
// 获取所有资源加载性能数据
const getResourceTiming = () => {
return performance.getEntriesByType('resource').map(resource => ({
name: resource.name,
type: resource.initiatorType,
duration: resource.duration,
size: resource.transferSize,
dns: resource.domainLookupEnd - resource.domainLookupStart,
tcp: resource.connectEnd - resource.connectStart,
ttfb: resource.responseStart - resource.requestStart,
transfer: resource.responseEnd - resource.responseStart
}));
};
// 监听新增资源加载
const resourceObserver = new PerformanceObserver((list) => {
list.getEntries().forEach(resource => {
tracker.trackPerformance({
type: 'resource_load',
name: resource.name,
initiatorType: resource.initiatorType,
duration: resource.duration,
timestamp: Date.now()
});
});
});
resourceObserver.observe({ type: 'resource', buffered: true });
四、高级优化技巧
1. 数据上报优化
1.1 优先级队列
class ReportQueue {
constructor() {
this.highPriority = []; // 错误、关键性能数据
this.lowPriority = []; // 行为数据、非关键指标
this.isSending = false;
}
add(data, priority = 'low') {
if (priority === 'high') {
this.highPriority.push(data);
} else {
this.lowPriority.push(data);
}
this.send();
}
send() {
if (this.isSending) return;
this.isSending = true;
// 优先发送高优先级数据
const data = this.highPriority.length > 0
? this.highPriority.shift()
: this.lowPriority.shift();
if (!data) {
this.isSending = false;
return;
}
fetch('/report', {
method: 'POST',
body: JSON.stringify(data),
keepalive: true
}).finally(() => {
this.isSending = false;
if (this.highPriority.length > 0 || this.lowPriority.length > 0) {
setTimeout(() => this.send(), 0);
}
});
}
}
1.2 Web Worker处理
// 主线程
const worker = new Worker('monitor.worker.js');
worker.postMessage({
type: 'track',
data: {
event: 'click',
// ...其他数据
}
});
// monitor.worker.js
self.onmessage = (e) => {
const { type, data } = e.data;
if (type === 'track') {
// 处理数据并存储
const compressed = compressData(data);
storeData(compressed);
// 批量上报
if (shouldReport()) {
const batch = getBatchData();
sendBatch(batch);
}
}
};
function sendBatch(batch) {
fetch('/report', {
method: 'POST',
body: JSON.stringify(batch)
}).catch(() => {
// 失败后重新放回队列
storeBatch(batch);
});
}
2. 数据采样策略
2.1 分层采样
function getSampleRate(eventType) {
const rates = {
error: 1.0, // 错误全采集
performance: 0.3, // 性能数据30%
click: 0.1, // 点击事件10%
scroll: 0.05 // 滚动事件5%
};
return rates[eventType] || 0.1;
}
function shouldTrack(eventType) {
const rate = getSampleRate(eventType);
return Math.random() < rate;
}
2.2 用户ID哈希采样
function getUserSampleRate(userId) {
// 将用户ID哈希后取模
const hash = md5(userId).substring(0, 8);
const intVal = parseInt(hash, 16);
return intVal % 100 / 100; // 返回0-1之间的值
}
function shouldTrackUser(userId, sampleRate) {
return getUserSampleRate(userId) < sampleRate;
}
3. 数据增强
3.1 设备信息收集
function getDeviceInfo() {
const ua = navigator.userAgent;
const screen = window.screen;
return {
ua,
platform: navigator.platform,
screenWidth: screen.width,
screenHeight: screen.height,
colorDepth: screen.colorDepth,
devicePixelRatio: window.devicePixelRatio || 1,
cpuCores: navigator.hardwareConcurrency || 'unknown',
memory: navigator.deviceMemory || 'unknown',
connection: navigator.connection ? {
effectiveType: navigator.connection.effectiveType,
rtt: navigator.connection.rtt,
downlink: navigator.connection.downlink,
saveData: navigator.connection.saveData
} : 'unknown'
};
}
3.2 会话信息
class Session {
constructor() {
this.sessionId = generateUUID();
this.startTime = Date.now();
this.pageViewCount = 0;
this.lastActivity = Date.now();
}
trackPageView() {
this.pageViewCount++;
this.lastActivity = Date.now();
}
isExpired() {
// 30分钟无活动视为会话结束
return Date.now() - this.lastActivity > 30 * 60 * 1000;
}
renew() {
if (this.isExpired()) {
this.sessionId = generateUUID();
this.startTime = Date.now();
this.pageViewCount = 0;
}
this.lastActivity = Date.now();
}
}
五、实际案例分析
1. SPA应用监控方案
1.1 路由变化监听
// Vue Router示例
router.afterEach((to, from) => {
tracker.trackPageView({
from: from.fullPath,
to: to.fullPath,
duration: Date.now() - tracker.currentPageStartTime
});
tracker.currentPageStartTime = Date.now();
});
// 手动监听history变化
const originalPushState = history.pushState;
history.pushState = function(state, title, url) {
originalPushState.apply(this, arguments);
tracker.trackPageView(url);
};
window.addEventListener('popstate', () => {
tracker.trackPageView(location.pathname);
});
1.2 组件级性能监控
// Vue组件生命周期监控
const componentTracker = {
install(Vue) {
Vue.mixin({
beforeCreate() {
this.$_startTime = Date.now();
},
mounted() {
const duration = Date.now() - this.$_startTime;
tracker.trackPerformance({
type: 'component_mount',
name: this.$options.name || 'anonymous',
duration,
timestamp: Date.now()
});
}
});
}
};
2. 错误定位优化
2.1 SourceMap解析
async function parseErrorStack(error) {
if (!error.stack) return error.stack;
const stackLines = error.stack.split('\n');
const parsedStack = [];
for (const line of stackLines) {
const match = line.match(/at (.+) \((.+):(\d+):(\d+)\)/);
if (match) {
const [, method, file, lineNo, columnNo] = match;
const sourcePos = await sourceMapService.lookup(file, lineNo, columnNo);
parsedStack.push({
method,
file,
line: lineNo,
column: columnNo,
sourceFile: sourcePos?.source,
sourceLine: sourcePos?.line,
sourceColumn: sourcePos?.column
});
} else {
parsedStack.push({ raw: line });
}
}
return parsedStack;
}
2.2 错误上下文收集
function collectErrorContext() {
return {
url: location.href,
route: window.currentRoute, // SPA应用当前路由
userAgent: navigator.userAgent,
localStorageKeys: Object.keys(localStorage),
cookies: document.cookie,
screen: `${window.screen.width}x${window.screen.height}`,
viewport: `${window.innerWidth}x${window.innerHeight}`,
network: navigator.connection?.effectiveType,
memory: navigator.deviceMemory,
// 最近3个用户操作
userActions: tracker.getRecentActions(3),
// 当前页面DOM节点数
domCount: document.getElementsByTagName('*').length
};
}
六、总结与最佳实践
1. 设计原则总结
性能优先:监控系统本身不能成为性能瓶颈
数据准确:确保采集的数据真实可靠
渐进增强:根据用户设备能力动态调整采集策略
故障隔离:监控系统异常不能影响主业务
可扩展性:方便添加新的监控维度
2. 推荐配置示例
const defaultConfig = {
// 错误监控
error: {
enable: true,
sampleRate: 1.0, // 错误全采集
ignore: [/Script error/i], // 忽略某些错误
maxStackDepth: 10 // 堆栈最大深度
},
// 性能监控
performance: {
enable: true,
sampleRate: 0.3,
metrics: ['LCP', 'FID', 'CLS', 'FCP', 'TTFB'],
resourceTiming: true,
longTaskThreshold: 100 // 长任务阈值(ms)
},
// 行为监控
behavior: {
enable: true,
sampleRate: 0.1,
events: ['click', 'scroll', 'input'],
scrollThreshold: 50 // 滚动超过50px才记录
},
// 上报配置
report: {
url: 'https://api.monitor.com/report',
batchSize: 5, // 批量上报条数
interval: 10000, // 上报间隔(ms)
retryTimes: 3, // 失败重试次数
useBeacon: true // 是否使用sendBeacon
},
// 调试模式
debug: false
};
3. 持续优化方向
智能采样:基于用户价值动态调整采样率
异常预测:通过机器学习识别潜在问题
前端追踪:实现完整的分布式追踪
性能洞察:提供可操作的性能优化建议
隐私保护:加强数据脱敏和合规处理
通过以上详细实现方案,可以构建一个功能完善、性能优异的前端监控SDK,满足各种复杂场景下的监控需求。在面试中,可以根据面试官的问题选择适当的深度进行讲解,同时结合自己的实际项目经验,展示解决具体问题的能力。
面试怎么说?
一、核心要点概述(1分钟)
"我设计的前端监控SDK主要解决三个核心问题:
全面错误监控:覆盖JS运行时、资源加载、API请求等各类异常
性能指标采集:实现Web Vitals标准指标和业务自定义指标
用户行为追踪:通过手动/自动埋点记录关键交互路径
整体设计遵循轻量级、低侵入原则,SDK体积控制在15KB以内,通过智能采样和分级上报保证系统稳定性。"
二、关键模块精讲(3分钟)
1. 错误监控
"错误系统实现了:
同步错误通过window.onerror捕获
异步错误通过unhandledrejection处理
资源错误监听元素error事件
API错误拦截fetch/XHR
为每个错误生成唯一指纹,附加设备、路由等上下文"
2. 性能监控
"性能模块重点采集:
加载指标:FP/FCP/LCP,交互指标:FID,视觉稳定性:CLS
通过PerformanceObserver监听长任务
针对SPA应用优化路由切换时的测量"
3. 埋点设计
"埋点系统采用分层策略:关键业务点手动埋点,通用交互自动采集,通过防抖节流优化性能,上报采用优先级队列,错误数据优先发送"
三、技术亮点(2分钟)
"解决的主要技术难点包括:
SPA监控:重写路由API确保页面跳转准确追踪
数据可靠性:本地缓存+失败重试机制
性能平衡:动态采样和懒加载机制
错误分析:SourceMap解析和错误聚合"
四、实践案例(1分钟)
"在实际项目中,通过这套系统:
发现并解决了第三方SDK导致的长任务卡顿
将LCP指标优化了30%
错误排查时间缩短50%"
五、总结(30秒)
"这套监控系统帮助我们建立了完善的前端可观测体系,后续计划加入智能告警和全链路追踪功能。"
猜你喜欢
- 2025-09-29 JAVA时间存储类Period和Duration_java时间格式类型
- 2025-09-29 办公小技巧:定时提醒不慌张 Excel制作智能提醒器
- 2025-09-29 Excel中14个常用的日期与时间函数,动画演示,中文解读
- 2025-09-29 MongoDB GPS 轨迹数据存储与查询设计指南
- 2025-09-29 前端性能优化笔记之首屏时间采集指标的具体方法
- 2025-09-29 日期函数(一)_日期运算函数
- 2025-09-29 告别跳转卡顿!微信小程序页面路由性能优化实战
- 2025-09-29 怎样快速提取单元格中的出生日期?用「Ctrl+E」批量搞定
- 2025-09-29 Excel日期函数应用详解_excel中日期时间函数
- 2025-09-29 前端日志回捞系统的性能优化实践|得物技术
- 最近发表
- 标签列表
-
- cmd/c (90)
- c++中::是什么意思 (84)
- 标签用于 (71)
- 主键只能有一个吗 (77)
- c#console.writeline不显示 (95)
- pythoncase语句 (88)
- es6includes (74)
- sqlset (76)
- apt-getinstall-y (100)
- node_modules怎么生成 (87)
- chromepost (71)
- flexdirection (73)
- c++int转char (80)
- mysqlany_value (79)
- static函数和普通函数 (84)
- el-date-picker开始日期早于结束日期 (76)
- js判断是否是json字符串 (75)
- c语言min函数头文件 (77)
- asynccallback (87)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- 无效的列索引 (74)