Освоение Dart & Flutter DevTools — Часть 5: Представление ведения журнала

автор Ашита Прасад (LinkedIn, Twitter), fluttergems.dev

Это пятая статья из серии подробных статей Mastering Dart & Flutter DevTools. В этой статье вы узнаете о ведении журналов в приложениях Flutter и о том, как эффективно использовать представление ведения журнала DevTools. Если вы хотите ознакомиться с любой другой статьей из этой серии, просто нажмите на ссылку, представленную ниже:

Установка и настройка DevTools является обязательным условием для этой части. Если вы пропустили, подробности установки и настройки подробно описаны здесь.

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

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

Различные методы регистрации

print() & debugPrint()

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

Например:

void main() {
  var message = "Hello logger!";
  print(message);
}

Отображает приведенный ниже вывод в терминале.

Hello logger!

Если это message слишком велико, некоторые строки журнала могут быть отброшены или усечены из-за ограничения печати ОС (например, Android).

Чтобы обойти это, мы можем использовать функцию debugPrint() из библиотеки foundation Flutter, которую можно изменить, назначив ее пользовательскому DebugPrintCallback, который может регулировать скорость отправки сообщений, чтобы избежать потери данных в Android. Вы также можете предоставить аргумент wrapWidth, который переносит message по словам до заданной ширины, чтобы избежать усечения вывода.

import 'package:flutter/foundation.dart' show debugPrintThrottled;

// if no wrapWidth is provided then automatically set it to 70 characters
final debugPrint = (String? message, {int? wrapWidth}) {
  debugPrintThrottled(message, wrapWidth: wrapWidth ?? 70);
};

void main() {
  var message = "Hello logger!";
  debugPrint(message);

  var longMessage = "A very long line that is automatically wrapped by the function so that it does not get truncated or dropped.";
  debugPrint(longMessage);
}

Отображает приведенный ниже вывод в терминале.

Hello logger!
A very long line that is automatically wrapped by the function so that it does
not get truncated or dropped.

Хотя обе вышеуказанные функции можно использовать для ведения журнала, выходные данные функций print() и debugPrint() можно легко просмотреть, поскольку они отображаются в терминале, даже если приложение находится в режиме выпуска. На самом деле вы можете запустить команду flutter logs или adb logcat | grep flutter в терминале и просмотреть журналы, созданные с использованием этих функций, во время работы приложения. Это может привести к проблемам с безопасностью, если какая-либо конфиденциальная информация или любая информация, связанная с процессом авторизации, регистрируется в вашем приложении из-за использования этих функций.

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

void main() {
    if (kReleaseMode) {
      debugPrint = (String? message, {int? wrapWidth}) {};
    }
}

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

бревно()

Помимо проблем с безопасностью, функции print() и debugPrint() также не очень полезны для анализа журнала, поскольку им не хватает функций для добавления детализации, выполнения настройки и отображения дополнительной информации во время ведения журнала.

Здесь нам на помощь приходит функция log(), доступная в библиотеке dart:developer. Помимо отображения message, представляющего сообщение журнала, в этой функции есть несколько других полезных параметров, которые могут помочь нам добавить детализацию и данные в наши журналы, например:

  • name (String) — короткая строка, которая может быстро помочь вам найти источник ошибки. В представлении регистрации оно становится значением поля Kind и может напрямую использоваться для фильтрации журналов. Если это значение не указано, значение столбца по умолчанию — log.
  • error (Object?) — объект ошибки обычно представляет собой данные приложения в той точке рабочего процесса (события), которую мы можем захотеть исследовать. Эти данные передаются и могут быть просмотрены в представлении регистрации.
  • level (int) — обозначает уровень серьезности (значение от 0 до 2000). Вы можете использовать класс package:logging Level, чтобы получить обзор возможных значений. Если значение равно или превышает 1000 (Level.SEVERE.value), поле журнала Kind в представлении регистрации выделяется красным, чтобы журнал выделялся.

Журналы, сгенерированные с помощью log(), не отображаются на терминале, что делает его безопасным. Мы можем просматривать эти журналы с помощью представления журнала DevTools, как описано в следующем разделе.

Представление журнала

Logging View в пакете DevTools можно использовать для просмотра следующих событий:

  • События среды выполнения Dart, такие как сборка мусора.
  • События фреймворка Flutter, такие как создание фрейма.
  • Стандартные выходные события (stdout, stderr) из приложения, созданного с использованием таких функций, как print().
  • Пользовательские события регистрации, созданные с помощью функции log() в приложении.

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

Шаг 1: Получение исходного кода

Создайте локальную копию ниже репозитория 👇



Откройте репозиторий в GitHub, как показано ниже.

Нажмите зеленую кнопку <> Code, затем перейдите на вкладку Local и нажмите Download ZIP, чтобы загрузить исходный код для этого упражнения.

Извлеките файл с помощью любого инструмента для распаковки.

Теперь у нас есть локальная копия исходника в папке logging_view_demo-master.

Шаг 2: Открытие проекта в VS Code

Запустите VS Code, откройте папку проекта в VS Code и перейдите в файл pubspec.yaml.

Нажмите на Get Packages, чтобы получить все пакеты, необходимые для проекта.

Шаг 3. Запуск представления ведения журнала в VS Code

Прежде чем мы запустим Logging View, мы должны запустить приложение.

Нажмите No Device в строке состояния и выберите целевое устройство. Если устройство по умолчанию уже выбрано, вы можете щелкнуть по нему и изменить целевое устройство.

