网络错误日志记录 (NEL)

简介

网络错误日志记录 (NEL) 是一种从来源收集客户端网络错误的机制。

它使用 NEL HTTP 响应标头来告知浏览器收集网络错误,然后与 Reporting API 集成以向服务器报告错误。

旧版 Reporting API 概述

要使用旧版 Reporting API,您需要设置 Report-To HTTP 响应标头。它的值是一个对象,用于描述浏览器要向其报告错误的端点组

Report-To:
{
    "max_age": 10886400,
    "endpoints": [{
    "url": "https://analytics.provider.com/browser-errors"
    }]
}

如果您的端点 URL 与您的网站位于不同的来源,则该端点应支持 CORS 预检请求。(例如 Access-Control-Allow-Origin: *; Access-Control-Allow-Methods: GET,PUT,POST,DELETE,OPTIONS; Access-Control-Allow-Headers: Content-Type, Authorization, Content-Length, X-Requested-With)。

在该示例中,将此响应标头与您的主页一起发送会将浏览器配置为向端点 https://analytics.provider.com/browser-errors 报告浏览器生成的警告,持续 max_age 秒。请务必注意,页面发出的所有后续 HTTP 请求(用于图像、脚本等)都将被忽略。配置是在主页的响应期间设置的。

标头字段说明

每个端点配置都包含一个 group 名称、max_ageendpoints 数组。您还可以选择在使用 include_subdomains 字段报告错误时是否考虑子域。

字段 类型 描述
group 字符串 可选。如果未指定 group 名称,则端点将获得名称“default”。
max_age 数字 必需。一个非负整数,用于定义端点以秒为单位的生命周期。“0”值将导致从用户代理的报告缓存中删除端点组。
endpoints Array<Object> 必需。指定报告收集器的实际 URL 的 JSON 对象数组。
include_subdomains 布尔值 可选。一个布尔值,用于为当前来源主机的所有子域启用端点组。如果省略或不是“true”的任何其他值,则不会向端点报告子域。

group 名称是用于将字符串与端点关联的唯一名称。在与 Reporting API 集成的其他位置使用此名称来引用特定的端点组。

max-age 字段也是必需的,它指定浏览器应使用端点并向其报告错误的时间长度。

endpoints 字段是一个数组,用于提供故障转移和负载平衡功能。请参阅关于 故障转移和负载平衡的部分。请务必注意,即使组在 endpoints 中列出了多个收集器,浏览器也只会选择一个端点。如果您想一次将报告发送到多台服务器,则您的后端将需要转发报告。

浏览器如何发送报告?

浏览器会定期批量处理报告,并将它们发送到您配置的报告端点。

为了发送报告,浏览器会发出 POST 请求,其中包含 Content-Type: application/reports+json 和包含捕获的警告/错误数组的正文。

浏览器何时发送报告?

报告的传递与您的应用无关,这意味着浏览器控制何时将报告发送到您的服务器。

浏览器会尝试在最合适的时间传递排队的报告。这可能在报告准备就绪后立即进行(以便向开发者提供及时的反馈),但如果浏览器正忙于处理更高优先级的工作,或者用户当时处于缓慢和/或拥塞的网络中,则浏览器也可能会延迟传递。如果用户是频繁访问者,则浏览器也可能会优先发送关于特定来源的报告。

使用 Reporting API 时,几乎没有性能问题(例如,与您的应用发生网络争用)。也没有办法控制浏览器何时发送排队的报告。

配置多个端点

通过发送多个 Report-To 标头,单个响应可以同时配置多个端点

Report-To: {
             "group": "default",
             "max_age": 10886400,
             "endpoints": [{
               "url": "https://example.com/browser-reports"
             }]
           }
Report-To: {
             "group": "network-errors-endpoint",
             "max_age": 10886400,
             "endpoints": [{
               "url": "https://example.com/network-errors"
             }]
           }

或者通过将它们合并到单个 HTTP 标头中

Report-To: {
             "group": "network-errors-endpoint",
             "max_age": 10886400,
             "endpoints": [{
               "url": "https://example.com/network-errors"
             }]
           },
           {
             "max_age": 10886400,
             "endpoints": [{
               "url": "https://example.com/browser-errors"
             }]
           }

