Skip to content

15 何为监控:让监控成为快速发现问题的重要方法

你好,我是三桥。

说到监控,可能你会想到服务器、网络等这种跟前端同学无关的监控层面,还有一些常见的场景。

  • 偶尔看下可视化面板,通过分析图表发现问题。
  • 开发需求时,通过浏览器开发者工具发现问题。
  • 使用生产环境的测试账号发现页面加载速度慢,白屏时间较长。
  • 使用了如Sentry等第三方系统上报埋点日志,但没有配置任何警报机制,偶尔才去查看一下。
  • 等到用户反馈后,才知道某些细节存在问题。

像上面这样的场景,你觉得是监控吗?

都不是。它们只不过是一些发现和解决问题的方法。

那究竟何为监控?

监控就是更好地理解系统,让我们可以根据实际情况做出最优的决策,提升产品的服务质量,最终提高用户的满意度。

换句话说,真正的监控并不仅仅是收集日志和生成可视化图表这些事情,更多的是通过这些日志来提升我们的产品体验和服务。

对于前端同学来说,监控就意味着追踪程序错误,判断用户体验的效果,分析产品功能的可用性,观察应用的性能状态等。

既然监控是一种手段,就需要通过集成一些工具或技术到前端应用里面,实时地监视前端应用的运行状态。

目前,最常见的前端监控包括4个方面。

  1. 错误监控。 就是捕获并记录前端程序的错误和异常,包括JavaScript脚本错误、网络请求错误、异步异常等等。
  2. 性能监控。 就是监测前端应用的加载时间、渲染时间、交互响应时间等关键的执行指标。
  3. 用户体验监控。 一般来说,这种监控都是通过收集用户行为或者是收集用户反馈得到的体验情况。
  4. 可视化监控。 这种方式就是可视化界面,将日志通过图表、报表等形式展现出来,帮助我们直观地分析前端应用的运行状态和趋势。

这4个方面的监控,其实是围绕着前端代码的范围去检测异常,通过代码异常来判断问题。

但事实上真的能够很快判断到问题吗?假设代码异常,我们还要分析是在哪一行报错,分析是什么逻辑问题,如果涉及后端一起配合的情况,还要一一找出对应的日志或者联调重现。这么多的繁杂工作,简单的问题还能及时发现,如果是一些很隐秘的问题,那就得排查很久了。

所以,真正的前端全链路监控,应该是以问题为中心的监控,包括下面这4个方面。

  1. 问题维度监控。 就是通过自定义日志的能力,在业务逻辑中捕获可能发生异常的地方,并上报日志,通过 tag 字段记录不同的异常场景。
  2. 网页指标监控。 前面我们已经学过WebVitals网页指标的知识了,全链路就是基于这6个指标来衡量网页的性能状况的,包括用户体验。
  3. 核心功能监控。 一个大型前端应用必定有很多封装底层能力的函数或功能,它们都是最核心、最重要的代码。例如接口请求的监控,前端和App通讯的监控等等。
  4. 用户行为监控。 这种监控就是用户维度的监控,例如用户访问过的每个页面之间的关联,页面内的交互关联以及登录态的关联等等。

我们通过下面一张图来总结一下以问题为中心的监控的4个方面。

图片

结合这4个方面的全链路监控,我们才能更高效地发现和解决问题,提升前端应用的质量和性能,从而提升用户体验和用户满意度。

怎么才能做好前端全链路监控呢?

我们知道,一套完整的监控系统是非常复杂的,从架构设计到实时监控,从日志记录到存储,从分析到警报,从策略调整到优化等等,需要非常多的资源去解锁监控系统的每一个功能模块。

我认为,我们可以从两个角度思考,高效达成前端全链路监控。

第一,我们要先了解下自己最擅长的是什么?就是利用Web技术栈完成一项产品功能。那么相对应的,最不擅长的就是服务端的技术又或者是后端架构的技术。

第二,我们可以采用最小MVP原则去支撑前端项目的全链路监控。

什么是最小MVP原则?

它是指在投入最少资源的情况下,实现一个初始版本的产品,从而验证产品概念,获取用户反馈,验证市场需求。MVP 的核心思想是通过最小的功能集合,尽快推出产品,以最快速度学习和迭代。

也就是说,采用最小MVP方法,结合前端最擅长的Web技术栈,快速实现可以跑通实时发现异常信息的监控流程,从而验证产品、发现问题、解决问题。

所以,我们可以把全链路监控当作创业产品项目去做,至于怎么做,我们可以分成三个部分,从0到1,从1到10,从10到100。

从0到1的阶段

我们先来看下从0到1的阶段,顾名思义就是实现一个无到有到的最小功能集前端监控。我们应该很清楚地知道,监控最重要的是能够快速发现问题。所以,在这个阶段,我们的核心目标就是跑通流程并发现问题。

