前端监控:如何监控前端性能、错误和用户行为,并提供数据支持。

前端监控:性能、错误与用户行为的数据驱动分析

大家好,今天我们来聊聊前端监控这个话题。前端监控的重要性无需赘述,它就像是前端应用的“体检报告”,能帮助我们了解应用的健康状况、发现潜在问题,并最终提升用户体验。

本次讲座将围绕以下三个核心方面展开:

  1. 性能监控: 如何衡量和优化前端性能,包括页面加载速度、资源加载、渲染性能等。
  2. 错误监控: 如何捕获和分析前端错误,包括 JavaScript 错误、HTTP 请求错误等。
  3. 用户行为监控: 如何跟踪和分析用户行为,包括页面访问、点击事件、表单提交等。

同时,我们将探讨如何利用这些监控数据来指导我们的开发和优化工作。

一、性能监控:页面加载速度、资源加载与渲染性能

性能是用户体验的基石。一个缓慢的应用会让用户感到沮丧,并可能导致用户流失。因此,性能监控是前端监控中至关重要的一环。

1.1 页面加载速度监控:

页面加载速度直接影响用户的第一印象。我们需要监控的关键指标包括:

  • FP (First Paint): 首次绘制时间,浏览器首次将任何视觉元素呈现到屏幕上的时间。
  • FCP (First Contentful Paint): 首次内容绘制时间,浏览器首次将内容(文本、图片等)呈现到屏幕上的时间。
  • LCP (Largest Contentful Paint): 最大内容绘制时间,视口中最大的内容元素呈现到屏幕上的时间。
  • TTI (Time to Interactive): 可交互时间,页面变得完全可交互的时间。
  • DCL (DOMContentLoaded): DOMContentLoaded 事件触发的时间,表示 HTML 文档已被完全加载和解析。
  • Load: load 事件触发的时间,表示 HTML 文档及其所有资源(如图片、样式表)都已完成加载。

我们可以使用 PerformanceObserver API 来监控这些指标。

const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
    console.log(entry.name, entry.startTime, entry.duration);
    // 将数据发送到后端
    sendDataToBackend({
      name: entry.name,
      startTime: entry.startTime,
      duration: entry.duration,
      entryType: entry.entryType,
    });
  });
});

observer.observe({ entryTypes: ['paint', 'navigation', 'resource', 'longtask'] });

function sendDataToBackend(data) {
  // 实现数据发送逻辑,例如使用 fetch 或 XMLHttpRequest
  fetch('/api/performance', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(data)
  })
  .then(response => {
    if (!response.ok) {
      console.error('Failed to send performance data:', response.status);
    }
  })
  .catch(error => {
    console.error('Error sending performance data:', error);
  });
}

代码解释:

  • PerformanceObserver 监听 paint, navigation, resource, longtask 这几种类型的 performance entries。
  • entryTypes 指定了要观察的性能条目类型。
  • list.getEntries() 获取所有观察到的性能条目。
  • entry.name 表示性能条目的名称,例如 "first-paint", "first-contentful-paint"。
  • entry.startTime 表示性能条目的开始时间。
  • entry.duration 表示性能条目的持续时间。
  • sendDataToBackend 函数负责将性能数据发送到后端服务器。

1.2 资源加载监控:

资源加载是影响页面加载速度的重要因素。我们需要监控的关键指标包括:

  • 资源加载时间: 每个资源(如图片、CSS、JavaScript)的加载时间。
  • 资源大小: 每个资源的大小。
  • 资源加载失败率: 资源加载失败的次数。

我们可以使用 PerformanceObserver API 监控资源加载时间。

const resourceObserver = new PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
    if (entry.entryType === 'resource') {
      console.log(entry.name, entry.duration, entry.transferSize);
      // 将数据发送到后端
      sendDataToBackend({
        name: entry.name,
        duration: entry.duration,
        transferSize: entry.transferSize,
        entryType: entry.entryType,
      });
    }
  });
});

resourceObserver.observe({ entryTypes: ['resource'] });

代码解释:

  • 此代码段监听 resource 类型的 performance entries,即资源加载相关的性能数据。
  • entry.transferSize 表示资源通过网络传输的大小(以字节为单位)。

我们还可以使用 <img> 标签的 onerror 事件来监控图片加载失败。

<img src="image.jpg" onerror="handleImageError(event)">

<script>
function handleImageError(event) {
  const image = event.target;
  const src = image.src;
  console.error(`Image failed to load: ${src}`);
  // 将错误信息发送到后端
  sendErrorToBackend({
    type: 'image_load_error',
    message: `Image failed to load: ${src}`,
    url: window.location.href,
  });
}
</script>

