首页 > 应用开发 > Html/css > 如何进行页面前端监控
下面我来分别介绍三类指标如何获取
Performance API中的
performance.timing对象。PerformanceObserver来监听
paint条目。PerformanceObserver来监听
largest-contentful-paint条目。EventListeners结合
PerformanceObserver。PerformanceObserver来监听
layout-shift条目。PerformanceObserver来监听
longtask条目。通过这种方式,可以捕获并计算页面在加载过程中所有长任务的阻塞时间。Long Tasks API和其他指标来推测。以下是一个简单的示例代码,展示如何使用
Performance API和
PerformanceObserver来测量这些指标:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 |
//
页面加载时间window.addEventListener(
'load'
, () => {
const timing = performance.timing;
const pageLoadTime = rank.chinaz.comos.ntxzk.cn timing.loadEventEnd - timing.navigationStart;
console.log(`Page Load Time: ${pageLoadTime} ms`);});//
内容绘制和最大内容绘制const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach((entry) => {
if
(entry.name ===
'first-contentful-paint'
) {
console.log(`First Contentful Paint: ${entry.startTime} ms`);
}
if
(entry.entryType ===
'largest-contentful-paint'
) {
console.log(`Largest Contentful Paint: ${entry.startTime} ms`);
}
});});observer.observe({
type
:
'paint'
, buffered:
true
});observer.observe({
type
:
'largest-contentful-paint'
, buffered:
true
});//
输入延迟let
firstInputDelay = 0;const fidObserver = sa.ntxzk.cn new PerformanceObserver((list) => {
const entries = list.getEntries();
for
(const entry of entries) {
firstInputDelay = entry.processingStart - entry.startTime;
console.log(`First Input Delay: ${firstInputDelay} ms`);
}});fidObserver.observe({
type
:
'first-input'
, buffered:
true
});//
累积布局偏移let
cumulativeLayoutShiftScore = 0;const clsObserver = new PerformanceObserver((list) => {
for
(const entry of list.getEntries()) {
if
(!entry.hadRecentInput) {
cumulativeLayoutShiftScore += entry.value;
console.log(`Cumulative 4ed8
Layout Shift: ${cumulativeLayoutShiftScore}`);
}
}});clsObserver.observe({
type
:
'layout-shift'
, buffered:
true
});//
长任务const ttiObserver = new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
entries.forEach(entry => {
console.log(`Long Task detected: ${entry.startTime} ms, duration: ${entry.duration} ms`);
});});ttiObserver.observe({ entryTypes: [
'longtask'
] }); |
总结:上面的指标
performance.timing主要是采集一些基础的时间,目前都是前后端分离的项目参考意义已经不大,除非是ssr和服务端的项目这部分指标有一些参考价值。像最大内容绘制largest-contentful-paint 是一个持续变动的指标,最后计算出最大时间,位移数据也是如此,长任务也是如此持续监听过程。
在前端监控中资源监控方面,通常使用的是浏览器提供的 Performance API。这个 API 提供了一组接口,对于资源监控,具体来说,可以使用以下接口:
PerformanceObserver,你可以监听到资源加载事件(
resource类型),并在资源加载完成时获取到相应的
PerformanceResourceTiming对象。以下是一个简单的示例,展示如何使用
PerformanceObserver来监控资源加载:
|
1
2
3
4
5
6
7
8
9
10
11
12
13 |
if
(
'PerformanceObserver'
in
window) {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntriesByType(
'resource'
);
entries.forEach((entry) => {
console.log(`Resource: ${entry.name}`);
console.log(`Start Time: ${entry.startTime}`);
console.log(`Duration: ${entry.duration}`);
console.log(`Initiator Type: ${entry.initiatorType}`);
});
});
observer.observe({
type
:
'resource'
, buffered:
true
});} |
总结:他可以对页面所有的资源加载进行监控,比如
js,css,图片还能够对
xmlhttprequest和
fetch监控。也就是说他能够对接口进行监控,当我们的页面有一些超长的请求可以单独识别出来进行报警。
下面是对请求接口的监控:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 |
if
(
'PerformanceObserver'
in
window) {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntriesByType(
'resource'
);
entries.forEach((entry) => {
if
(entry.initiatorType ===
'xmlhttprequest'
|| le.leleypay.cn entry.initiatorType ===
'fetch'
) {
console.log(`API Request: ${entry.name}`);
console.log(`Start Time: ${entry.startTime}`);
console.log(`Duration: ${entry.duration}`);
console.log(`Fetch Start: ${entry.fetchStart}`);
console.log(`Response End: ${entry.responseEnd}`);
console.log(`Transfer Size: ${entry.transferSize}`);
}
});
});
observer.observe({
type
:
'resource'
, buffered:
true
});} |
监听形式主要是通过捕获全局错误事件来实现。这种方式可以捕获大部分未处理的异常,并可以将其记录或发送到服务器进行分析。
window.onerror
window.onerror是最传统的异常捕获方法,可以捕获运行时的 JavaScript 错误。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 |
window.>
console.error(
'Error caught:'
, message,
source
, lineno, colno, error);
//
可以在这里将错误信息发送到服务器
sendErrorToServer({
message,
source
,
lineno,
colno,
error: error ? error.stack : null
});};function
sendErrorToServer(errorInfo) {
fetch(
'/log-error'
, {
method:
'POST'
,
headers: {
'Content-Type'
:
'application/json'
},
body: JSON.stringify(errorInfo)
});} |
window.addEventListener('error')这种方法可以捕获资源加载错误(如图片、脚本加载失败)。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 |
window.addEventListener(
'error'
,
function
(event) {
console.error(
'Resource Error:'
, event);
//
处理资源加载错误
if
(event.target instanceof HTMLImageElement) {
console.error(
'Image failed to load:'
, event.target.src);
}
//
可以在这里将错误信息发送到服务器
sendErrorToServer({
message: event.message,
source
: event.filename,
lineno: event.lineno,
colno: event.colno,
error: event.error ? event.error.stack : null
});},
true
); |
window.addEventListener('unhandledrejection')捕获未处理的 Promise 异常,此处主要用来捕获Promise没有被catch的异常,属于最后的兜底。
|
1
2
3
4
5
6
7
8 |
window.addEventListener(
'unhandledrejection'
,
function
(event) {
console.error(
'Unhandled promise rejection:'
, event.reason);
//
可以在这里将错误信息发送到服务器
sendErrorToServer({
message: event.reason ? event.reason.toString() :
'Unknown reason'
,
error: event.reason && event.reason.stack ? event.reason.stack : null
});}); |
主动上报形式是指在代码中手动捕获异常并上报。适用于需要在特定逻辑中捕获异常的场景。
try...catch在代码中使用
try...catch来捕获异常,并在
catch块中进行上报。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 |
function
riskyOperation() {
try {
//
执行可能抛出异常的代码
let
result = potentiallyFailingFunction();
console.log(
'Operation successful:'
, result);
} catch (error) {
console.error(
'Caught an error:'
, error);
//
在这里上报错误
sendErrorToServer({
message: error.message,
error: error.stack
});
}}function
potentiallyFailingFunction() {
//
模拟可能抛出异常的代码
if
(Math.random() > 0.5) {
throw new Error(
'Random failure'
);
}
return
'Success'
;} |
在现代前端框架(如 React、Vue)中,可以利用框架的错误边界或错误处理机制来捕获和上报异常。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 |
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError:
false
};
}
static getDerivedStateFromError(error) {
return
{ hasError:
true
};
}
componentDidCatch(error, errorInfo) {
console.error(
'ErrorBoundary caught an error:'
, error, errorInfo);
//
在这里上报错误
sendErrorToServer({
message: error.message,
error: error.stack,
componentStack: errorInfo.componentStack
});
}
render() {
if
(this.state.hasError) {
return
<h1>Something went wrong.<
/h1
>;
}
return
this.props.children;
}}//
使用 ErrorBoundary 包裹组件<ErrorBoundary>
<YourComponent /><
/ErrorBoundary
> |
总结:我本人更加倾向于主动上报形式,因为在现在的开发形式里面,编译工具和开发工具能够帮我们避免绝大多数的异常情况,我们的监控往往因为业务逻辑和接口数据返回不对导致对业务的影响,所以主动上报能够更加准确和清晰的给出异常,这样利于我们去监控,往往比较多的异常问题不是错误导致更多就是接口不符合预期这样
window.onerror形式不太全,最多只能当一个辅助使用。
业务指标跟踪也是前端监控的一部分,主要用于收集和分析用户在应用中的行为数据,以帮助理解用户的使用模式、评估功能效果、优化用户体验等。常见的业务指标包括页面访问量、点击事件、用户停留时间、转化率等,很多公司都是单独做,我所在的公司就是这样,其实原理很简单就是调用一个接口把想要上报的数据给带过去就行。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 |
let
clickCount = 0;//
获取按钮元素const button = document.getElementById(
'trackButton'
);//
为按钮添加点击事件监听器button.addEventListener(
'click'
, () => {
clickCount++;
console.log(
'按钮点击次数:'
, clickCount);});//
页面卸载时发送数据window.addEventListener(
'beforeunload'
, () => {
const data = JSON.stringify({ mpay.leleypay.cn clickCount });
//
使用 sendBeacon 发送数据,确保在页面卸载时也能发送成功
if
(navigator.sendBeacon) {
navigator.sendBeacon(
'/track-clicks'
, data);
}
else
{
//
备用方案,使用 fetch 发送数据
fetch(
'/track-clicks'
, {
method:
'POST'
,
headers: {
'Content-Type'
:
'application/json'
},
body: data
});
}}); |
有个问题,为什么用
navigator.sendBeacon去发送数据 ?这就涉及到如何上报数据的问题,下面我具体讲下。
XMLHttpRequest,它提供了更简洁的语法,但在页面卸载时仍可能会被中断。sendBeacon是非阻塞的,确保数据在页面关闭时也能可靠地发送。src属性来发送 GET 请求。这种方法常用于简单的数据上报,不会受到跨域限制,但只能发送少量数据。XMLHttpRequest和
Fetch没有什么太大本质的区别属于一类,他们都是比较常规的上报形式,但是他有个致命问题可能会被中断,导致数据丢失,特别在页面跳出的情况,还有就是在请求里面优先级高,在页面加载的前期当有大量的上报的时候会阻塞页面的请求看下图:Image Ping图片上报,在跨域和调用方式上比较好,但是有个致命的缺点数据量带不多。WebSocket我见过的很少,可能在某些场景用的多,但是我很少见到Navigator.sendBeacon专为页面卸载设计,确保数据在页面关闭时发送。非阻塞:不会影响页面卸载速度。在上图里面的优先级属于低,使用简单,但是有个问题,兼容性有点问题,可以配合fetch使用。他不会应为页面切换丢数据,调用起来非常方便。数据上报的形式可以从不同的角度来分类和理解。一般来说,数据上报主要有以下几种形式:
sendBeaconAPI。这种方式确保在用户离开页面时也能可靠地将数据发送到服务器。sendBeacon确保数据在页面关闭时发送。sendBeacon,需要备用方案。看你业务需要那种形式,去评估那种形式上报。
页面程序方法的运行时间进行监控是性能优化的重要一环,可以帮助识别性能瓶颈和优化点。以下是一些常用的方法来监控方法的运行时间:
console.time
和
console.timeEnd:console.time('label')开始计时,
console.timeEnd('label')结束计时,并在控制台输出运行时间。|
1 |
console.
time
(
'myFunction'
);myFunction();console.timeEnd(
'myFunction'
); |
performance.now() :performance.now()提供高精度的时间戳,可以用于精确测量函数的执行时间。|
1 |
const start = performance.now();myFunction();const end = performance.now();console.log(`myFunction took ${end - start} milliseconds`); |
PerformanceAPI:PerformanceAPI 提供了更全面的性能测量工具,可以记录和分析多个时间点。performance.mark()和
performance.measure()来标记和测量代码片段。|
1
2
3 |
performance.mark(
'start'
);myFunction();performance.mark(
'end'
);performance.measure(
'myFunction'
,
'start'
,
'end'
);const measures = performance.getEntriesByName(
'myFunction'
);console.log(measures[0].duration); |
stats.js或
New Relic等。performance.now()和日志记录,将数据发送到服务器进行分析。目前用的比较多的是
performance.mark()因为他是用的比
new date提供高精度的时间戳,具体的可以自己去查文档,我懒得讲
performance.mark()是 Web Performance API 提供的一个方法,用于在浏览器的性能时间线上创建一个标记。这个标记可以帮助开发者测量特定代码段的执行时间,尤其是在需要精确测量性能的复杂应用中。
performance.mark(name)来创建一个标记,其中
name是一个字符串,用于标识这个标记。例如:|
1
2 |
performance.mark(
'start'
);
//
执行一些代码performance.mark(
'end'
); |
performance.measure(name, startMark, endMark)方法来测量两个标记之间的时间间隔。
name是测量的名称,
startMark和
endMark是之前创建的标记。例如:|
1 |
performance.measure(
'myMeasure'
,
'start'
,
'end'
); |
performance.getEntriesByName(name)来获取测量结果。这个方法返回一个数组,其中包含所有与指定名称匹配的测量结果。例如:|
1
2
3
4 |
const measures = performance.getEntriesByName(
'myMeasure'
);measures.forEach((measure) => {
console.log(`${measure.name}: ${measure.duration}ms`);}); |
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70048949/viewspace-3109394/ ,如需转载,请注明出处,否则将追究法律责任。