以最小MVP原则设计监控方案,一共有3个步骤。

  1. 实现前端埋点。 简单来说就是前端页面要能够上报全链路维度的日志,例如捕获错误、网页指标数据等。
  2. 简单处理日志。 既然这个阶段是以最小MVP为原则,那我们就无需考虑日志的存储,只需要实现一个简易的接收链路日志的通知接口就好了。
  3. 警告通知。 通知呈现的载体有很多种,例如钉钉或企微的聊天窗口、邮件通知,甚至是一个实时接收日志信息的前端页面。无论是哪一种通知,只要符合团队的习惯就可以了。

这样的方案,基本上就是最小路径的监控方案了。虽然这个方案在功能上是存在缺陷的,大量的日志加告警通知,必定会影响接收方发现问题的体验,例如钉钉聊天群,你可能会被消息烦死。但它最适合维护小项目的团队,又或者是刚起步、需要监控的新项目。

在这节课的后面,我们会依据这个方案实现一套警告通知的简易流程。

从1到10的阶段

第二个阶段的核心目的就是优化第一个阶段的成果。因为第一个阶段是目标是验证流程,所以必定会存在很多无法满足的需求,例如日志分等级通知,明确哪些日志无需通知等等。

所以,从1到10的阶段是建立一套完善监控流程、细化监控需求的阶段。主要任务包括4个方面。

  1. 持续改进和优化。也就是改进第一个阶段所产生的上报日志的问题,包括异常日志的查缺补漏,弥补日志流程的缺陷等等。
  2. 建立相应的通知机制,例如设定告警级别。通过设置告警级别,我们可以选择性地监控不同维度的问题。
  3. 明确问题发生时的处理流程和责任人。大多数研发团队都有一种值班制的机制,保证出现紧急线上问题的时候有个值班的研发同学对接。当然,有些团队还会配备一个质量组的团队,专门接收线上问题反馈。
  4. 日志逻辑的优化。在这个阶段,前端同学就可以对日志进行更多的干预了,例如把日志信息存储在文件里,提供给定时器,轮询存储和分析日志,为告警提供更多的条件规则。

这个阶段的主要工作就是完善机制,同时满足多个前端项目监控的需求。关于监控规则、警告这部分的内容,我将会在后面的课程中给你详细讲解。

从10到100的阶段

经历了前两个阶段之后,监控流程趋于完善。接下来这个阶段的核心目标就是围绕监控流程补充基础设施,例如设计核心报表、维护系统稳定性、建立紧急响应机制等。

到了在这个阶段,监控平台已经不再是前端团队的事情了,通常会上升到公司内的基建层面。这个时候,前端团队就可以围绕监控流程,建立全链路核心报表,包括你最熟悉的可视化界面。

这个阶段还有一项重要的功能,自动化监控。也就是分析收集回来的日志,再根据阈值的设定给出周期性的触发告警。这也是监控系统中最难的地方,即使是专业团队都未必能够很好地把握业务分析流程。

一般情况下,有一定规模的软件企业或成熟的团队,都有接近100阶段的监控平台。如果有的话,我们就可以基于全链路的思维把前端项目监控起来。

如果没有的话,怎么办呢?我们也可以考虑使用第三方云服务商提供的日志服务,例如阿里云SLS。

好了,我们来总结一下这3个阶段, 既然要快速地发现问题,我们就应该用最小MVP原则实现简易的监控流程,满足监控的能力。有了监控,我们就可以持续地迭代和优化,这样就能够形成属于前端团队的全链路监控体系了。

从0到1的简易监控通知

既然要用最小MVP监控方案,那怎样才能实现最小功能集呢?这里,有个小技巧,就是利用Webhook特性。

Webhook是一种实现不同系统之间实时通信的机制,它允许一个系统在特定事件发生时通知另一个系统。从前端技术的角度来理解,Webhook的能力可以被用来实现实时的、基于事件的通知,例如把钉钉群收到的信息当作实时通知。

也就是说,我们可以通过Webhook接收前端应用上报的日志数据,比如当用户支付成功后,发送一条信息到钉钉,告诉前端同学某某某用户交易成功,失败场景也是同理。

现在,我们就以钉钉通知为例,尝试打通链路日志的通知流程。

要实现钉钉通知功能,我们需要完成三个步骤:申请钉群机器人,实现调用Webhook函数以及日志上报接口。

申请钉群机器人

要开通钉群机器人,首先要有一个钉钉群,每个钉钉群可以开通多个机器人。每个机器人都有自己的webhook地址,这个地址还包含了一个叫access_token访问令牌的参数。参考如下图。

图片

另外,为了确保webhook使用过程更安全,我们还需要开启签名加密和设定自定义关键词两个安全设置。其中,自定义关键词使用“前端全链路”5个字,这样,机器人就会判断必须包含“前端全链路”这个词才能发送成功。参考如下图。

图片

实现调用Webhook的函数

第二步是实现一个可以通过Webhook地址发送给钉钉消息的函数。按照Webhook的通用规范,我们先利用签名加密生成一串已加密的字符串。我们以Node.js代码为例子,具体实现如下。

// webhook.js

function sign(secret, content) {
  const str = crypto.createHmac('sha256', secret).update(content)
    .digest()
    .toString('base64');
  return encodeURIComponent(str);
}

