Всем привет!


Меня зовут Паша Востриков, я делаю на JS/TS много разного в «Лаборатории Касперского»: фронт, облачные сервисы (Node.js), штуки для коробочной поставки (OnPrem), платформенные компоненты и библиотеки. И, конечно же, Open Source.


Сегодня хотел бы затронуть тему отладки веб-приложений на JavaScript.


Итак, как отлаживаться? Как-как? console.log(1)



1. Отладка Frontend


tl;dr: для отладки фронтового кода:


  • debugger;
  • browser breakpoint + conditional breakpoint;
  • logger with remote storage;
  • metrics.

Мы с вами фронтенд-разработчики. Нам нужно сделать yet another классную фичу. Но прежде нужно отладиться. Мало ли что пошло не так.


Конечно, запустили фронт-сборку в режиме отладки. Например, Webpack Dev Server или Vite, или вообще Next.js. Честно говоря, конкретный способ запуска проекта не так важен, давайте поговорим именно про подходы. Нам нужны Source Maps, минимальная транспиляция и уж точно выключенная минификация ;)


Так или иначе, dev server запущен. Мы поставили debugger или breakpoint в developer tools.
Здесь все понятно.


// debugger
const sum = (x, y) => {
  const result = x + y
  debugger
  return result
}

Breakpoint:


breakpoint


Conditional Breakpoint:


сonditional-breakpoint


Что еще нам может помочь? Логи и метрики.


Если этого нет, то начать стоит хотя бы с банального console.log с диагностической информацией. Но без отдельного логера нельзя будет получить данные с прода.


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


На ум приходят библиотеки Pino и Winston.


Конечно, это всего лишь для примера, и наверняка у вас есть своя любимая и проверенная библиотека для логирования. Важно, чтобы данные отправлялись в удаленное персистентное хранилище. Так можно будет разобраться с ситуацией на проде ahead of time.


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


Разговор про метрики и прочую телеметрию продолжим в главе 3, а сейчас мы подошли к Node.js.


2. Отладка Node.js


tl;dr: для отладки кода Node.js:


  • флаги запуска --inspect и --inspect-brk;
  • node-inspector: debugger и breakpoint;
  • heap snapshot;
  • логи, трейсы и метрики.

Далеко не факт, что вам требуется Node.js для сбора логов и метрик — возможно, у вас frontend-only или бэкенд на другом языке. Но узнать, что есть в мире ноды, все равно считаю полезным для JS-разработчиков и разработчиц.


Итак, к этому моменту мы уже обложились логами и даже поотлаживали фронт нашего проекта. Но что делать, если наш сервер на Node.js упорно отвечает 400, 500 или другой ошибкой?


Если на проекте используется Node.js, то, скорее всего, мы запускаемся локальной командой вроде:


node index.js

Добавили к запуску --inspect — и по умолчанию на порту 9229 будет висеть отладчик. Можно зацепиться за него из хрома или из редактора/IDE. Подробнее в Debugging Guide в документации Node.js.


node --inspect index.js

node-inspector


Случается так, что нужно отследить всю инициализацию кода, тогда подойдет --inspect-brk. Брейкпойнт будет установлен на первой строке вашего index.js:


node --inspect-brk index.js

Более подробный гайд: https://www.builder.io/blog/debug-nodejs.


Что еще можно потестировать с ходу на ноде?


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


    Например, выбрав вариант с запуском ноды с включенным сигналом:


    node --heapsnapshot-signal=SIGUSR2 index.js

  2. Using Heap Snapshot гайд от Node.js.


  3. Загрузку и простой event loop с помощью Node.js API perfomance.eventLoopUtilization.



Статья, которая хорошо объясняет про Event Loop Utilization.


Вообще стоит добавить слой метрик, чтобы снимать показатели с фронтового и Node.js кода.


Давайте поговорим об этом подробней.


3. Observability: логи, трейсы, метрики


tl;dr: для обеспечения Observability:


  • логи, трейсы, метрики;
  • OpenTelemetry как точка старта.

Локальная разработка и тем более удаленная отладка сложных проектов невозможна без такого важного подхода, как Observability.


Observability держится на трех китах: logs, traces и metrics.


Приведу доклад, который мы делали в пандемию. Он абсолютно актуален и сегодня: «Логи, трейсы, метрики: как не заблудиться в собственном приложении»


logs-talk


Если вкратце и по-простому, то:


  1. Логи — это наши с вами console.log, отправляемые в хранилище и доступные для пост-анализа. Логи позволяют отследить конкретный запрос или вызов функции.
  2. Трассировки — это отдельные записи логов, связанные сквозным идентификатором. Трассировки позволяют отследить сквозной сценарий, увидеть всю цепочку вызовов и понять контекст произошедшей ошибки.
  3. Метрики — это численные показатели работы приложения. Метрики позволяют понять, сколько раз пользователь зашел на страницу или как долго грузился определенный раздел.

