文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

多数大规模的网络应用系统至少会结合一些浏览器监控功能以收集用户在浏览器的JS脚本语言的使用体验指标,然而,作为一个团体,我们不过多地讲述哪些东西在运行或没有运行。对于Github网站,在规模上我们已经采取了不同于许多其他公司的措施,因此我们希望与你们分享我们的浏览器监控设置的运行概况。

浏览器监控可能出现一个优化问题:如果你十分关切想监控你的用户的浏览器使用体验的话,你很可能也是那一种很在意安全、隐私及表现的公司。浏览器监控可以成为你了解用户体验的一个神奇的窗口,但同时因为它经常依赖于装载外来脚本及需发送用户数据到一个第三方应用以便收集展示,在所有这三个首要方面它都很典型地出现权衡取舍问题。

第 1 段(可获 2 积分)

作为 GitHub.com 我们已经知道很多通用的第三方解决方案并不适合我们,因为安全设置方面的问题。GitHub.com 设置了合理的 Content Security Policy (CSP) 限制头部用于预防 XSS 跨站点脚本攻击的错误。这些头部阻止 JavaScript(包括在 <script> 标签内的代码)在最新的支持 CSP 的浏览器上执行。我们同时又一个专门的白名单域名列表,可直接远程加载脚本,例如我们的 CDN 网络,并确保可执行 JavaScript 的范围是最小的。这个自动化的处理可以通过编写特定请求的数据来阻止一些常见的真实用户检测 (RUM)/浏览器监控工具(例如服务器响应时间),这些代码是在 HTML 页面中的 <script> 中编写,此举使得其他依赖第三方脚本的解决方案不那么有价值,不过我们需要添加另外的脚本源并相信这些脚本对性能和安全方面不会造成什么问题

第 2 段(可获 2 积分)

作为一个应用工程师,我很幸运能与一个痴迷于监控和度量的基础架构团队一起工作,并且他们基于开源技术构建了一个灵活的图形化得监控和报警工具链,这些开源软件包括 Graphite, Nagios, 和 D3 等等。尽管这些工具一开始并非为客户端监控而开发,但是它们足够灵活,可以很方便的添加浏览器监控的功能。我们已经收集了来自 Github.com Rails 应用的一些统计数据,因此收集浏览器数据只需要一些简单的 JavaScript 代码即可,此外需要一些额外的 Rails 代码用来清理和过滤一些没用的数据。

第 3 段(可获 2 积分)

我们要监控什么

页面加载性能

和其他规模跟我们差不多的网站类似,我们希望了解最终用户打开网页时内容加载到底有多快,因此我们需要监测每个浏览器的页面加载时间。一些第三方的工具可以让这个需求变动很容易实现,并通过一个数据(加载时间)或者两个数据(后台执行时间、网络传输时间以及浏览器渲染时间和其他相关资源加载的时间)显示页面加载性能。而我们需要更细粒度的分析。因此我们用 JavaScript 编写了我们自己的指标收集工具,用来收集上述两个数据是非常简单的,而且我们并非使用浏览器的 Navigation Timing API 来收集页面加载成功后的相关数据。

第 4 段(可获 2 积分)