async function send(content) {
    let singStr = '';
  if (robotConfig.secret) {
    const timestamp = Date.now();
    singStr = '&timestamp=' + timestamp + '&sign=' + sign(robotConfig.secret, timestamp + '\n' + robotConfig.secret);
  }
  // 省略后面代码
}

然后,我们构造一个完整的Webhook请求地址,具体实现参考如下。

// webhook.js

const BASE_URL = 'https://oapi.dingtalk.com/robot/send'

function buildWebhook(accessToken) {
  return `${BASE_URL}?access_token=${accessToken}`
}

async function send(content) {
    let singStr = '';
  if (robotConfig.secret) {
    const timestamp = Date.now();
    singStr = '&timestamp=' + timestamp + '&sign=' + sign(robotConfig.secret, timestamp + '\n' + robotConfig.secret);
  }
    const webhook = buildWebhook(robotConfig.accessToken) + singStr;
    // 省略部分代码
}

最后,采用Fetch对象向Webhook发起请求调用。具体实现如下。

// webhook.js

const robotConfig = {
  name: '前端全链路监控助手',
  accessToken: '<dingtalk robot access_token>',
  secret: '<dingtalk robot srcret>',
}

async function send(content) {
  let singStr = '';
  if (robotConfig.secret) {
    const timestamp = Date.now();
    singStr = '&timestamp=' + timestamp + '&sign=' + sign(robotConfig.secret, timestamp + '\n' + robotConfig.secret);
  }
  const webhook = buildWebhook(robotConfig.accessToken) + singStr;

  const result = await fetch(webhook, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(content)
  });
  console.log('result: ', await result.json())
  return result
}

好了,我们已经实现了发送Webhook的逻辑函数。不过,钉钉的Webhook机器人对数据格式是有规范要求的,我们不能直接把链路日志的数据传给Webhook,还需要定义符合规范的数据结构。这次,我们先实现基于文本类型规范的钉钉消息,具体实现逻辑参考如下代码。

// webhook.js

async function sendText(content, at) {
  at = at || {};
  const message = {
    msgtype: 'text',
    text: {
      content: `${content} \n ----- 来自:${robotConfig.name}`
    },
    at
  }
  return send(message);
}

日志上报接口

一切准备就绪后,我们就来到了第三步,在上报日志接口里增加调用 sendText 函数的逻辑,就完成了。具体实现逻辑参考如下代码。

// index.js

const webhook = require('./webhook');

app.get('/track.gif', (req, res) => {
  const data = req.query.data; // 获取URL中的data参数
    // 省略部分逻辑
  webhook.sendText(data);

  // 省略部分逻辑
});

这样,我们就完成了一个最小功能版本的全链路监控流程了。

前端全链路监控的一些问题

相信你在看完前面的例子后,会有很大的冲动想去实现一套真正的前端监控。实际上,真正的问题都还没出现呢。

一个完整的前端全链路监控需要解决的问题非常多,总结起来有4类问题。

第一,几乎所有的前端团队都不具备搭建完整的从0到1的监控平台 的能力。 因为真正的监控平台涉及大量后端服务知识,例如数据库、Redis、Kafka等。所以我的建议还是选择使用第三方云服务商的日志服务。

第二,随着项目越来越多,日志必定会出现大量的增长, 而且也不可能每条日志都发送到钉钉。虽然我们可以提供抽样功能,但也会影响前端同学对问题的判断。

第三,如果是自研监控系统,随着系统日志数据量的不断增加,分析日志数据的速度可能会受到影响。 因为数据量的增长意味着需要处理的信息也在增多,这无疑增加了系统的压力,也会因为处理速度的下降,导致告警通知不及时。

第四,我们提到过用户访问前端页面的场景是非常多样的,异常报错的形式和内容的呈现也不同。所以,优化不同的错误或问题,并进行归纳和总结,对于前端同学来说是一件极其有挑战性的工作。

但我们还是要强调,前端全链路监控是一件需要持续性优化的解决方案,是团队解决问题必备的方法之一。

总结

这节课我们重点学习了前端全链路监控的基本概念,以及以最小MVP原则设计一套简易的最小路径的监控方法。

再次强调, 前端全链路监控不同于常见的前端监控,它的监控维度是以发现问题为中心,包括问题维度监控、网页指标监控、核心功能监控和用户行为监控4方面。 快速、高效地发现和解决问题,就能提升前端应用的质量和性能,从而提升用户体验和用户满意度。

我们在课程中实现的简易监控通知功能,也算是实现了从0到1的过程。不过,要真正做到从1到10甚至100,需要持续性优化并改进策略,才能满足多项目多团队的前端全链路监控。而且这是一件极其有挑战性的工作,对个人以及团队在发现和解决问题上都有很大的帮助。

在下节课,我们将会继续学习前端全链路监控另一方面的知识,监控规则。

思考题

现在,留给你一道思考题。通过这节课,你学会了利用Webhook特性来监控问题,不妨思考一下在你平时的工作中,会用什么监控方法来快速发现问题呢?

欢迎你在留言区和我交流。如果觉得有所收获,也可以把课程分享给更多的朋友一起学习。我们下节课见!