В качестве метрик рекомендую Prometheus и соответственно prom-client, или сразу весь Open Telemetry, чтобы закрыть весь слой Observability: логи, трейсы и метрики.


После того, как настроены логи, метрики, и, скорее всего трассировки (здравствуй, Jaeger и Open Telemetry), следует задуматься о способе визуализации и чтения логов.


Мы в команде для себя так решили эту задачу.


  1. При локальной отладке — форматированные строки вида:
    [23:14:18][ServiceA] Operation #135 finished successfully
    [23:14:19][ServiceB] Subscriber: clear unsubscribe timeout #246
  2. При просмотре информации из хранилища — JSON-строки:
    { datetime: "13.09.2023 23:14:18.01", service: "ServiceA", message: "Operation #135 finished successfully", payload: {} }
    { datetime: "13.09.2023 23:14:19.55", service: "ServiceB", message: "Subscriber: clear unsubscribe timeout #246" }

Хочу добавить несколько слов про UI для работы с логами. Мы с командой пользовались в разное время несколькими системами (картинки из Интернета):



Лично мне больше нравится Kibana, хотя для большинства команд, в том числе со своей лицензией, думаю больше подойдет Grafana.


Также отдельно упомяну Sentry. Если вы ее не использовали, то рекомендую хотя бы попробовать. Весь комбайн в одном сервисе. Безусловно, где-то это хорошо, но кому-то не подойдет — например, в контексте хранения персональных данных клиентов.


Но вы просто попробуйте демо-песочницу Sentry. Уверен, что в следующем проекте попробуете, если сейчас только узнали ;)


4. Стенды для отладки


tl;dr: для получения удобного для работы стенда:


  • для простых кейсов: обычный запуск а ля npm run dev;
  • для более сложных интеграционных сценариев: Inrastructure as Code, контейнеризованное окружение;
  • docker compose — простейший способ для локального контейнеризованного окружения;
  • Storybook и другие песочницы для изолированной разработки фичи.

Мы уже поговорили об отладке фронтенда, отладке Node.js-кода и таком важном моменте, как Observability.


Но что делать, когда нужно воспроизвести поведение бага или новая фича требует преднастроенного окружения?


Безусловно, небольшой проект просто запустится на локальной машине — нет проблем.


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


С чего можно начать? Да хотя бы с локального docker-compose. Полностью изолированное контейнеризованное окружение. Это плюс. Главных минусов, на мой взгляд, два:


  1. не удобно менять код в контейнере
  2. вряд ли ваш прод в docker-compose ????

Второй проблеме можно посвятить отдельную статью. Давайте сейчас остановимся на том, что это tradeoff между простотой использования и подходом Infrastructure as Code.


Хочу поговорить о первом недостатке: неудобно отлаживать JS-код в контейнеризованной среде.


Можно смаппить volume, слинковав файлы с локальной файловой системы внутрь контейнера, но livereload, тем более для Webpack Dev Server и подобных тулов, тоже будет работать только отчасти.


В этом случае я рекомендую запускать все, не требующее отладки, контейнеризованно,
а то, что разрабатываете — локально. Connectivity обеспечивается через проброшенные порты до веб-сервера / очереди сообщений.


В итоге вы имеете понятную нативную среду для отладки и очень легкий в смысле конфигурации и подъема локальный стенд:


local-env


Следующий этап для бэкенда, не требующего отладки, и прочих «внешних запчастей», — иметь nightly-стенд, раздеплоенный где-то в инфраструктуре.
И локально опять же native привычное развертывание для нас JS-разработчиков.


