To log, or not to log: включаем и отключаем LogCat для debug-версии

Сразу скажу, эта заметка — сборник простых и полезных рецептов, ничего более. Пожалуй, стоит даже отнести её к советам для новичков, потому что бывалые разработчики давно уже сделали для себя нечто подобное. Речь идёт о возможности автоматически отключать лишний вывод в LogCat для release-версии приложения. Или, выражаясь иначе, о возможности добавить больше отладочного вывода в debug-режиме, да ещё и получить поддержку со стороны IDE.

Необходимость подобного решения очевидна: debug-версии часто выкидывают в LogCat кучу полезной для разработчика информации, которую в релизе лучше бы скрыть подальше от любопытных глаз. И даже если прятать нечего, загаживать лог в любом случае нехорошо.

Android и его LogCat
Честно утянутая лучшая картинка по запросу LogCat

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

Определение debug-сборки

Первая поставленная задача может решаться сразу несколькими путями. Но наиболее простой и быстрый из них появился ещё в 17-й версии Android Build Tools: средствами SDK автоматически генерируется класс BuildConfig, содержащий статическое поле DEBUG с признаком отладочной сборки. Т.е. проверка будет выглядеть так:

Таким образом, проверка «не весит» почти ничего: всё сводится к чтению boolean-константы, которую оптимизатор вполне может вообще выкинуть из скомпилированного кода за ненадобностью.

Впрочем, здесь появляется тонкость: при использовании Eclipse или старой системы сборки IDEA значение BuildConfig.DEBUG может обновляться некорректно (особенно когда в Eclipse выставлена галочка Build Automatically). В этом случае можно посоветовать либо никогда не забывать полную пересборку проекта, либо воспользоваться дополнительной runtime-проверкой подписи приложения при старте (код легко найти в гугле, «android check debug certificate»). Я же в дальнейшем буду предполагать, что мы используем gradle и Android Studio.

Для большинства проектов достаточно именно такого метода и такого подхода к проблеме (отладочная сборка — есть лог, релизная — нет лога). Но если возникают нестандартные ситуации (например, требуется релиз с выводом в лог, или наоборот — debug с заглушенным выводом), то проще всего создать собственный параметр и добавить его в новый buildType:

В этом случае конфигурация releaseWithLog будет являться релизной сборкой с ведением логов. Естественно, в коде слегка поменяется проверка:

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

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

Вспомогательный логгер

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

  1. Пишем свой класс, проксирующий методы android.util.Log (т.е. вызывающий одноименные методы Log) с дополнительной проверкой на отладку;
  2. Глобально заменяем существующие обращения к логу в проекте на свой прокси-класс.

Реализация очевидна, и для большинства случаев подходит следующий вариант:

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

Наконец, обратите внимание на метод printStackTrace() — его мы ещё обсудим ниже.

Замена вызовов

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

  1. Назовите свой прокси-класс Log, — т.е. так же, как и системный.
  2. Через Replace in path по проекту замените все вхождения строки android.util.Log на имя_своего_пакета.Log. По итогам этой операции мы повсеместно получим корректный код, ссылающийся именно на нужный прокси-класс. А если где-то в коде использовался android.util.LogPrinter, то просто напишите прокси-класс и для него тоже.
Android Studio: замена android.util.Log на класс из своего пакета
Замена простой строкой
  1. Выполните Refactor → Rename, смените имя своего класса — скажем, просто на L, как в примере выше. Это важный шаг: мы должны с первого взгляда и в любом месте кода отличать прямые обращения к системному логу от обращений к нашему прокси! Просто поверьте: так будет гораздо лучше, ведь путаницу может случайно внести даже сама IDE при неверном выполнении Import Class.

Так легко и просто заменятся автоматикой все явные обращения к LogCat, но есть ещё и неявные:

И если System.out может встретиться разве что в копипасте pure-Java-кода, то printStackTrace() попадается повсеместно.

