3 年前端所需要了解的工程化体系(二)
前期回顾:
上一篇是从项目开始的第一步技术选型开始,到规范统一、测试工具、构建工具的选择,和 CICD 部署发版。介绍了一个项目从创建到部署的全流程,但是这并不代表着整个项目的结束。
项目上线之后我们还需要做哪些工作?
都上线了,没有 bug 还关我毛事?
非也~
六、前端监控
1、前端为什么需要有监控?
一个很现实的原因是bug是不可能被全部测试出来的,由于成本和上线档期的考虑,测试无法做到“面面俱到”,即使时间充裕也总会有这样或那样的bug埋藏在某个角落。
一个稳健的前端监控系统可以帮助我们从被动转为主动,不再等待客服来寻求帮助,而是在问题出现时开发人员可以第一时间得知并解决。通过监控系统,我们还可以获取用户行为数据,追踪产品在用户端的使用情况,并以监控数据为基础,明确产品优化的方向。
某⼀天用户:xx商品无法下单! ⼜⼀天运营:xx广告在手机端打开不了!
大家反馈的bug,怎么都复现不出来,尴尬的要死!😢
如何记录项目的错误,并将错误还原出来,这是监控平台要解决的痛点之一
2、前端监控的目标与分类
目标:
- 提升用户体验
- 更快的发现发现异常、定位异常、解决异常
- 了解业务数据,指导产品升级——数据驱动的思想:获取用户行为以及跟踪产品在用户端的使用情况,并以监控数据为基础,指明产品优化的方向。
分类:
- 前端监控总体可以分为以下三类
-
- 埋点/用户的行为数据监控
- 性能监控
- 异常监控
网上的一些监控案例
用户数据监控/埋点
常见的数据监控包括:
指标名称 | 定义或说明 |
---|---|
PV (Page View) | 用户访问特定页面的次数,即页面的浏览量或点击量。 |
PV上报 | 用于业务分析,可设置告警以及时发现问题。 |
PV波动告警 | 当PV的波动轨迹出现异于正常的波动时触发告警,适用于波动变化具有一定规律性的页面。 |
PV最小值阈值告警 | 当PV值低于某个阈值时触发告警。 |
UV (Unique Visitor) | 访问网站的不同个体或设备数量。 |
新独立访客 | 当日的独立访客中,历史上首次访问网站的访客。 |
跳出次数 | 仅浏览了1个页面就离开网站的访问(会话)行为。跳出次数越多,可能表示访客对网站兴趣较低或站内入口质量较差。 |
来访次数 | 由该来源进入网站的访问(会话)次数。 |
用户在每一个页面的停留时间 | 衡量用户在页面上的停留时长。 |
用户通过什么入口来访问该网页 | 分析用户是通过什么途径或方式找到并访问网页。 |
用户在相应的页面中触发的行为 | 分析用户在页面上的行为模式。 |
网站的转化率 | 衡量网站用户完成特定目标(如购买、注册等)的比率。 |
导航路径分析 | 分析用户在网站上的行动路径,了解用户行为和偏好。 |
统计这些数据是有意义的,比如我们知道了用户来源的渠道,可以促进产品的推广,知道用户在每一个页面停留的时间,可以针对停留较长的页面,增加广告推送等等。
代码埋点:
手动埋点也称为代码埋点,是通过手动在代码中插入埋点代码(SDK 的函数)的方式来实现数据收集。大多第三方数据统计服务商大都采用这种方案,这种方法的优点是:
- 灵活:开发人员可以根据需要自定义属性和事件,以捕获特定的用户行为和数据。
- 精确:可以精确控制埋点位置,以确保收集到关键数据。
然而,手动埋点的缺点包括:
- 工作量大:需要在代码中多次插入埋点代码,工程量较大。
- 沟通成本高:需要开发、产品和运营之间的频繁沟通,容易导致误差和延迟。
- 更新迭代成本高:每次有埋点更新或漏埋点都需要重新发布应用程序,成本较高。
以神策为例:
import sensors from 'sa-sdk-javascript'
// 一些动态的参数需要我们和服务端商议自行填写
sensors.init({
name: 'sensors',
server_url: '', // 服务端接受数据地址
show_log: false, // 不输出log到控制台
cross_subdomain: false, // 不在根域下设置cookie
is_track_single_page: true, // 表示是否开启单页面自动采集 $pageview 功能,SDK 会在 url 改变之后自动采集web页面浏览事件 $pageview。
heatmap: {
// 是否开启点击图,default 表示开启,自动采集 $WebClick 事件,可以设置 'not_collect' 表示关闭。
clickmap: 'not_collect',
// 是否开启触达注意力图,not_collect 表示关闭,不会自动采集 $WebStay 事件,可以设置 'default' 表示开启。
scroll_notice_map: 'not_collect'
}
})
sensors.registerPage({
current_url: location.href,
referrer: document.referrer
})
sensors.quick('autoTrack')
window.sensors = sensors
export default sensors
使用:
// 引入
import sensorsTrack from '@/utils/sendSensors.js'
// 需要上报的事件 || 函数内添加
sensorsTrack('事件名', 上报参数, callback()) || sensorsTrack('事件名', 上报参数)
全埋点:
全埋点是一种自动收集所有用户行为和事件的方法,然后通过后端过滤和分析以提取有用的数据。
优点:
- 全自动:无需手动埋点,数据自动收集,降低了工程量,而且不会出现漏埋和误埋等现象。
- 全面性:捕获了所有用户行为,提供了完整的数据集。
缺点:
- 数据量大:数据量庞大,需要后端过滤和处理,可能增加服务器性能压力。
- 数据处理复杂:需要处理大量原始数据,提取有用的信息可能需要复杂的算法和逻辑。
性能监控
通过测量页面加载时间、资源加载时间、接口请求时间等指标,来评估前端应用程序的性能。可以使用Performance API或者自定义计时器来实现性能监控。
-
性能数据捕获:通过window.performance对象获取页面加载过程中各个关键节点的性能数据。可以记录DNS解析时间、TCP连接时间、DOMContentLoaded时间等,并进行相应处理,如上报性能数据到后端服务器或展示给用户。
-
各个阶段的加载时间:通过计算各个阶段(如重定向、DNS解析、TCP连接等)的耗时情况来分析优化性能瓶颈。可以记录各个阶段的耗时,并进行相应处理,如上报耗时数据到后端服务器或展示给用户。
常见的性能指标:
网页重定向的耗时:redirectEnd - redirectStart
检查本地缓存的耗时: domainLookupStart - fetchStart
DNS查询的耗时:domainLookupEnd - domainLookupStart
TCP连接的耗时:connectEnd - connectStart
从客户端发起请求到接收到响应的时间 / TTFB:responseStart - fetchStart
首次渲染时间/白屏时间:responseStart - pnt.startTime
下载服务端返回数据的时间:responseEnd - responseStart
request请求耗时:responseEnd - requestStart
解析dom树耗时:domComplete - domInteractive
dom加载完成的时间:domContentLoadedEventEnd
页面load的总耗时:duration
常见的首屏性能指标定义
- FP(First Paint),代表浏览器第一次在页面上绘制的时间,这个时间仅仅是开始绘制的时间,但是未必真的绘制了什么有效的内容。
- FCP(First Contentful Paint),代表浏览器第一次绘制出 DOM 元素(如文字、标签等)的时间。FP 可能和 FCP 是同一个时间,也可能早于 FCP,但一般来说两者的差距不会太大。
- FMP(First Meaningful Paint),首次渲染有意义的内容的时间,“有意义”没有一个标准的定义,FMP的计算方法也很复杂。
- LCP(largest contentful Paint),度量的是首屏视图中最大的元素的渲染时间。
- FID(largest contentful Paint),度量的是从用户首次和网站进行交互到响应该事件的实际延时的时间。
- CLS(largest contentful Paint),CLS 度量的是页面产生连续累计布局偏移分数。
- TTFB(Time To First Byte),是指客户端从发起请求到接收到服务器响应的第一个字节的时间,是反应网站性能的重要指标。
可以通过window.performance.getEntriesByType('navigation')[0]的参数进行计算
或者借助第三方插件web-vitals:
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;
long task
获取页面的长任务列表:
const entryHandler = list => {
for (const long of list.getEntries()) {
// 获取长任务详情
console.log(long);
}
};
let observer = new PerformanceObserver(entryHandler);
observer.observe({ entryTypes: ["longtask"] });
memory页面内存
performance.memory 可以显示此刻内存占用情况,它是一个动态值,其中:
- jsHeapSizeLimit 该属性代表的含义是:内存大小的限制。
- totalJSHeapSize 表示总内存的大小。
- usedJSHeapSize 表示可使用的内存的大小。
通常,usedJSHeapSize 不能大于 totalJSHeapSize,如果大于,有可能出现了内存泄漏
function getMemoryUsage() {
if (performance.memory) {
const { jsHeapSizeLimit, totalJSHeapSize, usedJSHeapSize } = performance.memory;
return {
jsHeapSizeLimit, // JavaScript 堆的内存大小限制
totalJSHeapSize, // 已分配的 JavaScript 堆内存
usedJSHeapSize // 当前使用的 JavaScript 堆内存
};
} else {
console.warn('performance.memory is not supported by this browser.');
return null;
}
}
function detectMemoryLeak() {
const memoryUsage = getMemoryUsage();
if (memoryUsage) {
const { usedJSHeapSize } = memoryUsage;
// 这里可以添加检测逻辑,例如连续几次内存使用量都在增长
// 如果检测到内存泄漏,可以记录日志或上报
}
}
// 每隔60秒检测一次内存泄漏
setInterval(detectMemoryLeak, 60000);
错误监控
捕获前端应用程序中的JavaScript错误、网络请求错误等,并将错误信息包括堆栈信息、错误类型等上报到后端服务器。可以使用window.onerror或者try-catch来捕获错误。
-
JS执行错误:通过监听window.onerror/window.addEventListener('error', function(event) { ... }, true)事件,捕获页面中的JavaScript错误。可以获取错误的行号、列号、错误信息等,并进行相应处理,如上报错误信息到后端服务器或展示给用户。
-
Promise异常:通过监听unhandledrejection事件,捕获Promise对象的异常。可以获取异常信息,并进行相应处理,如上报异常信息到后端服务器或展示给用户。
-
资源异常:通过监听window.onload事件和error事件,捕获页面中加载的资源(如图片、CSS、JS等)加载失败或出错的情况。可以获取资源URL和错误类型,并进行相应处理,如上报资源加载失败信息到后端服务器或展示给用户。
-
接口错误:通过拦截XMLHttpRequest对象,监听接口请求返回的状态码和异常情况。可以获取请求URL、状态码和错误信息,并进行相应处理,如上报接口请求错误信息到后端服务器或展示给用户。
-
白屏:通过判断DOMContentLoaded事件是否触发来判断是否出现白屏或长时间加载不出内容。可以记录白屏时间,并进行相应处理,如上报白屏时间到后端服务器或展示给用户。
详细讲解:(参考 sentry 的错误捕获实现方式)
- window.onerror
-
- 监控JavaScript运行时错误(包括语法错误)和 资源加载错误
window.onerror = function(message, source, lineno, colno, error) { ... }
window.addEventListener('error', function(event) { ... }, true)
// 函数参数:
// message:��误信息(字符串)。可用于HTML onerror=""处理程序中的event。
// source:发生错误的脚本URL(字符串)
// lineno:发生错误的行号(数字)
// colno:发生错误的列号(数字)
// error:Error对象(对象
大家可以看到 JS 错误监控里面有个 window.onEerror,
又用了 window.addEventLisenter('error'),
其实两者并不能互相代替。
window.onError 是一个标准的错误捕获接口,它可以拿到对应的这种 JS 错误;
window.addEventLisenter('error')也可以捕获到错误,
但是它拿到的 JS 报错堆栈往往是不完整的。
同时 window.onError 无法获取到资源加载失败的一个情况,
必须使用 window.addEventLisenter('error')来捕获资源加载失败的情况。
- Promise
-
- Promise的话主要是unhandledrejection事件,也就是未被catch的reject状态的promise
window.addEventListener("unhandledrejection", event => {
console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
});
- setTimeout、setInterval、requestAnimationFrame等
-
- 其实就是通过代理的方式把原来的方法拦截一下在调用真实的方法之前做一些自己的事情
const prevSetTimeout = window.setTimeout;
window.setTimeout = function(callback, timeout) {
const self = this;
return prevSetTimeout(function() {
try {
callback.call(this);
} catch (e) {
// 捕获到详细的错误,在这里处理日志上报等了逻辑
// ...
throw e;
}
}, timeout);
}
- Vue的Vue.config.errorHandler
-
- 跟上面的同理
// sentry中对Vue errorHandler的处理
function vuePlugin(Raven, Vue) {
var _oldOnError = Vue.config.errorHandler;
Vue.config.errorHandler = function VueErrorHandler(error, vm, info) {
// 上报
Raven.captureException(error, {
extra: metaData
});
if (typeof _oldOnError === 'function') {
// 为什么这么做?
_oldOnError.call(this, error, vm, info);
}
};
}
module.exports = vuePlugin;
- React的错误捕获
-
- 如果一个class组件中定义了static getDerivedStateFromError() 或componentDidCatch()这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。当抛出错误后,请使用static getDerivedStateFromError()渲染备用 UI ,使用componentDidCatch()打印错误信息
- hooks 没有类似的错误捕获钩子函数,但是可以通过ErrorBoundary触发
// ErrorBoundary的示例
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
this.setState({ hasError: true });
// 在这里可以做异常的上报
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
Sentry 是如何实现的React的ErrorBoundary的上报?
// ts声明的类型,可以看到sentry大概实现的方法
/**
* A ErrorBoundary component that logs errors to Sentry.
* Requires React >= 16
*/
declare class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
state: ErrorBoundaryState;
componentDidCatch(error: Error, { componentStack }: React.ErrorInfo): void;
componentDidMount(): void;
componentWillUnmount(): void;
resetErrorBoundary: () => void;
render(): React.ReactNode;
}
// 真实上报的地方
ErrorBoundary.prototype.componentDidCatch = function (error, _a) {
var _this = this;
var componentStack = _a.componentStack;
// 获取到配置的props
var _b = this.props, beforeCapture = _b.beforeCapture, onError = _b.onError, showDialog = _b.showDialog, dialogOptions = _b.dialogOptions;
withScope(function (scope) {
// 上报之前做一些处理,相当于axios的请求拦截器
if (beforeCapture) {
beforeCapture(scope, error, componentStack);
}
// 上报
var eventId = captureException(error, { contexts: { react: { componentStack: componentStack } } });
// 开发者的回调
if (onError) {
onError(error, componentStack, eventId);
}
// 是否显示sentry的错误反馈组件(也是一种收集错误的方式)
if (showDialog) {
showReportDialog(__assign(__assign({}, dialogOptions), { eventId: eventId }));
}
// componentDidCatch is used over getDerivedStateFromError
// so that componentStack is accessible through state.
_this.setState({ error: error, componentStack: componentStack, eventId: eventId });
});
};
- 请求错误拦截上报
-
- XHR通过重写(拦截)send和open
- fetch通过拦截整个方法(需要讨论,reject的情况)
- axios通过请求/响应拦截器 注意:sentry支持自动收集和手动收集两种错误收集方法,但是还不能捕捉到异步操作、接口请求中的错误,比如接口返回404、500等信息,此时我们可以通过Sentry.caputureException()进行主动上报。
sentry 是如何上传 source-map 的?
source-map: 在前端开发中,source-map(源映射)是一种用于调试的工具,可以将编译或打包后的代码映射回原始的源代码。这对于调试和错误跟踪非常有用,因为现代前端开发通常会涉及到代码的编译、压缩和打包,导致最终的生产环境代码难以阅读和调试。
为什么需要 Source Map
随着前端开发工具链的复杂化,代码通常会经过以下处理:
- 编译:如将 TypeScript 编译为 JavaScript,或将 JSX 编译为 JavaScript,es6 转 es5 等。
- 打包:如使用 Webpack 或 Rollup 将多个模块打包成一个文件。
- 压缩:如使用 UglifyJS 或 Terser 压缩代码以减少文件大小。
这些处理过程会使原始代码变得难以阅读和调试。Source Map 通过在编译和打包过程中生成映射文件,可以帮助开发者在调试工具中查看和调试原始代码。
前端监控通过source-map 可以实现什么工能?
sentry/webpack-plugin上传的原理
大概的一个过程其实就是在webpack的afterEmit钩子,获取到打包之后的文件。然后过滤得出文件类型是/.js∣m˙ap|\.map∣m˙ap/结尾的就上传到sentry的服务器上。然后删除的时候只删除/.map$/结尾的文件。
// upload sourcemaps
apply(compiler) {
// afterEmit在生成文件到output目录之后执行
compiler.hooks.afterEmit.tapAsync(this.name, async (compilation, callback) => {
const files = this.getFiles(compilation);
try {
await this.createRelease();
await this.uploadFiles(files);
console.info('\n\u001b[32mUpload successfully.\u001b[39m\n');
} catch (error) {
// todo
}
callback(null);
});
}
// 获取需要上传的文件
getFiles(compilation) {
// 通过 compilation.assets 获取我们需要的文件信息,格式信息
// compilation.assets {
// 'bundle.js': SizeOnlySource { _size: 212 },
// 'bundle.js.map': SizeOnlySource { _size: 162 }
// }
return Object.keys(compilation.assets)
.map((name) => {
if (this.isIncludeOrExclude(name)) {
return { name, filePath: this.getAssetPath(compilation, name) };
}
return null;
})
.filter(Boolean);
}
// 获取文件的绝对路径
getAssetPath(compilation, name) {
return path.join(compilation.getPath(compilation.compiler.outputPath), name.split('?')[0]);
}
// 上传文件
async uploadFile({ filePath, name }) {
console.log(filePath);
try {
await request({
url: `${this.sentryReleaseUrl()}/${this.release}/files/`, // 上传的sentry路径
method: 'POST',
auth: {
bearer: this.apiKey,
},
headers: {},
formData: {
file: fs.createReadStream(filePath),
name: this.filenameTransform(name),
},
});
} catch (e) {
console.error(`uploadFile failed ${filePath}`);
}
}
// 删除 sourcemaps
sentryDel(compiler) {
compiler.hooks.done.tapAsync(this.name, async (stats, callback) => {
console.log('Whether to delete SourceMaps:', this.isDeleteSourceMaps);
if (this.isDeleteSourceMaps) {
await this.deleteFiles(stats);
console.info('\n\u001b[32mDelete SourceMaps done.\u001b[39m\n');
}
callback(null);
});
}
// 删除文件
async deleteFiles(stats) {
console.log();
console.info('\u001b[33mStarting delete SourceMaps...\u001b[39m\n');
Object.keys(stats.compilation.assets)
.filter((name) => this.deleteRegex.test(name))
.forEach((name) => {
const filePath = this.getAssetPath(stats.compilation, name);
if (filePath) {
console.log(filePath);
fs.unlinkSync(filePath);
} else {
console.warn(`bss-plugin-sentry: 不能删除 '${name}', 文件不存在, 由于生成错误它可能没有创建`);
}
});
}
或者等@sentry/webpack-plugin上传完之后,打包结束,rm-rf删除本地的 map 文件
export const SentryPluginConfig = {
deleteAfterCompile: true, // 上传完 source-map 文件后要不要删除当前目录下的source-map 文件
// Sentry options are required
organization: '', // 组名
project: '', // 当前项目名
baseSentryURL: 'https://xxx', // 默认是 https://sentry.io/api/0,也即是上传到 sentry 官网上去,如果是自己搭建的 sentry 系统,那把sentry.io替换成你自己的sentry系统域名就行。
apiKey: '',
// Release version name/hash is required
release: SentryRelease, // 版本
};
录制
- 大概的流程就是首先保存一个一开始完整的dom的快照,然后为每一个节点生成一个唯一的id。
- 当dom变化的时候通过MutationObserver来监听具体是哪个DOM的哪个属性发生了什么变化,保存起来。
- 监听页面的鼠标和键盘的交互事件来记录位置和交互信息,最后用来模拟实现用户的操作。
- 然后通过内部写的解析方法来解析(我理解的这一步是最难的)
- 通过渲染dom,并用RAF来播放,就好像在看一个视频一样。
上报方式
常见的上报方式包括 AJAX(通过 XMLHttpRequest 或 fetch)、图片(Image 对象)和 Beacon(navigator.sendBeacon)。每种方式都有其优缺点,适用于不同的场景和需求。
1. AJAX
优点
- 灵活性高:可以处理复杂的请求和响应,包括处理 HTTP 状态码、响应头和响应体。
- 支持各种 HTTP 方法:支持 GET、POST、PUT、DELETE 等多种 HTTP 方法。
- 可处理大数据:适合发送大量数据或复杂的 JSON 数据。
缺点
- 同步请求会阻塞页面:虽然可以发送同步请求,但这会阻塞页面,影响用户体验。现代浏览器中一般推荐避免使用同步请求。
- 兼容性问题:老旧浏览器可能不完全支持 fetch API,需要回退到 XMLHttpRequest。
示例
// 使用 fetch API
function reportErrorWithFetch(data) {
fetch('https://your-monitoring-service.com/report', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
}).catch(err => console.error('Failed to report error:', err));
}
// 使用 XMLHttpRequest
function reportErrorWithXHR(data) {
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://your-monitoring-service.com/report', true);
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
xhr.send(JSON.stringify(data));
}
2. 图片(Image Beacon)
优点
- 简单:实现非常简单,只需设置图片的 src 属性。
- 浏览器支持广泛:几乎所有浏览器都支持 Image 对象。
- 适合 GET 请求:适合发送简单的 GET 请求,特别是监控上报失败时的兜底方案。
缺点
- 数据量有限:URL 长度有限,不适合发送大量数据或复杂的 JSON 数据。
- 无法处理响应:无法处理服务器的响应,适合于只需要发送数据而不需要处理响应的场景。
示例
function reportErrorWithImage(data) {
const img = new Image();
img.src = `https://your-monitoring-service.com/report?data=${encodeURIComponent(JSON.stringify(data))}`;
}
3. Beacon
优点
- 非阻塞:异步发送数据,不会阻塞页面,特别适合在页面卸载(如关闭、刷新、导航离开)时上报数据。
- 适合 POST 请求:可以发送 POST 请求,适合发送较大数据量。
- 可靠性高:即使在页面关闭时也能确保数据被发送。
缺点
- 浏览器支持有限:虽然现代浏览器支持 navigator.sendBeacon,但老旧浏览器可能不支持。
- 无法处理响应:无法处理服务器的响应,适合于只需要发送数据而不需要处理响应的场景。
示例
function reportErrorWithBeacon(data) {
navigator.sendBeacon('https://your-monitoring-service.com/report', JSON.stringify(data));
}
上报时机
无论是埋点还是性能数据、错误数据的上报都不应该阻塞页面的正常渲染。
优先使用 requestIdleCallback,利用浏览器空闲时间上报,其次使用微任务上报
总结:
-
前端埋点:在需要监控的地方插入代码,收集相关数据。可以通过手动埋点或自动埋点的方式进行数据采集。手动埋点需要开发人员主动在代码中插入埋点代码,而自动埋点则是通过脚本自动捕获页面的交互行为和性能数据。
-
数据上报:将采集到的数据发送到后端服务器进行存储和分析。可以使用Ajax、img src、信标Beacon等方式将数据发送到后端服务器。数据上报需要考虑网络请求的稳定性和性能,可以使用批量上报或异步上报等方式优化上报效率。
-
分析和计算:服务端对采集到的数据进行加工汇总,进行统计分析和计算。可以根据业务需求对数据进行聚合、过滤、排序等操作,以便后续的可视化展示和监控报警。
-
可视化展示:将经过加工汇总的数据按照各种维度进行展示,以便用户能够直观地了解系统的运行情况和性能状况。可以使用图表、表格、仪表盘等形式展示数据,并提供交互功能,方便用户深入分析。
-
监控报警:根据预设的规则和条件,对监控指标进行实时监测,并在达到一定阈值时触发报警机制。可以通过邮件、短信、钉钉等方式通知相关人员,并及时处理异常情况,保障系统稳定运行。
七、性能优化
在收集了这么多的性能数据和错误数据之后,必然是要开始下一步的优化喽~
本期分享了项目上线之后我们需要关注的一些事情。主要是前端监控的作用和实现方式,以及性能优化方案。下一期准备从重构、微前端、serverless等方向做一些简单的分享总结。(盗了不少图)
转载自:https://juejin.cn/post/7381855349463957513