1.3 渲染性能监控:

渲染性能直接影响用户界面的流畅度。我们需要监控的关键指标包括:

  • FPS (Frames Per Second): 每秒帧数,表示浏览器每秒绘制的帧数。
  • 长任务 (Long Tasks): 超过 50ms 的任务,会阻塞主线程,导致页面卡顿。

我们可以使用 requestAnimationFrame API 来监控 FPS。

let frameCount = 0;
let lastTime = performance.now();

function calculateFPS() {
  frameCount++;
  const now = performance.now();
  const delta = now - lastTime;
  if (delta >= 1000) {
    const fps = frameCount / (delta / 1000);
    console.log('FPS:', fps);
    // 将数据发送到后端
    sendDataToBackend({
      name: 'fps',
      value: fps,
    });
    frameCount = 0;
    lastTime = now;
  }
  requestAnimationFrame(calculateFPS);
}

requestAnimationFrame(calculateFPS);

代码解释:

  • requestAnimationFrame 在浏览器下一次重绘之前调用指定的函数。
  • frameCount 记录绘制的帧数。
  • 每隔 1000ms (1秒),计算 FPS 并将其发送到后端。

我们可以使用 PerformanceObserver API 来监控长任务。

const longTaskObserver = new PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
    console.log('Long Task:', entry.name, entry.duration);
    // 将数据发送到后端
    sendDataToBackend({
      name: 'long_task',
      duration: entry.duration,
      startTime: entry.startTime,
      attribution: entry.attribution,
    });
  });
});

longTaskObserver.observe({ entryTypes: ['longtask'] });

代码解释:

  • 此代码段监听 longtask 类型的 performance entries,即耗时较长的任务。
  • entry.attribution 提供有关导致长任务的脚本的信息。

1.4 性能监控数据分析:

收集到性能数据后,我们需要对其进行分析,找出性能瓶颈。

  • 页面加载速度慢: 检查资源大小、资源加载顺序、是否存在阻塞渲染的资源。
  • FPS 低: 检查是否存在复杂的计算或渲染操作,是否存在大量的 DOM 操作。
  • 存在长任务: 分析长任务的来源,优化代码逻辑,避免阻塞主线程。

表格:性能监控指标总结

指标 描述 监控方法 分析方向
FP 首次绘制时间 PerformanceObserver 检查是否存在阻塞渲染的资源,优化关键渲染路径。
FCP 首次内容绘制时间 PerformanceObserver 检查是否存在阻塞渲染的资源,优化关键渲染路径。
LCP 最大内容绘制时间 PerformanceObserver 优化最大内容元素的加载和渲染,例如使用图片懒加载、优化图片大小。
TTI 可交互时间 PerformanceObserver 优化 JavaScript 代码的执行,延迟非关键 JavaScript 代码的加载。
DCL DOMContentLoaded PerformanceObserver 检查是否存在阻塞 DOMContentLoaded 事件的脚本,延迟非关键脚本的加载。
Load load PerformanceObserver 检查是否存在阻塞 load 事件的资源,优化资源加载顺序。
资源加载时间 每个资源加载所需的时间 PerformanceObserver 检查是否存在加载缓慢的资源,优化资源大小,使用 CDN 加速资源加载。
资源大小 每个资源的大小 PerformanceObserver 压缩资源,使用合适的图片格式,避免加载不必要的资源。
资源加载失败率 资源加载失败的次数 <img> onerror事件 检查资源 URL 是否正确,检查服务器是否可用。
FPS 每秒帧数 requestAnimationFrame 检查是否存在复杂的计算或渲染操作,是否存在大量的 DOM 操作,使用 Web Workers 将计算密集型任务移到后台线程。
长任务 超过 50ms 的任务 PerformanceObserver 分析长任务的来源,优化代码逻辑,避免阻塞主线程,使用 Web Workers 将计算密集型任务移到后台线程。

二、错误监控:JavaScript 错误与 HTTP 请求错误

错误是不可避免的,但我们可以通过监控来及时发现和修复错误,减少对用户体验的影响。

2.1 JavaScript 错误监控:

JavaScript 错误是前端应用中最常见的错误类型。我们需要监控的关键信息包括:

  • 错误类型: 例如 TypeErrorReferenceError 等。
  • 错误信息: 错误的具体描述。
  • 错误堆栈: 错误发生时的调用堆栈,可以帮助我们定位错误的位置。
  • 错误发生的文件和行号: 错误发生的代码位置。