System.out и printStackTrace()

Первый вариант мы будем считать простым и легко поддающимся лечению. На моей практике в 99% случаев используется System.out.println() и System.err.println(), потому поможет простая текстовая замена:

Нагляднее показать сразу в диалоге замены
Нагляднее показать сразу в диалоге замены

Либо вы можете заменить out и err, как описано, например, здесь.

Нет, одна только их замена не поможет, поскольку android.util.Log не использует System.out или System.err, а ровно наоборот — out и err являются экземплярами AndroidPrintStream, где вызывается Log.println(). Подробнее о всей схеме, например, в этой презентации (слайд 5).

Чуть сложнее ситуация с printStackTrace(). Здесь уже потребуется регулярное выражение для замены:

При соответствующих настройках замены, разумеется:

Переключились в режим регулярных выражений, исключаем строки
Переключились в режим регулярных выражений, исключаем строки

В большинстве случаев и здесь тоже проблем не возникает. Мы получили полностью завёрнутый вывод в LogCat.

Разумеется, мы не контролируем вывод сторонних библиотек, а потому проверка их «говорливости» (равно как борьба с ней) останется за скобками. Повторюсь: вывод в лог мы не перехватываем и уследить способны лишь за собственной дисциплиной в этом вопросе.

Настройка Android Studio

Важный финальный этап: сделать так, чтобы IDE не мешала нам работать с новым логом, а вовсе даже наоборот — помогала. К счастью, здесь всё сводится к элементарной настройке шаблонов.

Открываем настройки, выбираем Editor → File and Code Templates. Откроется окно шаблонов текущего проекта («For current project»), нас в нём интересует для начала Catch Statement Body на вкладке Code. Он отвечает за самое, пожалуй, больное место проекта: автоматически создаваемые catch-блоки. По умолчанию туда ставится printStackTrace() напрямую, а нам нужна «завёрнутая» версия. Меняем шаблон:

Диалог настройки шаблона Android Studio
Новый шаблон printStackTrace(): через наш логгер

Эту настройку я считаю необходимой и достаточной; впрочем, есть ещё одна полезная возможность — автоматический импорт нашего логгера в новые созданные классы, например. Если желаем получить подобное (а потом уже выкидывать лишнее через Optimize Imports), то идём на вкладку Includes и выбираем File Header.

Здесь лежит тот код, который вставляется перед каждым создаваемым через IDE классом, включая шаблоны Android-объектов. По умолчанию там установлен всего лишь комментарий с указанием имени автора и даты создания файла. Мы же допишем перед этим комментарием импорт нашего логгера:

Добавлен импорт
Добавлен импорт

Здесь же, кстати, можно поменять текст комментария. Обратите внимание, что добавленный импорт сломает шаблон package-info, а потому из него можно просто выкинуть строку

Ведь для package-info она всё равно по большому счёту бесполезна.

Вот так в два простых шага мы получили рабочую среду, помогающую нам использовать собственный логгер. Главное — не забыть нажать Apply или OK.

Подобьём

Итоги статьи банальны: вот именно так мы легче всего получаем управляемый вывод в LogCat.

Пожалуй, здесь интереснее побочные эффекты, ведь попутно был

  1. дан репепт опеределения debug-сборки,
  2. показан живой пример собственных параметров через gradle,
  3. разобрана хитрость с заменой кода малой кровью в проектах любой сложности,
  4. показана настройка шаблонов IDE (и её полезность),
  5. и просто даны занимательные факты и ссылки.

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

Скриншот побочных эффектов в TextView.java
TextView.java: в коде Android тоже любят побочные эффекты

И хотя не сказал бы что я don’t care about the result, но оцениваю сейчас свою работу в первую очередь с этой позиции.

Да и логом управлять тоже полезно, разумеется.

To log, or not to log: включаем и отключаем LogCat для debug-версии
Оцените пост


Комментарии:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Капча (решите пример) *