Мы продолжим и выберем эмулятор Android в качестве целевого устройства.

Теперь нажмите Run, чтобы запустить приложение (основная функция), как показано на изображении ниже.

Logging View можно запустить, как показано ниже, используя следующие инструкции:

Click Dart DevTools in status bar > 
Click Open DevTools in Web Browser > Click Logging tab

При запуске приложения вы уже можете видеть некоторые журналы в представлении ведения журнала. Это события фреймворка Flutter.

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

Пример №1: Строка

Нажмите кнопку Short String на экране приложения. Это действие вызовет ряд событий (событие flutter.frame Flutter frame, gc сборка мусора), включая событие журнала, как показано на изображении ниже.

Когда кнопка нажата, она запускает следующее событие журнала:

log(
  "Log Event: Short String",
  name: "buttonLog",
  error: kShortString,
);

Событие можно увидеть в представлении регистрации, как показано ниже. buttonLog передается как значение параметра name, становится значением поля Kind в строке событий журнала. Нажмите на журнал, чтобы просмотреть его детали. Сообщение журнала Log Event: Short String является первой строкой сведений, за которым следуют данные приложения kShortString, строка Hello logger!, в следующей строке.

Фильтрация необходимых журналов

Такие события, как события кадра Flutter (flutter.frame) и события сборки мусора (gc), захваченные в представлении ведения журнала, которые бесполезны для нашего исследования, можно отфильтровать с помощью параметра фильтра, как показано ниже.

Просто нажмите кнопку Filter и введите -k:flutter.frame,gc, чтобы скрыть все журналы вида flutter.frame и gc. Если вы хотите просмотреть журналы только определенного типа, который вы указали (например, события buttonLog), просто введите k:buttonLog в текстовое поле фильтра, и он отобразит журналы только этого типа.

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

Пример № 2: длинная строка (серьезная)

Нажатие кнопки Long String (Severe Event) вызывает следующее событие журнала:

log(
  "Log Event: Long String",
  name: "buttonLog",
  error: kString,
  level: 1000,
);

В этом примере мы установили значение параметра level как 1000. Поскольку это значение равно >=1000 (эквивалентно Level.SEVERE.value в package:logging), поле журнала Kind buttonLog выделяется красным цветом фона в представлении регистрации, как показано ниже. Кроме того, можно заметить, что по сравнению с обычными журналами терминала, которые усекаются после определенной длины, в этом случае все данные (длинная строка) регистрируются для дальнейшей проверки.

Пример №3: Большой список

Теперь давайте продолжим и нажмем кнопку Large List, чтобы вызвать следующее событие журнала:

log(
  "Log Event: Large List",
  name: "buttonLog",
  error: kList,
);

В данном случае kList — это большой список, содержащий более 350 элементов. Опять же, весь список отображается в журнале, который был бы усечен, если бы отображался на терминале.

Пример № 4: Карта

Как и в случае со списком, давайте теперь продолжим и зарегистрируем карту, используя следующий вызов:

log(
  "Log Event: Map",
  name: "buttonLog",
  error: kMap,
);

Соответствующий результат журнала показан на изображении ниже:

Пример № 5: Пользовательский объект класса

Одно из основных преимуществ использования функции log() и представления журнала заключается в том, что их можно использовать для анализа сложных данных (доступных как объект класса Dart), которые могут вызывать ошибку времени выполнения.

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

class MenuModel {
  Menu? menu;

  MenuModel({this.menu});

  MenuModel.fromJson(Map<String, dynamic> json) {
    menu = json['menu'] != null ? Menu.fromJson(json['menu']) : null;
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = {};
    if (menu != null) {
      data['menu'] = menu!.toJson();
    }
    return data;
  }
}

Теперь мы можем создать объект этого класса с некоторыми исходными данными, указанными в kModelData.

final model = MenuModel.fromJson(kModelData);

При нажатии кнопки Dart Object (Custom Class) запускается следующее событие журнала. Поскольку данные хранятся с использованием пользовательской модели (Dart Object), мы должны использовать функцию jsonEncode() библиотеки dart:convert для кодирования объекта в строку JSON, а затем передать ее в качестве аргумента ошибки.

import 'dart:convert';

log(
  "Log Event: Dart Object (Custom Class)",
  name: "buttonLog",
  error: jsonEncode(model),
);

Используя метод toJson() класса MenuModel, функция jsonEncode() преобразует объект в удобочитаемую строку JSON, которая отображается для дальнейшего анализа в представлении сведений для записи журнала, как показано ниже.

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

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

Мы хотели бы узнать о вашем опыте ведения журнала во Flutter и представлении ведения журнала DevTools. Если вы столкнулись с какими-либо проблемами при выполнении этого упражнения или при запуске инструмента для вашего проекта, пожалуйста, не стесняйтесь упоминать об этом в комментариях, и мы обязательно рассмотрим это. Кроме того, если у вас есть какие-либо другие предложения, добавьте их в комментариях ниже.

В остальных статьях этой серии мы обсудили другие инструменты, доступные в наборе DevTools, которые могут помочь вам создавать высокопроизводительные приложения Flutter. Не забудьте перейти по ссылкам ниже, чтобы перейти к инструменту, который вы хотите изучить дальше:

Исходный код(ы), используемые в этой статье:



Выражаем особую благодарность Kamal Shree (GDE — Dart & Flutter) за рецензирование этой статьи.