Подробное объяснение системы внешнего мониторинга и технологии внедрения

монитор

1. Обзор системы внешнего мониторинга

Полноценная система внешнего мониторинга включает в себя такие процессы, как сбор, хранение журналов, сегментация и расчет журналов, анализ данных и сигнализация.Для фронтенд-разработчика это означает, что работа больше не ограничивается разработкой интерфейсных конечный бизнес.Требуются возможности эксплуатации и обслуживания службы Nginx, возможности анализа в режиме реального времени/офлайн, возможности разработки и эксплуатации приложений Node и т. д.

Текущие основные системы внешнего мониторинга в основном включаютАли ОРУЖИЕ,FUNdebug,sentryи т.д., но независимо от того, какой системе система в основном соответствует упомянутой выше системе, далее будет рассмотрена реализация функций клиентской части системы.

2. Сборная часть

2.1 Стабильность

2.1.1 Ошибки сценария

Существует два основных типа ошибок сценария: синтаксические ошибки и ошибки времени выполнения. Основными методами наблюдения являются:

try-catch
window.onerror
window.addEventListener('error',()=>{})
window.addEventListener('unhandledrejection'()=>{})

Опыт использования: onerror в основном используется для перехвата непредвиденных ошибок, а try-catch можно использовать для отслеживания конкретных ошибок в предсказуемых ситуациях Комбинация этих двух форм более эффективна. Используйте window.onerror для перехвата ошибок времени выполнения JS, используйте window.addEventListener('unhandledrejection') для перехвата необработанных ошибок отклонения обещаний и window.addEventListener('error') для перехвата ошибок загрузки ресурсов. Однако он также может фиксировать ошибки времени выполнения js.Чтобы избежать повторных отчетов об ошибках времени выполнения js, сбор данных выполняется только тогда, когда событие.

Кроме того, когда в коде try-catch возникает синтаксическая ошибка или асинхронная ошибка, ее невозможно нормально отловить.

Пример try-catch (синтаксическая ошибка)

try {
    function empty()   // <-  throw error 语法错误
} catch(e){
    console.log('语法错误信息 ↙');
    console.log(e);
}

Не удалось зафиксировать ошибки

Пример try-catch (асинхронная ошибка)

try {
    setTimeout(function() {
        test // <- throw error 异步错误
    },0)
} catch(e){
    console.log('异步错误信息 ↙');
    console.log(e);
}

Не могу поймать ошибки

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

2.1.1.1 Устранение ошибки скрипта междоменного ресурса

Есть еще один момент, на который стоит обратить особое внимание, обратите внимание на отчет об ошибке скрипта междоменного ресурса

developer.mozilla.org...

Когда синтаксическая ошибка возникает в скрипте, загруженном из другого домена, во избежание утечки информации (см. ошибку 363897) сведения о синтаксической ошибке не сообщаются, а просто сообщается «Ошибка скрипта». В некоторых браузерах по

пример:sandbox.runjs.cn/show/...Откройте страницу, чтобы открыть консоль. Страница загружает js-скрипты двух разных доменов соответственно. window.onerror, настроенный с перекрестным происхождением, может сообщать подробные сообщения об ошибках. Если перекрестный источник не настроен, может быть сообщено только об «ошибке сценария», а сообщение об ошибке отсутствует.

Конкретные шаги по устранению влияния ошибки сценария из политики того же источника следующие:

1. Добавьте тег скрипта на страницуcrossoriginАтрибуты.

<script src="http://127.0.0.1:8077/main.js" crossorigin></script>

Увеличиватьcrossoriginатрибут, браузер автоматически добавитOriginПоля, инициируйте запрос обмена ресурсами для перекрестных источников.OriginИсточник запроса указывается серверу, и сервер будет судить, является ли ответ нормальным в соответствии с источником.

2. Добавьте в заголовок ответаAccess-Control-Allow-Originдля поддержки совместного использования ресурсов между доменами.