我们可以使用 window.onerror 事件来捕获 JavaScript 错误。

window.onerror = function(message, source, lineno, colno, error) {
  console.error('JavaScript error:', message, source, lineno, colno, error);
  // 将错误信息发送到后端
  sendErrorToBackend({
    type: 'javascript_error',
    message: message,
    source: source,
    lineno: lineno,
    colno: colno,
    stack: error ? error.stack : null,
    url: window.location.href,
  });
  return true; // 阻止浏览器默认的错误处理
};

代码解释:

  • window.onerror 是一个全局错误处理函数,当 JavaScript 运行时发生未捕获的错误时,该函数会被调用。
  • message 是错误信息。
  • source 是发生错误的文件 URL。
  • lineno 是发生错误的行号。
  • colno 是发生错误的列号。
  • error 是 Error 对象,包含错误的堆栈信息。
  • return true; 阻止浏览器默认的错误处理,避免在控制台中显示错误信息。

2.2 HTTP 请求错误监控:

HTTP 请求错误表示前端应用与后端服务器之间的通信出现了问题。我们需要监控的关键信息包括:

  • 请求 URL: 发生错误的请求 URL。
  • HTTP 状态码: 例如 404、500 等。
  • 错误信息: 错误的具体描述。

我们可以通过重写 XMLHttpRequest 对象来监控 HTTP 请求错误。

(function() {
  const originalXHR = window.XMLHttpRequest;

  function MyXHR() {
    const xhr = new originalXHR();

    xhr.addEventListener('load', function() {
      if (this.status >= 400) {
        console.error('HTTP error:', this.status, this.responseURL);
        // 将错误信息发送到后端
        sendErrorToBackend({
          type: 'http_error',
          status: this.status,
          url: this.responseURL,
          response: this.responseText,
        });
      }
    });

    return xhr;
  }

  window.XMLHttpRequest = MyXHR;
})();

代码解释:

  • 这段代码使用立即执行函数表达式 (IIFE) 来创建一个闭包,以避免污染全局命名空间。
  • originalXHR 保存原始的 XMLHttpRequest 对象。
  • MyXHR 是我们自定义的 XMLHttpRequest 对象,它继承了原始 XMLHttpRequest 对象的功能。
  • 我们重写了 XMLHttpRequest 对象的 addEventListener 方法,监听 load 事件。
  • load 事件处理函数中,我们检查 HTTP 状态码是否大于等于 400,如果是,则表示发生了 HTTP 错误。
  • 我们将错误信息发送到后端。

我们也可以通过 fetch API 的 catch 方法来监控 HTTP 请求错误。

fetch('/api/data')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return response.json();
  })
  .catch(error => {
    console.error('Fetch error:', error);
    // 将错误信息发送到后端
    sendErrorToBackend({
      type: 'fetch_error',
      message: error.message,
      url: '/api/data',
    });
  });

2.3 错误监控数据分析:

收集到错误数据后,我们需要对其进行分析,找出错误的根本原因。

  • 频繁出现的 JavaScript 错误: 检查代码逻辑,修复 bug。
  • 频繁出现的 HTTP 请求错误: 检查后端服务器是否可用,检查 API 接口是否正确。
  • 特定用户才会出现的错误: 分析用户环境,例如浏览器版本、操作系统等。

表格:错误监控指标总结

指标 描述 监控方法 分析方向
JavaScript 错误 前端 JavaScript 代码中发生的错误 window.onerror 检查代码逻辑,修复 bug,考虑使用 try-catch 语句捕获潜在的错误。
HTTP 请求错误 前端应用与后端服务器通信时发生的错误 重写 XMLHttpRequest 对象,使用 fetch API 的 catch 方法 检查后端服务器是否可用,检查 API 接口是否正确,检查网络连接是否正常。
错误类型 错误的类型,例如 TypeErrorReferenceErrorSyntaxError 等。 window.onerror 根据错误类型分析错误的根本原因,例如 TypeError 可能表示变量类型错误,ReferenceError 可能表示变量未定义。
错误信息 错误的具体描述 window.onerror,重写 XMLHttpRequest 对象,使用 fetch API 的 catch 方法 根据错误信息快速定位错误发生的位置和原因。
错误堆栈 错误发生时的调用堆栈 window.onerror 根据错误堆栈追踪错误的调用路径,帮助定位错误的根本原因。
错误发生的文件和行号 错误发生的代码位置 window.onerror 精确定位错误发生的代码位置,方便快速修复 bug。
请求 URL 发生错误的请求 URL 重写 XMLHttpRequest 对象,使用 fetch API 的 catch 方法 检查请求 URL 是否正确,检查 API 接口是否正确。
HTTP 状态码 HTTP 响应的状态码,例如 404、500 等。 重写 XMLHttpRequest 对象,使用 fetch API 的 catch 方法 根据 HTTP 状态码判断错误类型,例如 404 表示资源未找到,500 表示服务器内部错误。

