Внешний вид системы мониторинга

JavaScript монитор

Предисловие: Создайте подключаемую интерфейсную систему мониторинга, которую можно подключать и отключать по желанию.

1. Сбор данных

1. Аномальные данные

1.1 Исключение статического ресурса

Используйте window.addEventListener('error',cb)

Так как этот метод будет фиксировать много ошибок, нам необходимо отфильтровать ошибки загрузки файла статических ресурсов, здесь отслеживаются только js, css и img.

⚠️ Используйте localName, чтобы получить имя элемента выбранного элемента, если это не элемент, localName имеет значение null

// 捕获静态资源加载失败错误 js css img
window.addEventListener('error', e => {
    const target = e.target
    if (!target) return
    const typeName = e.target.localName;
    let sourceUrl = "";
    if (typeName === "link") {
        sourceUrl = e.target.href;
    } else if (typeName === "script" || typeName === "img") {
        sourceUrl = e.target.src;
    }

    if (sourceUrl) {
        lazyReportCache({
            url: sourceUrl,
            type: 'error',
            subType: 'resource',
            startTime: e.timeStamp,
            html: target.outerHTML,
            resourceType: target.tagName,
            paths: e.path.map(item => item.tagName).filter(Boolean),
            pageURL: getPageURL(),
        })
    }
}, true)

1.2 JS-ошибка

Получить строку, номер столбца и стек ошибок при возникновении ошибки через window.onerror

В производственной среде необходимо загрузить файл карты, сгенерированный после упаковки, и использовать исходную карту для получения количества строк ошибок и исходных файлов до распаковки из сжатых файлов кода, а также номеров строк и столбцов.

// parseErrorMsg.js
const fs = require('fs');
const path = require('path');
const sourceMap = require('source-map');

export default async function parseErrorMsg(error) {
  const mapObj = JSON.parse(getMapFileContent(error.url))
  const consumer = await new sourceMap.SourceMapConsumer(mapObj)
  // 将 webpack://source-map-demo/./src/index.js 文件中的 ./ 去掉
  const sources = mapObj.sources.map(item => format(item))
  // 根据压缩后的报错信息得出未压缩前的报错行列数和源码文件
  const originalInfo = consumer.originalPositionFor({ line: error.line, column: error.column })
  // sourcesContent 中包含了各个文件的未压缩前的源码,根据文件名找出对应的源码
  const originalFileContent = mapObj.sourcesContent[sources.indexOf(originalInfo.source)]
  return {
    file: originalInfo.source,
    content: originalFileContent,
    line: originalInfo.line,
    column: originalInfo.column,
    msg: error.msg,
    error: error.error
  }
}

function format(item) {
  return item.replace(/(\.\/)*/g, '')
}

function getMapFileContent(url) {
  return fs.readFileSync(path.resolve(__dirname, `./dist/${url.split('/').pop()}.map`), 'utf-8')
}

1.3 Пользовательские исключения

Печатается через console.error, считаем это заказной ошибкой

Используйте window.console.error, чтобы сообщить информацию о пользовательском исключении.

1.4 Исключение интерфейса

  1. Когда код состояния ненормальный, сообщите об исключении
  2. Переопределить метод onloadend и сообщить об исключении, когда значение кода в объекте ответа не равно «000000».
  3. Перепишите метод onerror.Когда сеть прерывается, событие onload(end) не может быть запущено, и будет запущено onerror, и в это время будет сообщено об исключении.

1.5 Прислушивайтесь к необработанным ошибкам промисов

Когда обещание отклонено и нет обработчика отклонения, запускается событие необработанного отклонения.

Используйте window.addEventListener('unhandledrejection',cb)

⚠️ Полученная информация представлена ​​на рисунке ниже, где Reason.stack — это нужная нам информация.

image.png

2. Данные о производительности

2.1 FP/FCP/LCP/CLS