一旦您发送了 Report-To 标头,浏览器就会根据其 max_age 值缓存端点,并将所有那些令人讨厌的控制台警告/错误发送到您的 URL。

故障转移和负载平衡

大多数时候,您将为每个组配置一个 URL 收集器。但是,由于报告可能会产生大量的流量,因此规范中包含了受 DNS SRV 记录启发的故障转移和负载平衡功能。

浏览器将尽最大努力将报告传递到组中最多一个端点。可以为端点分配 weight 以分配负载,每个端点接收指定比例的报告流量。还可以为端点分配 priority 以设置回退收集器。

仅当上传到主收集器失败时,才会尝试回退收集器。

示例:在 https://backup.com/reports 创建一个回退收集器

Report-To: {
             "group": "endpoint-1",
             "max_age": 10886400,
             "endpoints": [
               {"url": "https://example.com/reports", "priority": 1},
               {"url": "https://backup.com/reports", "priority": 2}
             ]
           }

设置网络错误日志记录

设置

要使用 NEL,请使用使用命名组的收集器设置 Report-To 标头

Report-To: {
    ...
  }, {
    "group": "network-errors",
    "max_age": 2592000,
    "endpoints": [{
      "url": "https://analytics.provider.com/networkerrors"
    }]
  }

接下来,发送 NEL 响应标头以开始收集错误。由于 NEL 是针对来源选择加入的,因此您只需发送一次标头。 NELReport-To 都将应用于将来对同一来源的请求,并将根据用于设置收集器的 max_age 值继续收集错误。

标头值应为 JSON 对象,其中包含 max_agereport_to 字段。使用后者引用您的网络错误收集器的组名

GET /index.html HTTP/1.1
NEL: {"report_to": "network-errors", "max_age": 2592000}

子资源

示例:如果 example.com 加载 foobar.com/cat.gif 并且该资源加载失败

  • foobar.com 的 NEL 收集器会收到通知
  • example.com 的 NEL 收集器不会收到通知

经验法则是 NEL 重现服务器端日志,只是在客户端生成。

由于 example.com 无法看到 foobar.com 的服务器日志,因此它也无法看到其 NEL 报告。

调试报告配置

如果您没有在服务器上看到报告显示,请转到 chrome://net-export/。该页面对于验证配置是否正确以及报告是否正确发送非常有用。

ReportingObserver 呢?

ReportingObserver 是一种相关的但不同的报告机制。它基于 JavaScript 调用。它不适用于网络错误日志记录,因为无法通过 JavaScript 拦截网络错误。

示例服务器

下面是一个使用 Express 的示例 Node 服务器。它展示了如何配置网络错误的报告,并创建了一个专用处理程序来捕获结果。

const express = require('express');

const app = express();
app.use(
  express.json({
    type: ['application/json', 'application/reports+json'],
  }),
);
app.use(express.urlencoded());

app.get('/', (request, response) => {
  // Note: report_to and not report-to for NEL.
  response.set('NEL', `{"report_to": "network-errors", "max_age": 2592000}`);

  // The Report-To header tells the browser where to send network errors.
  // The default group (first example below) captures interventions and
  // deprecation reports. Other groups, like the network-error group, are referenced by their "group" name.
  response.set(
    'Report-To',
    `{
    "max_age": 2592000,
    "endpoints": [{
      "url": "https://reporting-observer-api-demo.glitch.me/reports"
    }],
  }, {
    "group": "network-errors",
    "max_age": 2592000,
    "endpoints": [{
      "url": "https://reporting-observer-api-demo.glitch.me/network-reports"
    }]
  }`,
  );

  response.sendFile('./index.html');
});

function echoReports(request, response) {
  // Record report in server logs or otherwise process results.
  for (const report of request.body) {
    console.log(report.body);
  }
  response.send(request.body);
}

app.post('/network-reports', (request, response) => {
  console.log(`${request.body.length} Network error reports:`);
  echoReports(request, response);
});

const listener = app.listen(process.env.PORT, () => {
  console.log(`Your app is listening on port ${listener.address().port}`);
});

延伸阅读