Access-Control-Allow-Origin: *Указывает, что междоменный запрос пройден, и к ресурсу может получить доступ любой межсайтовый сайт. И когда ресурс разрешен только изhttp://127.0.0.1:8066Когда межсайтовый запрос недоступен с других сайтов, он сможет вернуть:

Access-Control-Allow-Origin:http://127.0.0.1:8066

3. Укажите доменное имяAccess-Control-Allow-Originдолжен быть включен в заголовок ответаVary:Origin。

Роль поля Vary состоит в том, чтобы предоставить серверу кэширования основу для правил кэширования и фильтрации кэша. при увеличенииVary:OriginПосле заголовка ответа сервер кэширования кэширует различные версии в соответствии с содержимым поля Origin и решает, следует ли использовать кэшированный ответ в соответствии с Origin в заголовке запроса при запросе ответа.

Например, если не добавить Вари, будет проблема с некорректным попаданием в кеш.

На приведенном выше рисунке ответ на первый запрос (происхождение: 127.0.0.1:8066) кэшируется браузером. по ошибке получено Access-Control-Allow-Origin:http://127.0.0.1:8066приведет к сбою загрузки ресурса.

Поэтому, когда Access-Control-Allow-Origin не возвращается как *, необходимо добавить заголовок возврата Vary, чтобы избежать проблем с разрешениями, вызванных кэшированием.

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

путем переписыванияXMLHttpRequestа такжеfetchродной метод достижения

Инкапсулировать xmlHttpRequest