三、用户行为监控:页面访问、点击事件与表单提交

用户行为监控可以帮助我们了解用户如何使用我们的应用,发现用户体验的痛点,并根据用户行为进行优化。

3.1 页面访问监控:

我们需要监控的关键信息包括:

  • 页面 URL: 用户访问的页面 URL。
  • 访问时间: 用户访问页面的时间。
  • 停留时间: 用户在页面上停留的时间。
  • 来源 URL: 用户从哪个页面跳转到当前页面。

我们可以使用 history API 和 performance API 来监控页面访问。

(function() {
  let startTime = performance.now();
  let previousUrl = '';

  function recordPageView(url) {
    const now = performance.now();
    const duration = now - startTime;
    console.log('Page View:', url, duration, previousUrl);
    // 将数据发送到后端
    sendDataToBackend({
      type: 'page_view',
      url: url,
      duration: duration,
      previousUrl: previousUrl,
    });
    startTime = now;
    previousUrl = url;
  }

  // 监听 history.pushState 和 history.replaceState 事件
  const originalPushState = history.pushState;
  history.pushState = function(state, title, url) {
    originalPushState.apply(this, arguments);
    recordPageView(url || window.location.href);
  };

  const originalReplaceState = history.replaceState;
  history.replaceState = function(state, title, url) {
    originalReplaceState.apply(this, arguments);
    recordPageView(url || window.location.href);
  };

  // 监听 popstate 事件
  window.addEventListener('popstate', function(event) {
    recordPageView(window.location.href);
  });

  // 初始页面加载时记录页面访问
  recordPageView(window.location.href);
})();

代码解释:

  • 这段代码使用立即执行函数表达式 (IIFE) 来创建一个闭包,以避免污染全局命名空间。
  • startTime 记录页面加载或跳转的时间。
  • previousUrl 记录上一个页面的 URL。
  • recordPageView 函数记录页面访问信息,并将其发送到后端。
  • 我们重写了 history.pushStatehistory.replaceState 方法,以便在页面 URL 发生变化时记录页面访问。
  • 我们监听了 popstate 事件,以便在用户点击浏览器的前进或后退按钮时记录页面访问。
  • 在初始页面加载时,我们也记录页面访问。

3.2 点击事件监控:

我们需要监控的关键信息包括:

  • 点击元素: 用户点击的元素。
  • 点击时间: 用户点击元素的时间。
  • 点击位置: 用户点击元素的位置。

我们可以使用 addEventListener API 来监控点击事件。

document.addEventListener('click', function(event) {
  const target = event.target;
  const timestamp = Date.now();
  const x = event.clientX;
  const y = event.clientY;
  console.log('Click Event:', target, timestamp, x, y);
  // 将数据发送到后端
  sendDataToBackend({
    type: 'click_event',
    target: target.tagName + (target.id ? '#' + target.id : '') + (target.className ? '.' + target.className.split(' ').join('.') : ''),
    timestamp: timestamp,
    x: x,
    y: y,
  });
});

代码解释:

  • 我们监听了 document 对象的 click 事件。
  • event.target 是用户点击的元素。
  • event.clientXevent.clientY 是用户点击元素的位置。
  • 我们将点击事件信息发送到后端。

3.3 表单提交监控:

我们需要监控的关键信息包括:

  • 表单 ID: 用户提交的表单 ID。
  • 提交时间: 用户提交表单的时间。
  • 表单数据: 用户提交的表单数据。
  • 提交结果: 表单提交是否成功。

我们可以使用 addEventListener API 来监控表单提交事件。

const forms = document.querySelectorAll('form');

forms.forEach(form => {
  form.addEventListener('submit', function(event) {
    event.preventDefault(); // 阻止表单默认提交行为

    const formId = form.id;
    const timestamp = Date.now();
    const formData = new FormData(form);
    const data = {};
    for (let [key, value] of formData.entries()) {
      data[key] = value;
    }
    console.log('Form Submit:', formId, timestamp, data);

    // 将数据发送到后端
    sendDataToBackend({
      type: 'form_submit',
      formId: formId,
      timestamp: timestamp,
      data: data,
    });

    // 模拟表单提交成功
    setTimeout(() => {
      console.log('Form Submit Success:', formId);
      sendDataToBackend({
        type: 'form_submit_success',
        formId: formId,
      });
    }, 1000); // 模拟 1 秒的延迟
  });
});