Команда разработчиков Chrome разработала ряд показателей для определения производительности веб-страницы:

  • FP (first-paint), время от загрузки страницы до появления первого пикселя на экране.
  • FCP (first-contentful-paint), время с момента загрузки страницы до завершения отображения любой части содержимого страницы на экране.
  • LCP (largest-contentful-paint), время от начала загрузки страницы до завершения рендеринга самого большого текстового блока или элемента изображения на экране.
  • CLS (layout-shift), начиная с загрузки страницы и еесостояние жизненного циклаСовокупный балл для всех неожиданных смещений макета, произошедших во время скрытия.


Среди них первые три показателя эффективности могут быть переданы напрямуюPerformanceObserver(PerformanceObserver — это объект мониторинга производительности, используемый для мониторинга событий измерения производительности) для получения. CLS, с другой стороны, требует некоторых вычислений.

В расчете, чтобы выяснить это раньше, мы сначала рассмотрим концепцию окна беседы: смещение между одним или несколькими макетами, интервал между ними составляет менее 1 секунды, а макет первого и последнего временного интервала смещения ограничен 5 секунд, макет смещения более 5 секунд будет разделен на новое окно разговора.

Команда Chrome Speed ​​Metrics работает над этимАнализ в масштабеПосле этогоСмещение совокупного максимального значения во всех окнах сеансаИспользуется для отображения наихудшего случая макета страницы (например, CLS).

Как показано на рисунке ниже: окно сеанса 2 имеет лишь небольшое смещение макета, тогда окно сеанса 2 будет игнорироваться, а CLS вычисляет только сумму смещений макета в окне сеанса 1.

拉低平均值的小布局偏移示例

2.2 Событие DOMContentLoaded и событие загрузки

  • DOMContentLoaded: HTML-документ загружен и проанализирован. Когда в документе нет скрипта, браузер может вызвать DOMContentLoaded после синтаксического анализа документа; когда в документе есть скрипт, скрипт блокирует синтаксический анализ документа, и скрипт должен ждать CSS перед скрипт, который нужно загрузить, прежде чем он сможет быть выполнен. Но в любом случае DOMContentLoaded не нужно ждать парсинга других ресурсов, таких как изображения.
  • onload: он не будет запущен, пока все другие ресурсы, такие как изображения, видео, аудио и т. д. на странице, не будут загружены.

Почему при разработке мы уделяем особое внимание размещению css в начале, а js в конце?

image.png

Во-первых, порядок размещения файлов определяет приоритет загрузки.Во избежание перестановки страницы или перерисовки, вызванной изменением стиля, браузер блокирует отрисовку содержимого.После того, как все CSS загружены и обработаны, содержимое страницы будет отрисовано. одновременно Появится «белый экран».

Чтобы оптимизировать взаимодействие с пользователем, современным браузерам не нужно ждать, пока будут проанализированы все HTML-документы, прежде чем начинать построение дерева рендеринга макета, а это означает, что браузер может отображать неполное дерево DOM и cssom и сокращать время белого экрана. как можно скорее.

Предположим, мы поставили js во главе, js будет блокировать парсинг dom, вызывая задержку FP (First Paint), поэтому мы ставим js в конец, чтобы сократить время FP, но это не уменьшит время срабатывания DOMContentLoaded .

2.3 Длительная загрузка ресурсов и попадание в кеш

пройти через PerformanceObserverКоллекция, когда браузер не поддерживает PerformanceObserver, ее также можно понизить через performance.getEntriesByType(entryType), где:

  • Navigation TimingСобраны показатели производительности для HTML-документов.
  • Resource TimingСобирает показатели производительности ресурсов, от которых зависит документ, таких как: css, js, изображения и т.д.

Здесь не учитываются следующие типы ресурсов:

  • маяк: используется для сообщения данных, а не статистики
  • xmlhttprequest: отдельная статистика

Мы можем получить следующую информацию об объекте ресурса:

image.png

