Skip to the content

Browser reports

Counts the intervention and deprecation reports of the Reporting API that the page can read, and sends one event for each report.

BrowserReportMonitor uses a ReportingObserver to get the reports that the page itself can read: interventions and deprecations. An intervention shows that the browser blocked or changed an action of the page, for example a heavy ad. The monitor counts each report and sends an event with its details.

What it measures

The signal is the number of reports, with the attribute type (intervention or deprecation).

The monitor answers this question: did the browser act against the page, or does the page use APIs that the browser will remove? An intervention can explain a change of the behavior of the page. A deprecation tells you to change the code before the browser removes the API.

How it works

  1. The monitor makes a ReportingObserver for the types intervention and deprecation, with buffered: true. Thus it also gets the reports from before its start. After stop() and start(), the new observer does not ask for the buffered reports, because the monitor counted them already. The monitor does not get the reports during the stop.
  2. For each report of these types, it reads id, message, sourceFile and lineNumber from the body. A missing field gives an empty string or 0.
  3. The factory adds 1 to lag_browser_reports and, with an event sink, sends a lag.browser_report event. It sends at most 10 events in each period of 60 s, and it removes the query string and the fragment from the source file.

When the constructor fails, the monitor logs a warning and gets no reports. The monitor cannot get crash reports, for example oom or unresponsive. The browser sends them only to the endpoints of the Reporting-Endpoints header, because the page is gone. The page-view context puts the page-view ID into those crash reports.

Browser support

FeatureChromiumFirefoxSafari
ReportingObserver69 (workers from 84)149 (also in workers)16.4 (not in workers)
Report type deprecation69149 (not in workers)not available
Report type intervention69not availablenot available

Safari has ReportingObserver, but it has neither of the two types, thus the monitor gets no reports there. MDN and the compatibility data mark InterventionReportBody as deprecated, but Chromium still sends intervention reports, for example for heavy ads (browser support).

Measurement validity

The monitor does not pause while the page is hidden, and it uses no SampleValidator. It counts reports, not durations.

Metrics

MetricKindUnitAttributesDescription
lag_browser_reportsCounter{report}typeThe number of reports from the Reporting API, for example interventions and deprecations.

The browser report event

lag.browser_report has these attributes:

AttributeValue
typeintervention or deprecation
idThe ID of the report, for example HeavyAdIntervention.
messageThe message of the browser.
source_fileThe file that caused the report, without the query string and the fragment.
line_numberThe line in the file, or 0.

setupAllMonitors() adds lag.page_view.id to each event.

Configuration

function createInstrumentedBrowserReports(
    deps : CoreDeps & ReportingDeps & Partial<EventDeps> & Partial<AbsoluteClockDeps> & Partial<PerformanceDeps>,
) : MonitorHandle<BrowserReportMonitor>;

The factory uses CoreDeps (logger, clock, meter) and ReportingDeps (ReportingObserver). EventDeps is optional: without it, the factory sends no events. The event limit (10 each minute) is a constant of the factory. The class takes the list of report types as its last argument.

AbsoluteClockDeps (absoluteClock) and PerformanceDeps (performance) are optional: they give the events their times. Without both, the events get the time of the call. A report has no time of its own, thus the time of an event is the time of the delivery.

import { metrics } from "@opentelemetry/api";
import { logs } from "@opentelemetry/api-logs";
import { createBrowserDeps, createInstrumentedBrowserReports, createOtelEventSink } from "@mark1russell7/lag";

const deps = createBrowserDeps(window, {
    logger : console,
    meter : metrics.getMeter("lag"),
    events : createOtelEventSink(logs.getLogger("lag-events")),
});
if (deps.ReportingObserver) {
    const reports = createInstrumentedBrowserReports({ ...deps, ReportingObserver : deps.ReportingObserver });
    reports.stop();
}

Cost

  • Observers: one ReportingObserver. No timers.
  • Records: one counter value for each report.
  • Events: at most 10 each minute.

Limits

  • Only two types. The monitor ignores the other report types, for example csp-violation and permissions-policy-violation.
  • No crash reports. A page cannot read its own crash reports.
  • Events are limited. After 10 events in a period of 60 s, the factory sends no more events in that period. The counter still counts each report.
  • Safari gives no reports. It has no deprecation or intervention reports.

Tests

Unit tests:

  • BrowserReportMonitor.test.ts: the observer asks for interventions and deprecations, with buffered: true. The monitor reads the fields of each report body. Without ReportingObserver, it logs a warning. An error in the report callback does not stop the other reports. stop() disconnects the observer, and the monitor can start again. A fake of the Reporting API with a report buffer makes sure that a start after a stop counts no report two times.
  • setup-all-monitors.test.ts: a HeavyAdIntervention report gives an event with the source file without its query string.
  • support.test.ts: the rate limiter lets the limit of actions occur in each window, and stripUrlParameters() removes the query string and the fragment.

Browser test (Vitest browser mode with Playwright, in Chromium, Firefox, WebKit and Chrome):

  • lag-monitors.test.ts: the setup starts the monitor only where the browser has ReportingObserver. No browser test examines its values at this time.

Source

lag: Main-thread responsiveness monitoring for browser apps, exported as OpenTelemetry metrics.

To change a page, edit its file in packages/site/content/. The writing style guide tells you how.

An AI model (Claude, from Anthropic) wrote most of the text and the code of this site and of the library, under the direction of the author. The tests and the STE linter examine them. The writing standard gives the reason for this note.