代码解释:

  • 我们获取了所有 form 元素。
  • 我们监听了每个 form 元素的 submit 事件。
  • event.preventDefault() 阻止表单默认提交行为,以便我们可以自定义表单提交逻辑。
  • FormData 对象用于获取表单数据。
  • 我们将表单提交信息发送到后端。

3.4 用户行为监控数据分析:

收集到用户行为数据后,我们需要对其进行分析,找出用户体验的痛点,并根据用户行为进行优化。

  • 用户在哪些页面停留时间较长: 分析页面内容是否难以理解,页面布局是否不合理。
  • 用户经常点击哪些元素: 分析元素是否易于点击,元素功能是否符合用户预期。
  • 用户经常在哪些表单上提交失败: 分析表单填写是否困难,表单验证是否过于严格。

表格:用户行为监控指标总结

指标 描述 监控方法 分析方向
页面访问 用户访问的页面 history API,performance API 分析用户访问路径,了解用户对哪些页面感兴趣,优化用户访问流程。
停留时间 用户在页面上停留的时间 history API,performance API 分析用户在哪些页面停留时间较长,了解用户对哪些页面内容感兴趣,优化页面内容,提高用户参与度。
点击事件 用户点击的元素 addEventListener API 分析用户经常点击哪些元素,了解用户对哪些功能感兴趣,优化页面布局,提高用户点击效率。
表单提交 用户提交的表单 addEventListener API 分析用户经常在哪些表单上提交失败,了解表单填写是否存在困难,优化表单设计,提高表单提交成功率。
来源 URL 用户从哪个页面跳转到当前页面 history API,performance API 分析用户来源,了解用户从哪些渠道进入应用,优化推广策略。
点击位置 用户点击元素的位置 addEventListener API 可以做点击热力图,分析用户点击习惯。
表单数据 用户提交的表单数据 addEventListener API 分析用户提交的数据,了解用户行为模式,优化产品功能。
提交结果 表单提交是否成功 addEventListener API 分析表单提交失败的原因,优化表单验证逻辑,提高表单提交成功率。

四、数据驱动的优化策略

监控的最终目的是为了改进。通过对监控数据的分析,我们可以制定针对性的优化策略。

4.1 性能优化:

  • 优化图片: 使用合适的图片格式、压缩图片大小、使用图片懒加载。
  • 优化 JavaScript 代码: 减少 JavaScript 代码的执行时间、避免阻塞主线程。
  • 优化 CSS 代码: 减少 CSS 代码的大小、避免使用复杂的 CSS 选择器。
  • 使用 CDN: 使用 CDN 加速资源加载。
  • 代码分割: 将代码分割成多个 chunk,按需加载。
  • 预加载: 预加载关键资源,提高页面加载速度。

4.2 错误修复:

  • 修复 JavaScript 错误: 根据错误信息和堆栈信息,修复 JavaScript 代码中的 bug。
  • 修复 HTTP 请求错误: 检查后端服务器是否可用,检查 API 接口是否正确。

4.3 用户体验优化:

  • 优化页面布局: 根据用户行为数据,优化页面布局,提高用户点击效率。
  • 简化表单填写: 简化表单填写,减少用户填写错误。
  • 改进导航: 改进导航,方便用户找到所需信息。
  • 个性化推荐: 根据用户行为数据,进行个性化推荐。

如何更好地进行监控?

  • 选择合适的监控工具: 市面上有很多前端监控工具,例如 Sentry、Fundebug、阿里云 ARMS 等。选择合适的监控工具可以帮助我们更高效地进行监控。
  • 制定完善的监控策略: 制定完善的监控策略,明确监控的目标和指标。
  • 定期分析监控数据: 定期分析监控数据,找出问题并及时修复。
  • 自动化监控: 自动化监控,例如使用 CI/CD 工具进行性能测试。

本次分享就到这里,希望对大家有所帮助。

总结

前端监控涵盖性能、错误和用户行为三个关键领域。通过有效的数据收集和分析,我们可以深入了解应用程序的状态,及时发现问题,并根据数据驱动的策略进行优化,最终提升用户体验。选择合适的工具,制定完善的策略,并坚持定期分析和自动化监控,才能充分发挥前端监控的价值。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注