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
- The monitor makes a
ReportingObserverfor the typesinterventionanddeprecation, withbuffered: true. Thus it also gets the reports from before its start. Afterstop()andstart(), 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. - For each report of these types, it reads
id,message,sourceFileandlineNumberfrom the body. A missing field gives an empty string or 0. - The factory adds 1 to
lag_browser_reportsand, with an event sink, sends alag.browser_reportevent. 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
| Feature | Chromium | Firefox | Safari |
|---|---|---|---|
ReportingObserver | 69 (workers from 84) | 149 (also in workers) | 16.4 (not in workers) |
Report type deprecation | 69 | 149 (not in workers) | not available |
Report type intervention | 69 | not available | not 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
| Metric | Kind | Unit | Attributes | Description |
|---|---|---|---|---|
lag_browser_reports | Counter | {report} | type | The number of reports from the Reporting API, for example interventions and deprecations. |
The browser report event
lag.browser_report has these attributes:
| Attribute | Value |
|---|---|
type | intervention or deprecation |
id | The ID of the report, for example HeavyAdIntervention. |
message | The message of the browser. |
source_file | The file that caused the report, without the query string and the fragment. |
line_number | The 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-violationandpermissions-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, withbuffered: true. The monitor reads the fields of each report body. WithoutReportingObserver, 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: aHeavyAdInterventionreport 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, andstripUrlParameters()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 hasReportingObserver. No browser test examines its values at this time.
Source
BrowserReportMonitor.ts: the monitor.instrumented/browser-reports.ts: the factory and the event.rate-limiter.ts: the event limit and the removal of URL parameters.