if(!window.XMLHttpRequest) return;
var xmlhttp = window.XMLHttpRequest;
var _oldSend = xmlhttp.prototype.send;
var _handleEvent = function (event) {
    if (event && event.currentTarget && event.currentTarget.status !== 200) {
          // 自定义错误上报 }
}
xmlhttp.prototype.send = function () {
    if (this['addEventListener']) {
        this['addEventListener']('error', _handleEvent);
        this['addEventListener']('load', _handleEvent);
        this['addEventListener']('abort', _handleEvent);
    } else {
        var _oldStateChange = this['onreadystatechange'];
        this['onreadystatechange'] = function (event) {
            if (this.readyState === 4) {
                _handleEvent(event);
            }
            _oldStateChange && _oldStateChange.apply(this, arguments);
        };
    }
    return _oldSend.apply(this, arguments);
}

инкапсулировать выборку

if(!window.fetch) return;
    let _oldFetch = window.fetch;
    window.fetch = function () {
        return _oldFetch.apply(this, arguments)
        .then(res => {
            if (!res.ok) { // True if status is HTTP 2xx
                // 上报错误
            }
            return res;
        })
        .catch(error => {
            // 上报错误
            throw error;  
        })
}

2.1.3 Исключение ресурса

Исключение ресурсов: изображение на странице, CSS, JS и другие активы загружают ошибку в случае сбой в поле «Захват событий», где вы можете получить ресурсы, не удалось загрузить обратный вызов:

window.addEventListener( 'error ', function(e){
//排除JSError
    if( ! (e instanceof ErrorEvent)){
//资源路径
        e.target.src || e.target.href
        //资源类型
        e.target.tagName
    }
}, true)

Советы: Третий параметр addEventListener имеет значение true, чтобы выполняться во время процесса захвата, и наоборот, чтобы выполнять обработчик во время процесса всплытия.

2.1.4 Белый экран

Белый экран: рендеринг не завершен из-за какой-либо проблемы в процессе загрузки страницы, и пустая страница отображается ненормально.

2.1.5Crash

Сбой: страница аварийно завершает работу из-за переполнения памяти, бесконечного цикла и т. д.

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

2.2 Беглость

2.2.1 Скорость загрузки страницы

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

Ниже приведены четыре критерия оценки индикаторов, относящиеся к беглости/беглости операций в процессе использования пользователем.

2.2.1 Скорость отклика

Скорость отклика: время от операции пользователя до ответа страницы, обычно менее 100 мс.

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

var observer = new PerformanceObserver(function(list){list.getEntries().forEach(entry =>{
//name: entry.name
//整体耗时: entry.duration
//事件处理函数耗时: entry.processingEnd - entry.processingStart)
});
observer.observe({type: "event", buffered: true});

2.2.3 Беглость анимации

Плавность анимации: стабильны ли частота кадров и количество кадров любой анимации на странице.

Слушайте выполнение каждого requestAnimationFrame во время выполнения анимации, вычисляйте:

1. Частота кадров: номер кадра анимации/время работы анимации.

2. Частота кадров: (количество кадров, которые должны воспроизводиться с частотой 60 кадров в секунду — фактическое количество кадров) / количество кадров, которые должны выполняться с частотой 60 кадров в секунду.

//以60帧每秒的标准,每一帧之间的间隔msIn0neFrame = 1000/60;
//期望帧数
expectedFrames = Math.floor(e.elapsedTime*1000 / msInoneFrame ) ;
//掉帧率
error_rate = (expectedFrames -运行帧数)/expectedFrames

2.2.4 Свободная прокрутка

Беглость прокрутки: время задержки от скольжения пальца пользователя до фактического начала прокрутки страницы, обычно менее 100 мс.

Chrome упомянул индикаторы SSL и SUL2 в «Speed ​​​​Launch Metrics Survey», которые используются для измерения задержки от первого и не первого касания пальцем до начала прокрутки экрана.

//目前只有集成了UC内核的阿里系APP(如手淘、猫客)支持。
var observer = new Performanceobserver ( ( list)=>{
list .getEntries ( ) .forEach ( entry =>{
if( entry -duration > 100 ) {
// send
}) ;
}) ;
observer.observe ( {entryTypes: [ "touchscrolllatency" ] } ) ;
//entry对象包含核心字段:
{
" name " : "ssl" ,// "ssl" or "sul"
" entryType " : "touchscrolllatency" ,
"startTime" : 1000.10000000000000, //用户开始拖动页面的时刻距离performance.timing
"duration" : 100.10000000000000 , //用户开始拖动页面到页面开始滚动的时间差值,单位ms...
}

2.2.5 Катон

Катон: В течение всего жизненного цикла страницы основной поток продолжает выполнять определенную задачу более 50 мс

Механизм очереди событий браузера определяет, что для получения ответа менее 100 миллисекунд приложение должно возвращать управление основному потоку каждые 50 миллисекунд:

PerformanceLongTask предоставляет возможность обнаружения зависания, которое может обнаруживать исключение, когда основной поток ядра браузера зависает более чем на 50 мс:

var observer = new Performanceobserver(function(list) {
list.getEntries().forEach(entry => {
//开时时间: entry.startTime
//持续时间: entry.duration
}
});
observer.observe({type: "longtask", buffered: true});

2.3 Перехват ошибок скриптов в приложениях Vue

Некоторое время назад, когда я готовил собственный проект front-end мониторинга sdk, мне довелось столкнуться с кем-то, кто задал вопрос по мышлению, ответ на этот вопрос на самом деле очень прост, потому что Vue глобально фиксирует ошибки Vue.config. errorHandler = function(error, vm , info){}, ошибка в этом методе отличается от формата, выплевываемого window.onerror, ее нельзя обработать напрямую через пакет source-map, отношение сопоставления нельзя сопоставить , есть много проблем, нужно использоватьTraceKitВ рамках этого пакета можно использовать для конвертации.

Sentry также в настоящее время предоставляет SDK мониторинга для проектов Vue, который использует метод Vue.config.errorHandler, но используется преобразование ошибокraven-js, предназначен для сбора ошибок браузера, был интегрирован TraceKit, ниже приведена ключевая часть исходного кода в sentry

function vuePlugin(Raven, Vue) {
  Vue = Vue || window.Vue;

  // quit if Vue isn't on the page
  if (!Vue || !Vue.config) return;
  
  var _oldOnError = Vue.config.errorHandler;
  Vue.config.errorHandler = function VueErrorHandler(error, vm, info) {
      const metaData = {};
    if (Object.prototype.toString.call(vm) === '[object Object]') {
      metaData.componentName = formatComponentName(vm);
      metaData.propsData = vm.$options.propsData;
    }

    if (typeof info !== 'undefined') {
      metaData.lifecycleHook = info;
    }
    // ...

    // 上报
    Raven.captureException(error, {
      extra: metaData
    });
    if (typeof _oldOnError === 'function') {
      // 为什么这样做?
      _oldOnError.call(this, error, vm, info);
    }
  };
}
module.exports = vuePlugin;

Ответ на вопрос «Зачем это делать?» выше:

По сути, здесь используется идея АОП (аспектно-ориентированного программирования).Его основная функция заключается в извлечении некоторых функций, не связанных с основным модулем бизнес-логики.По сути, это добавление слоя к исходной функции, независимо от внутренняя реализация исходной функции.

К тому же, если не писать так, то ошибку нужно кидать отдельно, потому что при настройке разработчиком Vue.config.errorHandler сообщение об ошибке не будет выведено на консоль, проще всего через консоль .error() to Информация печатается на консоли.

На этой идее основаны 8 методов перезаписи массивов в Vue 2.0.zhuanlan.zhihu.com/p/166676919

2.4 Воспроизведение поведения пользователя

Во-первых, определить, какое поведение необходимо собирать? С точки зрения пользователя мы рассматриваем возможные процессы в цикле просмотра одностраничного приложения: вход на домашнюю страницу приложения — загрузка содержимого страницы — просмотр содержимого страницы — взаимодействие с пользователем (взаимодействие с мышью/клавиатурой и т. д.) — перейти на новую страницу...

Чтобы объединить действия пользователя в полную цепочку действий, чтобы обеспечить контекст для ошибок js, нам нужно знать, когда, где и что произошло. Таким образом, все действия на странице (включая, но не ограничиваясь взаимодействием с пользователем) в описанном выше процессе просмотра пользователем можно грубо обобщить в следующих категориях: запросы API, события мыши, события клавиатуры, переходы маршрутизации, ошибки и т. д.

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

Мы можем полностью отслеживать различные события взаимодействия с пользователем в документе верхнего уровня, такие как щелчок, нажатие клавиши, движение мыши, прокрутка и т. д. Однако у этого метода также есть очевидный недостаток: допустим, пользователь прослушивает событие клика на дом и устанавливает event.stopPropagation(), событие клика в этом случае не может отслеживаться документом, и часто такое поведение особенно важно как для диагностики ошибок, так и для бизнес-анализа. Обходной путь — спрятать хук в addEventListener.

var bhEventHandler = function(){
  //记录用户行为
}
document.addEventListener('click', bhEventHandler, false);
var types = ['EventTarget', 'Node'];
for (var i = 0; i < types.length; i++) {
    var type = types[i];
    var proto = window[type] && window[type].prototype;
    reWrite(proto.addEventListener, function (orig) {
        //重写addEventListener,记录用户行为
    });
}

Переходы по маршруту, несомненно, вызовут изменения в истории браузера.Каждый раз, когда активная запись в истории изменяется, срабатывает событие popstate окна, но вызов history.pushState() или history.replaceState() не вызовет событие popstate. Таким образом, мониторинг маршрутных переходов можно разделить на два аспекта: с одной стороны, хуки встроены в window.onpopstate, а с другой стороны, хуки встроены в history.pushState и history.replaceState.

var origPopstate = window.onpopstate;
window.onpopstate = function () {
    //记录路由行为    
    if (origPopstate) {
        return origPopstate.apply(this, args);
    }
};
var dosomething = function (orig) {
    return function (...args) {
        //记录路由行为    
        return orig.apply(this, args);
    };
};
reWrite(window.history.pushState, dosomething);
reWrite(window.history.replaceState, dosomething);

На самом деле есть еще один способ добиться воспроизведения пользовательского поведения, то есть браузер предоставляетMutationObserverФункция, используйте ее для записи изменений дерева dom с течением времени, сохраняйте их в массиве через diff и сообщайте об этом, а интерфейс отображения ошибок реализует воспроизведение поведения пользователя путем разбора дерева dom, как просмотр видео. Учебная станция Vue реализует эффект клика на видео и непосредственно редактирования демо.На самом деле это принцип.То, что мы видим,это не видео,а библиотека для рисования сообщества dom tree.rrweb, заключается в достижении этой функции.

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

3. Информационная отчетность

После того, как информация собрана и собрана, следующим шагом является сообщение данных.В настоящее время существуют в основном следующие три способа представления информации.

Потерянная точка: когда браузер щелкает, чтобы перейти, запрос отчета о щелчке перед переходом выполнит трехстороннее рукопожатие.Если сеть медленная, сервер работает медленно или запрос отчета все еще находится в стадии обработки, на этом время, если страница удалена Теперь браузер автоматически прервет текущий запрос. Таким образом, HTTP-запрос не устанавливается, в результате чего отчет фактически не отправляется.

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

window.addEventListener('unload', logData, false);
function logData() {
    var client = new XMLHttpRequest();
    client.open("POST", "/log", false); // 第三个参数表明是同步的 xhr
    client.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
    client.send(analyticsData);
}

В этом смысл существования метода sendBeacon(). Использование метода sendBeacon () заставит пользовательский агент асинхронно отправлять данные на сервер, когда появляется возможность, но не удаляет задержку и не влияет на производительность загрузки страницы при следующей навигации. Это решает все проблемы с отправкой данных аналитики: данные достоверны, передача асинхронна и не влияет на загрузку следующей страницы. Кроме того, код на самом деле намного проще, чем многие другие методы!

В следующем примере показан теоретический шаблон статистического кода — отправка данных на сервер с помощью метода sendBeacon().

window.addEventListener('unload', logData, false);

function logData() {
    navigator.sendBeacon("/log", analyticsData);
}

Поэтому здесь рекомендуется сначала проверить, поддерживает ли браузер Navigator.sendBeacon(), а если нет, использовать другие методы создания отчетов.

4. Анализ журнала, обработка, хранение, анализ, реализация функции сигнализации

1. Чем заняться

  • Прием и обработка журналов; создание серверных приложений и предоставление интерфейсов отчетов журналов для сбора SDK.

  • Выпуск данных: журналы, полученные серверным приложением, обрабатываются в потоковые данные, которые могут быть обработаны потоковыми вычислениями в реальном времени, такими как DataHub, SLS, Kafka и т. д.

  • Обработка журналов: Платформа потоковых вычислений выполняет обработку потоковых данных в режиме реального времени, которая может быть основана на Flink, Spark, Storm и т. д.

  • 1. Анализ в реальном времени на основе выборки: анализ в реальном времени на основе базы данных OLAP, такой как Alibaba Cloud Hologres, Hive, Kylin и т. д.

  • 2 Вычисление сводки в реальном времени: вычисляйте сводку необработанных журналов в режиме реального времени по индикаторам (таким как частота ошибок) и сохраняйте их в базах данных OLTP, таких как Alibaba Cloud RDS, GaussDB, TBase и т. д.

  • Мониторинг и оповещение: создание клиентских приложений для мониторинга и оповещения

2. Требуемые системные службы:

Сервер приложений + DataHub + Flink + Hologres (RDS)

4.1 SourceMap находит место ошибки

Большая часть вышеперечисленного является работой бэкэнд-студентов, но перед бэкэнд-частью также стоит более важная задача, заключающаяся в том, чтобы найти реальный логический код в соответствии с информацией об ошибках исходного кода + онлайн-кода Js, чтобы быстро найти аномалию

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

1. Утечки исходного кода

2. Размер файла сильно увеличен

Но это затрудняет правильное определение местоположения кода ошибки при возникновении ошибки.Теперь SourceMap обычно используется для обнаружения реального кода. SourceMap — это информационный файл, в котором хранится информация об исходном файле и об отношении сопоставления между исходным файлом и обработанным файлом. При обнаружении ошибки в сжатом коде конкретная информация об ошибке исходного файла может быть получена после обработки с помощью количества строк и столбцов информации об ошибке и соответствующего файла SourceMap.

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

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

Быстро найти схему отчетов об ошибках скриптов на основе SourceMap

Ссылка на процесс для поиска реального кода с использованием файла sourceMap:

будет использованоsource-mapа такжеstacktraceyВ этих двух библиотеках source-map используется для анализа файла sourcemap и восстановления исходного кода, а stacktracey используется для анализа неправильной информации о стеке.

Пример стека, в котором мы собрали информацию об ошибках, выглядит следующим образом:

Error: test at App.render (webpack:///./src/app.tsx?:47:13) at finishClassComponent 
(webpack:///./node_modules/react-dom/cjs/react-dom.development.js?:18470:31) at updateClassComponent
(webpack:///./node_modules/react-dom/cjs/react-dom.development.js?:18423:24) at beginWork$1 
...

Возьмите первую строку стека, чтобы проиллюстрировать

  1. Webpack:///./src/app.tsx в скобках представляет собой имя исходного файла. (Название файла здесь довольно странное, поэтому я пока не буду в него вникать.
  2. :47:13 в скобках означает, что ошибка произошла в строке 47 и столбце 13.

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

const tracey = new Stacktracey(errorStack); // 解析错误信息
 for (const frame of tracy) {
  // 这里的frame就是stack中的一行所解析出来的内容 
  console.log(frame.line, frame.column);
   // 错误发生的行和列
   }

Мы уже знаем строку и столбец, где произошла ошибка, поэтому, получив исходный код через source-map, мы можем точно знать, где произошла ошибка.

Ниже приведен небольшой пример, объединяющий исходную карту и трассировку стека.

Следует отметить, что информация о строках и столбцах, анализируемая stacktracey, представляет собой позицию в сжатом коде.Чтобы получить информацию о строках и столбцах, соответствующую исходному коду, необходимо вызвать Consumer.originalPositionFor для ее получения. Для получения дополнительных API исходных карт вам все равно придется просмотреть документацию.

const sourceMap = require('source-map'); 
const SourceMapConsumer = sourceMap.SourceMapConsumer; 
const Stacktracey = require('stacktracey'); 
const errorStack = '...'; // 错误信息 
const sourceMapFileContent = '...'; // sourcemap文件内容 
const tracey = new Stacktracey(errorStack); // 解析错误信息 
const sourceMapContent = JSON.parse(sourceMapFileContent); 
const consumer = await new SourceMapConsumer(sourceMapContent); 
for (const frame of tracey) { // 这里的frame就是stack中的一行所解析出来的内容 
// originalPosition不仅仅是行列信息,还有错误发生的文件originalPosition.source 
 const originalPosition = consumer.originalPositionFor({ line: frame.line, column: frame.column, }); // 错误所对应的源码 
 const sourceContent = consumer.sourceContentFor(originalPosition.source); 
 console.log(sourceContent); 
 }

Вот демонстрационный проект, который использует библиотеку исходных карт для поиска проблем.GitHub.com/JoeyOver/Но…

Документ запуска проекта, написанный автором, не является полным.Я добавлю его сюда.Перед запуском вы должны войти в основной каталог и каталог примера, чтобы выполнить npm install для установки соответствующих зависимостей, а затем выполнить команду запуска.

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

Справочная статья:

developer.aliyun.com/article/714…

GitHub.com/Джоуи никогда/нет...

GitHub.com/Чен Шанниовер/TR ах…

zhuanlan.zhihu.com/p/136840107

zhuanlan.zhihu.com/p/64033141

zhuanlan.zhihu.com/p/136840107

Ууху. Call.com/question/28…

у-у-у. Краткое описание.com/afraid/80 не 559 не голоден 0…

By School: Внедрение системы мониторинга — Курс стажировки по интерфейсу Alibaba