Используйте performance.now() для точного расчета времени выполнения программы:

  • Разница между performance.now() и Date.now() заключается в том, что они возвращают время в микросекундах (миллионных долях секунды), что является более точным. И в отличие от Date.now(), на который влияет блокировка выполнения системной программы, время performance.now() увеличивается с постоянной скоростью и не зависит от системного времени (системное время можно настроить вручную или с помощью программного обеспечения).
  • Date.now() выводит время UNIX, т. е. время с 1970 года, а performance.now() выводит время относительно performance.timing.navigationStart (инициализация страницы).
  • Различия с использованием Date.now() не совсем точны из-за системных ограничений (могут блокироваться) при расчете времени. Но использование разницы в performance.now() не влияет на точное время, когда мы вычисляем время выполнения программы.

Определите, попадает ли ресурс в кеш:
В этих объектах ресурсов есть поле transferSize, которое указывает размер полученного ресурса, включая поле заголовка ответа и размер данных ответа. Если это значение равно 0, оно считывается непосредственно из кеша (принудительный кеш). Если это значение не равно 0, а поле encodedBodySize равно 0, это означает, что выполняется согласование кэша (encodedBodySize указывает размер тела данных ответа на запрос). Если вышеуказанные условия не выполняются, это означает, что кеш не попал.

2.4 Отнимающие много времени запросы интерфейса и успех или неудача вызовов интерфейса

Перепишите методы отправки и открытия в цепочке прототипов XMLHttpRequest.

import { originalOpen, originalSend, originalProto } from '../utils/xhr'
import { lazyReportCache } from '../utils/report'

function overwriteOpenAndSend() {
    originalProto.open = function newOpen(...args) {
        this.url = args[1]
        this.method = args[0]
        originalOpen.apply(this, args)
    }
    
    originalProto.setRequestHeader = function newSetRequestHeader(...args) {
        const header = args[0];
        const value = args[1];
        this.reqHeaders[header] = value;
        originalOpen.originalSetRequestHeader(this, args);
    }

    originalProto.send = function newSend(...args) {
        this.startTime = Date.now();
        this.reqBody = args[0];

        const onLoadend = () => {
            this.endTime = Date.now()
            this.duration = this.endTime - this.startTime
            
            // 获取请求 url, method, status, response, reqBody, reqHeaders, duration, startTime, endTime,
            console.log(this);
            reportError(this);
            reportPerformance(this);

            this.removeEventListener('loadend', onLoadend, true)
        }

        this.addEventListener('loadend', onLoadend, true)
        originalSend.apply(this, args)
    }
}

export default function xhr() {
    overwriteOpenAndSend()
}

2. Отчетность по данным

1. Метод отчетности

использоватьsendBeaconСпособ объединения с XMLHttpRequest

Зачем использовать sendBeacon?

Статистический и диагностический код обычно отправляет данные, инициируя синхронный запрос XMLHttpRequest в обработчике событий выгрузки или перед выгрузкой (en-US). Синхронный XMLHttpRequest вынуждает пользовательский агент откладывать выгрузку документа и приводит к тому, что следующая навигация появляется позже. Следующая страница ничего не может сделать с этой плохой производительностью загрузки.
navigator.sendBeacon()Этот метод можно использовать для асинхронной передачи небольших объемов данных на веб-сервер по протоколу HTTP без задержки выгрузки страницы или влияния на скорость загрузки следующей навигации. Это решает все проблемы с отправкой данных аналитики: данные достоверны, передача асинхронна и не влияет на загрузку следующей страницы.

2. Когда сообщать

  1. Сначала кэшируйте отчетные данные, а затем используйте requestIdleCallback/setTimeout, чтобы отложить отчет после буферизации до определенной суммы.
  2. Отчет, когда вы собираетесь покинуть текущую страницу (обновить или закрыть) (onBeforeUnload) / отчет, когда страница не видна (onVisibilitychange, оценка состояния document.visibilityState/document.hidden)