2.17. Project: логирование

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

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

В этом параграфе мы рассмотрим разные способа логирования в Dart и Flutter. Двигаться будем от простого к сложному, пока не настроим логирование на уровне всего приложения.

Самый простой способ: функция print

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

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

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

Совсем отказываться от print не стоит — просто эта функция не даёт той тонкости настройки, которая делает отладку приложения более эффективной и безопасной.

Логирование по уровням

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

  • verbose — малозначительные логи, условно «техническая» информация, требующаяся для глубокого погружения в то, что происходило до интересующего вас при дебаге момента;
  • debug — чуть более важный уровень «технических» логов, часто выводящийся в консоль по умолчанию, в отличие от verbose;
  • info — информационные сообщения, отражающие наступление каких-то существенных событий, но исключительно в рамках ожидаемого поведения;
  • warning — что-то уже пошло не так, однако всё ещё не портит пользовательский опыт;
  • error — произошла ошибка, которая сломала сценарий взаимодействия пользователя с программой;
  • critical/fatal — критическая ошибка, разрушающая пользовательский опыт, например крэш.

В Flutter вы можете использовать функцию log из библиотеки dart:developer для логирования таких гранулярных сообщений. Эта функция принимает три параметра: сообщение, уровень критичности и имя. Имя используется для указания имени приложения или компонента, который выполняет логирование. Уровень критичности определяет тип сообщения, которое логируется.

Пример использования функции log во Flutter
1import 'dart:developer';
2
3void main() {
4  log('This is a verbose message', name: 'MyApp', level: 200);
5  log('This is a debug message', name: 'MyApp', level: 300);
6  log('This is an info message', name: 'MyApp', level: 400);
7  log('This is a warning message', name: 'MyApp', level: 500);
8  log('This is an error message', name: 'MyApp', level: 1000);
9  log('This is a critical/fatal message', name: 'MyApp', level: 2000);
10}

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

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

Чтобы увидеть логи в консоли Flutter, вы можете запустить ваше приложение в debug-режиме и просмотреть вывод в консоли или открыть вкладку “Debug Console” в редакторе кода.

Вот так будет выглядеть вывод консоли:

1V/MyApp(1234): This is a verbose message
2D/MyApp(1234): This is a debug message
3I/MyApp(1234): This is an info message
4W/MyApp(1234): This is a warning message
5E/MyApp(1234): This is an error message
6F/MyApp(1234): This is a critical/fatal message

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

Важно помнить, что, несмотря на название, сообщения, выводимые debugPrint, также печатаются в релизных сборках. Таким образом, использовать debugPrint стоит аналогично обычному print и актуально оно, если выводимые сообщения содержат большой объём текста.

Логирование на уровне приложения

Хотя использование print/debugPrint и более продвинутого log позволяет успешно сообщать о происходящем в программе в консоль, это далеко не самые удобные инструменты.

print и debugPrint совсем примитивны. Над log вам потребуется написать собственную обёртку, чтобы не забывать, какое именно значение — 200, 300, 400 или 500 — соответствует искомому уровню критичности.

Что насчёт кастомизации стиля выводимых сообщений? А насчёт настройки отдельных хранилищ для сообщений на случай, если вы захотите дать пользователю возможность поделиться логом при отправке сообщения разработчикам или вовсе программно отправлять всю историю логов за последние X секунд при возникновении ошибки? Всё это невозможно из коробки реализовать при использовании log.

Для этого можно использовать сторонние библиотеки — это популярный подход к организации логирования. Мы рекомендуем две:

  • logger — самая популярная библиотека;
  • logging — чуть более простая, но от разработчиков Dart.

Далее мы рассмотрим именно logger, а с logging вы при желании сможете разобраться сами.

logger

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

1import 'package:logger/logger.dart';
2
3void main() {
4 final logger = Logger();
5 
6  logger.v('Verbose message');
7  logger.d('Debug message');
8  logger.i("Info message");
9  logger.w("Warning message");
10  logger.e("Error message");
11  logger.wtf("What a terrible failure message");
12  
13  //...
14}

Выводимые сообщения при этом будут выглядеть следующим образом:

flutter

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

Что стоит помнить при организации системы логирования

  • Стоит сразу начинать использовать продвинутые инструменты логирования, а не откладывать на потом — это позволит сэкономить кучу времени, а также нервов при дебаге, который неизбежен в работе над любым приложением.
  • Внимательно подходите к выбору уровня критичности сообщения — это позволит быстро ориентироваться в потоке сообщений и фильтровать только необходимые, игнорируя ненужные.
  • Не логируйте персональные данные пользователей. Это неэтично и зачастую противозаконно.
  • В случае работы с многомодульным проектом не пренебрегайте тегами или настройкой дерева логгеров, чтобы легко понимать, какой именно модуль залогировал что-то в консоль.

Теперь вы знаете, как настроить логирование: как на уровне отдельного метода, так и на уровне всего приложения.

Чтобы закрепить знания советуем пройти квиз. А в следующем параграфе мы рассмотрим смежную тему: как грамотно обрабатывать ошибки в приложении.

Чтобы добавить в заметки выделенный текст, нажмите Ctrl + E
Предыдущий параграф2.16. Основы работы с сетью и данными
Следующий параграф2.18. Project: обработка ошибок