Когда работаем с браузернм кодом, еще хочу подсветить три сценария отладки:


  1. Storybook для UI Kit.


    Must have, если почему-то еще не попробовали. Storybook, он же стенд, он же документация, он же staging, если не production (для UI Kit).


    ![storybook](https://habrastorage.org/webt/sp/x-/gw/spx-gw6h_dvq1vjytujtssb5she.png


  2. Storybook для функционала.


    Ваша фича с добавленными моками. Запуск zero-config, подтюнить может участник соседней команды, скажет вам просто спасибо.


  3. Если приложение большое, то обязательно нужна песочница, чтобы запускать изолированно микрофронты.



Для примера: если прод состоит из hostApp и нескольких микрофронтов, то для удобства и скорости локальной разработки должна быть возможность запустить только hostApp + конкретный микрофронт. При этом в hostApp могут быть выключены функции, ненужные для отладки микрофронта. Это и есть песочница.


5. Опыт нашей команды


Мы занимаемся разработкой UI в B2B-направлении — делаем Single Management Console, рабочее место для IT- и ИБ-администраторов.


storybook


Наша команда выросла из шести человек плоской структуры, до 34 человек в едином JS Stream, распределенном на фиче-тимы.


Большую часть из описанного мы уже используем. Но не все.
Я как рязанец могу с уверенностью сказать, что метро в Рязани и Туле тоже не сразу строилось.


К примеру, локальное контейнеризованное окружение у нас уже есть, и отладка в Storybook UI Kit отдельных продуктовых сценариев тоже уже есть.


А вот песочницу для микрофронтов только разрабатываем — развиваем наш тулсет UIF. Это набор библиотек и подходов для построения пользовательских интерфейсов в Kaspersky. On the top of it мы реализем интеграционную платформу для веб-сервисов и микрофронтендов. Рассказывали об общей концепции в статье «Микрофронты для всех. Как мы построили платформу UIF, и что под капотом».


Недавно в подкасте «Тяжелое утро с HolyJS #47» разговаривали с Лешей Золотых @zolotyh о построении UIF и вообще об архитектуре.


Сейчас мы двигаемся в развитии UIF дальше и вышли с первыми четырьмя библиотеками в Open Source: https://github.com/kasperskylab/uif.


Среди опубликованных пакетов:



Сейчас мы занимается доработкой и публикацией в Open Source библиотеки управления настройками (читай — формами на стероидах) и песочницей для микрофронтов.


6. Post Scriptum


В качестве концовки статьи хотелось бы привести три самых значительных инсайта, которые я накопил по отладке за свои 13 лет в нашей уютной веб-разработке. В них нет хардкора, связанного с async_hooks и flame charts, но тем не менее мне эти тезисы кажутся очень важными:


1. Тест на внимательность


Если вы смотрите на код и вам кажется, что он точно должен работать, но вот уже четверть часа вы без понятия, почему код не работает, — позовите кого-то из команды. Мы называем это «тест на внимательность». Глаз замыливается. Показали тиммейту, обсудили, вместе нашли решение и побежали довольные вперед. Не стясняйтесь показать, что вы чего-то не знаете или не умеете. Все чего-то не знают и не умеют :)


2. Feature Flags


Если вы хотите отладиться на интеграционном стенде или просто не успеваете сделать фичу, чтобы отдать ее в уходящий релизный поезд, заложите фиче-флаг. Это может быть if + env.variable, конфиг из смонтированного файла или запрос к удаленному сервису вроде Firebase.


С помощью фиче-флагов вы сможете непрерывно поставлять код в main, дробя большие фичи по частям.


А по теме статьи — так еще и проще отлаживаться. Вошли в dev-режим, активировали фиче-флаг и делаете, что нужно. А остальные просто используют билд с ветки main, и вы им не мешаете.


3. Лучший способ отладки — пристальный взгляд


Мы все — люди-человеки.


Хорошие инструменты и практики ограждают нас от ошибок и подталкивают к правильным решениям.


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


Можно писать сотни тысяч записей логов в минуту в самую продвинутую систему сборки логов — так, что потом ничего нельзя разобрать.


Важно желание и умение нас, разработчиков, проникать в глубь бизнес-логики.


Если у вас есть желание сделать хорошо — вы это сделаете при любых раскладах. И в бирюзовом стартапе, и в аджайло-скрамбане, и в энтерпрайзо-вотерфоле на заводе.


Мы пишем читабельный код для людей — для нас самих. А способы и системы отладки мы изучаем и делаем для нас будущих, когда у нас что-то пошло не так. Поэтому подумайте о способах отладки ваших JS-приложений уже сегодня, пока «все так».


Stay tuned, придем с обновлениями о веб-тулсете Kaspersky UIF, а пока расскажите, пожалуйста, в комментариях о ваших подходах и «натоптанных тропах» при отладке JS-приложений.


Ну а если вам интересно отлаживать приложения по нашим практикам, а также строить и опенсорсить UIF вместе с нами, то приходите к нам во Frontend-команду ;)

Комментарии (10)


  1. selkwind
    29.09.2023 17:45

    Интересно, на что Вы бы посоветовали заменить dev tools, если хочется более комфортного UI, типа той же толстой Visual Studio


    1. Alexufo
      29.09.2023 17:45

      эээ... девтул это самое продвинутое, что есть для отладки js


      1. werpo
        29.09.2023 17:45

        И как там свои шорткаты назначить? Как свои шрифты дли отображения выбрать ?


  1. kovserg
    29.09.2023 17:45
    -2

    Да js отлаживать одно удовольствие. Особенно пристальным взглядом /s
    image


    1. Dolios
      29.09.2023 17:45
      +1

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

      Никто в здравом уме в 99% случаев не занимается отладкой минифицированного js бандла.


      1. kovserg
        29.09.2023 17:45
        -1

        А если это вредоносный js или еще лучше wasm?


        1. Dolios
          29.09.2023 17:45

          А если это вредоносный бинарь? Речь про отладку в топике.


        1. Alexufo
          29.09.2023 17:45

          С васмом ещё проще, его без js не бывает да и он декомпилируется для анализа в С подобный


          1. kovserg
            29.09.2023 17:45
            -1

            "В теории нет разницы между теорией и практикой. А на практике есть"


            ps: минимизированный js тоже c подобный. и без навигации и возможности переименовывать метки и других инструментов, вы это болото не пересечете. И почему вы считаете что wasm нельзя обфусцировать.


            1. Alexufo
              29.09.2023 17:45

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