Логирование — это процесс формирования логов, сообщений программы разработчику о том, что происходит на том или ином этапе выполнения кода.
Организация полного и удобного логирования — важная составляющая разработки любой программы, поскольку это позволяет разработчикам отслеживать поведение своего кода и устранять неполадки.
В этом параграфе мы рассмотрим разные способа логирования в 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 вы при желании сможете разобраться сами.
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}
Выводимые сообщения при этом будут выглядеть следующим образом:

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