The Navigation Timing API (diagram from http://www.w3.org/TR/navigation-timing/)

我们使用一个叠加图来展示时间和图之间的增量关系。同时也有一个如下的整体图来展示一些其他的统计数据,例如浏览器 (Safari, Firefox, Internet Explorer) 和页面 ( repositories#show 页面或者 blob#edit 页面).

Our Navigation Timing API stats

这意味着需要收集更多的数据,而不只是窗口加载时间,通过这个图表可以有助于性能的问题诊断,而不是只是标注出是否有性能问题。这个图显示从最早到最新的时序数据堆叠,可用于缩小性能问题的范围。例如如果后台应用很慢,那么在这个图中就会突出显示在 request 和 response 层中,而一个慢的图片 CDN 会在 domComplete 层中显示,一个 JS 框架的初始化的性能问题会在 domInteractive 层中显示。

第 5 段(可获 2 积分)

我们仍然支持一些老的没有实现 Navigation Timing API 的浏览器,因此我们收集了模拟 time-to-window-load 或者是 “cross-browser load time.” 的数据。这些都是通过 JavaScript 来计算的,我们在 pagehide (当用户离开或者关闭页面)保存一个“模拟浏览开始”时间戳到浏览器的 sessionStorage 。在下一次页面加载时,通过计算开始时间和 Windows 加载时间之间的时间差来获得。这会得到一个非常大的数值,如果需要的话可以在面板或者报表中显示,可以帮助我们在不支持 Navigation Timing API 的浏览器上跟踪用户体验,这其实与支持 Navigation Timing API 的浏览器区别并不大。

第 6 段(可获 2 积分)
# Browser page load timing stats collection # - collects all Navigation Timing metrics # - collects simulated time-to-load in browsers without nav timing (Safari < 8) 
setNavigationStart = ->
  unless window.performance.timing
    try sessionStorage.setItem 'navigationStart', Date.now()

sendTimingResults = ->
  # Defer report until next tick so we catch the tail end of the load event timing.   setTimeout ->
    timing =  window.performance?.timing or {}

    # Merge in simulated cross-browser load event     timing['crossBrowserLoadEvent'] = Date.now()

    if chromeFirstPaintTime = window.chrome?.loadTimes?()?.firstPaintTime
      # firstPaintTime is in seconds; convert to milliseconds to match       # performance.timing.       timing['chromeFirstPaintTime'] = Math.round chromeFirstPaintTime * 1000

    # Merge in simulated navigation start, if no navigation timing     if not window.performance.timing
      navStart = try sessionStorage.getItem 'navigationStart'
      timing['simulatedNavigationStart'] = parseInt(navStart, 10) if navStart

    GitHub.reportStats timing
  , 0

$(window).on 'pagehide', setNavigationStart
$(window).on 'load', sendTimingResults
第 7 段(可获 2 积分)

模拟负载性能

由于 GitHub.com 并非一个单页应用,我们使用 Pjax 加载了大量的内容,Pjax 是一个 jQuery 的插件,使用 AJAX 和 pushState 在用户点击一个链接时加载页面的部分内容。Pjax 用来替换浏览器的标准导航行为,通常速度能提升 1000 毫秒。从用户的角度看,pjax 加载就是页面的加载,但对浏览器来说是另外一个 ajax 请求。pjax 的目的是为了提升用户操作的性能,但我们因此就没法收集常规的页面加载指标以确定性能是否更佳。

第 8 段(可获 2 积分)

为了检测 pjax 耗费的时间,我们使用类似的策略来模拟“浏览器开始” 行为。Pjax 可以在每次导航 'pjax:start''pjax:end' 时触发对应事件。当我们接收到首个事件,我们记录启动的时间戳,当我们接收到第二个事件,可以调用 setImmediate 方法来等待方法调用返回并记录结束时间戳。面向用户的功能代码也可以侦听 'pjax:end' 事件并做 DOM 操作或者数据处理。在等待下一个记号时,我们可以计算整个操作耗费的事件,类似浏览器的 domContentLoaded 事件。

第 9 段(可获 2 积分)

使用 pjax 加载页面来替换标准浏览器的浏览行为也会产生一些不利的因素 —— 例如没有默认的加载中的指示,这跟标准浏览器导航的行为不同,因为你可以在导航条上看到正在加载的动画指示。如果一个 pjax 请求超时或者出错,我们会变成标准的全页面导航。这个时候用户就会碰到特别慢的页面加载。这意味着比较 pjax 和标准页面加载的性能是非常重要的 —— 如果我们可以量化到底我们为用户的访问节省了多少时间,这就可以帮我们确认是否有必要将更多的链接改成 pjax 的方式。

第 10 段(可获 2 积分)

pjax comparison graph

JavaScript 错误

另外我们主要关注的问题是 JavaScript 错误。我们发布的过程一般是小改动、频繁更新。可能一个提交操作到更新到生产系统之间的时间不过是几分钟而已。小改动、频繁更新可以降低每次部署更新的风险,但是高频更新意味着我们不可能在发布之前做太多的跨浏览器的回归测试。因此这项监控可以让我们定位到一些出错的问题。

第 11 段(可获 2 积分)

GitHub 内部维护了一个名为 Haystack 的应用程序,这是一个错误报告工具可以用来查看 Github.com 的应用级错误。Haystack 一开始并非报告 JavaScript 错误而开发的,其之前用于显示 Rails 应用的错误,如果我们可以发送 JS 错误到 Rails 应用,那么我们也可以发送这些错误到 Haystack。

客户端上用于手机错误的 JavaScript 相当简单。我们有一个针对未捕获的 JavaScript 错误的全局事件处理器,大多数的信息来自于 Error 对象本身或者错误触发的事件,包括错误信息、文件名、行数、堆栈以及事件对象。

第 12 段(可获 2 积分)
# 报告 JavasScript 错误到 Haystack loadTime = window.performance.now()

$(window).on 'error', (event) ->
  {message, filename, lineno, error} = event.originalEvent

  context = {
    message, filename, lineno
    url: window.location.href
    readyState: document.readyState
    referrer: document.referrer
    stack: error?.stack
    historyState: JSON.stringify window.history.state
    timeSinceLoad: window.performance.now() - loadTime
  }

  # Turn the actual target into a selector so we can see it in Haystack.   context.eventTarget = $(context.eventTarget).inspect() if context.eventTarget?

  # Report errors to app   errorsURL = document.querySelector('meta[name=browser-errors-url]').content
  fetch errorsURL,
    method: 'POST'
    body: JSON.stringify context

  return
第 13 段(可获 2 积分)

JavaScript 的错误报告总是有一些噪音 —— 浏览器扩展代码可能会触发一些看似正常的错误;此外一些 API 也可能无法在每日构建以及测试版的浏览器上运行等等。我们必须对这些与应用无关的 JavaScript 错误报告的噪音进行过滤。多数时候,这些无关的错误比我们发送到后端 Haystack 的错误要多得多。我们通过错误堆栈中的浏览器扩展文件进行过滤,这似乎是跟扩展的行为相关,一些特别错误我们加到黑名单中将直接被忽略(例如一个弹窗的阻止扩展)。同事还有很多一次性的错误不值得深挖,但我们可以通过每小时多次发生的错误或者是多个用户碰到的错误来发现真正的问题。

第 14 段(可获 2 积分)

这个时候我们可能会问:“为什么不在发布之前通过编写浏览器测试来捕获错误和一些回归测试的问题”? 这是一个很好的问题 —— 我绝对不认为客户端监控会完全取代集成和验收测试。但测试几乎无法捕获在真实产品环境中以及各种用户浏览器版本下的真实情况。而浏览器监控可以完全捕获这些问题。此外,测试只在你提交代码并运行的时候检查错误,而前端代码的环境一直在变化,我们需要一直知道用户碰到的新的错误,而不管这些错误来自我们新发布的代码或者是 Google 新推出的 Chrome 版本。

第 15 段(可获 2 积分)

进展如何?

维护数据收集并制图的这一套工作并不是对每个公司都有意义,但对我们来说受益匪浅。一个事实就是我们的浏览器监控使用和其他团队一样的后台和网络统计,这可降低那些对前端指标感兴趣的公司的门槛。对编写前端代码的人来说,我希望设计网络相关的浏览时间指标,例如 domainLookupStart, domainLookupEnd, connectStartconnectEnd ,这样我就可以确认前端的一些性能问题是否是浏览器端戴拿的问题,而不是网络问题。而一旦这些数据通过图形展示出来,整个公司的人都可以方便的查看。这个作用很大,例如可以检测 DDoS 攻击对终端用户的影响。

第 16 段(可获 2 积分)

另外一个浏览器监控的好处是,它可以让我们更容易的收集到各种新的信息。在过去的一年里,我们的工程师已经根据这些监控的数据增加了一些新的指标类型,而这个工作一般只需编写不足 100 行的代码即可。我们也开始跟踪支持新浏览器的特性(例如支持 User Timing API 或者支持原生显示 emoji 表情),这个代码类似我们的性能指标代码,通过浏览器指定类型的 JavaScript 错误并在 Haystack 中显示这些错误,可以跟踪可访问性方面的问题。

第 17 段(可获 2 积分)

下一步?

我们可以非常直观的通过浏览器监控工具来了解用户在使用 Github.com 的体验。但这个必须是我们经常看的时候才有效 —— 但其实应该有一些更快的方法来捕捉前端问题。接下来我们要通过更多易操作的前端代码来实现报警功能。Github 的架构团队已经完成了一些非常惊艳的工作来允许工程师从不同的渠道中获取告警信息。我想能够将浏览器的指标存放到告警工具中,这些指标可为后台指标所用,如果前端出现问题,或者性能有些下降时我们就可以即时的在聊天工具上得到提醒。

第 18 段(可获 2 积分)

文章评论