Каждый Android-разработчик проводит в окне Logcat примерно треть своей жизни. И пока проект маленький, там всё хорошо. Но когда приложение разрастается до десятков изолированных модулей, Logcat превращается в филиал ада. Туда одновременно сыплется аналитика, сетевые запросы, логи инициализации, внутренние «шорохи» библиотек и куча другого мусора.
Недавно я дошёл до точки кипения. Локализовать плавающий баг в этом бесконечном водопаде текста стало физически больно - глаза просто вытекали. Стандартные фильтры Android Studio, конечно, работают, но когда тебе нужно постоянно переключаться между логами пяти разных фич и при этом не видеть спам от остальных, ручная настройка регулярных выражений начинает сниться в кошмарах. Нужно постоянно помнить точные названия тегов всех модулей, собирать длинные конструкции с исключениями через минусы и вертикальные черты, а через пять минут переписывать всё заново под другую задачу.
Сначала я пробовал стандартные фильтры LogCat, затем написал скрипты которые чистили по нужным тегам, затем переключался на скармливание этих логов в AI. Выжранные токены заставили чистить уже подготовленные логи. Но все равно это было неудобно и отнимало время.
Поняв, что так дальше жить нельзя, я решил сделать инструмент под себя. Результатом стал плагин TAO LogExt для Android Studio, который превращает эту рутину в удобное управление в несколько кликов.
Идея плагина в том, чтобы дать разработчику возможность управления прямо внутри студии в виде отдельного Tool Window. Вместо того чтобы держать в голове названия тегов или копировать их из кода, они автоматически заводятся по мере поступления в интерфейс плагина и их можно активировать обычными чекбоксами. То, чего не хватало - это возможность объединять эти теги в группы или пресеты. Например, можно собрать профиль для сети, куда войдут теги OkHttp и вашего API, профиль для навигации или отдельный профиль под конкретную фичу. Переключение между ними занимает долю секунды: ткнул нужный пресет, и рабочая область моментально очистилась от лишнего шума.
Точно так же плагин решает проблему со спамящими сторонними SDK. Если какая-то библиотека начинает забивать консоль сообщениями каждую секунду, её тег можно отправить в игнор, и он исчезнет из выдачи (вернуть можно). При этом плагин не ломает стандартное поведение Android Studio и не пытается переписать сам Logcat - он просто автоматизирует рутину на основе выбранных вами галочек.
Инструмент создавался исключительно для себя, чтобы спасти остатки нервов на проекте с легаси и огромным количеством логов. В итоге процесс дебага ускорился в разы: вместо бесконечного набора текста в строке поиска я теперь просто переключаю профили мышкой.
Ниже я расскажу, как этот плагин устроен «под капотом», чтобы вы могли создать свой инструмент или разобраться, как работают расширения для Android Studio.

С чего начинается плагин: Инфраструктура
Если вы привыкли к стандартным Android-проектам, то разработка плагинов может сначала сбить с толку. Мне так было непривычно. Здесь используется IntelliJ Platform SDK, который диктует свои правила игры. На самом деле все оказалось не слишком страшным.
Для удобства при начале разработки я сначала запустил IntelliJ IDEA, так как там есть визард для создания плагинов. После создания я загрузил проект в Android Studio и продолжил разработку в ней чтобы была возможность сразу тестировать вживую.
Итак, о коде. Сразу скажу, что если кусочков кода недостаточно, то их можно посмотреть целиком в репозитории.
build.gradle.kts - Начинаем настройку
В блоке intellijPlatform пишем целевую платформу, указывая через local(file(…)) путь к установленной Android Studio. Здесь же в pluginConfiguration задаются метаданные плагина и параметры ideaVersion. Маленький нюанс, поля sinceBuild и untilBuild определяют и ограничивают версии Студии, в которых плагин сможет запуститься. Например, значение 261 означает поддержку версий начиная с 2026.1. Тут нужно помнить что версии для Android Studio и IntelliJ IDEA отличаются. Для себя они это отмечают префиксами AI- IC- IU- IE-, но в данной настройке только цифрами ¯\_(ツ)_/¯ и это может иногда вызвать путаницу.
Очень важно в runIde настроить параметры JVM и системные свойства. Здесь нужно прописать disable.android.first.run, чтобы при каждом запуске отладки не запускался Setup Wizard, а память maxHeapSize выставить с запасом, чтобы среда разработки работала быстрее. И наконец, блок signing подготавливает плагин к публикации: здесь указываются пути к ключам и сертификатам для подписи дистрибутива, без чего размещение в Marketplace невозможно. В проекте я добавил бат файлы, которые упрощают это дело.
intellijPlatform { pluginConfiguration { name = "TAO LogExt" ideaVersion { sinceBuild = "261" // Начиная с Android Studio 2026.1 untilBuild = "271.*" } } signing { certificateChainFile = file("certificate_chain.crt") privateKeyFile = file("private_key.pem") } }
plugin.xml - манифест для плагина
Этот файл лежит в src/main/resources/META-INF/ и выполняет роль AndroidManifest.xml. Как вы понимаете, он необходим. Поле id служит уникальным идентификатором, который не стоит менять после публикации. В тегах depends указываем нужные нам модули: com.intellij.modules.platform дает доступ к базовым окнам и редакторам, а org.jetbrains.android открывает возможности работы с ADB и управления устройствами. Без этой связки классы Android не будут доступны.
Именно здесь прописываем наш toolWindow, создающий боковую панель, или projectService для хранения пользовательских настроек. Команды управления описываются в блоке actions. Здесь регистрируются наши ...Action и привязываются к группам меню ViewMenu или ToolsMenu, чтобы плагин можно было найти в панелях Студии. Также заполняются данные с информацией об авторе. В общем есть аналогии с регистрацией Activity и Service.
<idea-plugin> <id>ua.at.tsvetkov.logext</id> <name>TAO LogExt</name> <vendor url="https://github.com/lordtao">Alexandr Tsvetkov</vendor> <depends>com.intellij.modules.platform</depends> <depends>org.jetbrains.android</depends> <extensions defaultExtensionNs="com.intellij"> <toolWindow id="TAO LogExt" anchor="bottom" icon="/icons/toolWindowIcon.svg" factoryClass="ua.at.tsvetkov.logext.ui.LogExtToolWindowFactory"/> <projectService serviceImplementation="ua.at.tsvetkov.logext.services.LogExtSettingsService"/> </extensions> <actions> <action id="ua.at.tsvetkov.logext.actions.OpenLogExtAction" class="ua.at.tsvetkov.logext.actions.OpenLogCatAction" text="TAO LogExt" icon="/icons/toolWindowIcon.svg"> <add-to-group group-id="ViewMenu" anchor="last"/> </action> </actions> </idea-plugin>
Ресурсы: Иконки и графика
Графика плагина живет в директории src/main/resources/icons/, но есть одно исключение - иконка самого плагина, которая отображается в Marketplace и списке установленных расширений. Файл pluginIcon.svg (и его версия для темной темы pluginIcon_dark.svg) должен лежать строго в папке src/main/resources/META-INF/. Если этого не сделать, в каталоге плагинов будет отображаться стандартная заглушка.
Для всех остальных элементов интерфейса принято использовать формат SVG, чтобы они корректно масштабировались на любых экранах. Если вы создаете иконку toolWindowIcon.svg, то для темной темы IDE автоматически попытается найти файл с суффиксом dark, например toolWindowIcon_dark.svg. Это позволяет иконкам оставаться контрастными при переключении оформления. В TAO LogExt также используются вспомогательные иконки, такие как индикаторы активности фильтров (лампочки), которые динамически меняются в интерфейсе в зависимости от состояния плагина по такому же принципу.
Добываем логи: Рефлексия и ADB
Самое интересное начинается при попытке достать логи. Нам нужна библиотека ddmlib, которая уже есть в Студии. Но версии Студии меняются, и чтобы плагин не переставал работать при обновлении, я использовал рефлексию. В LogExtListenerService находим метод executeShellCommand у устройства и запускаем команду logcat -v threadtime. Это и есть наши любимые логи. Такой вариант, надеюсь, позволит сохранять независимость от внутренних изменений Logcat в новых версиях IDE.
Чтобы перехватить поток данных, мы создаем динамический прокси для интерфейса IShellOutputReceiver. Это позволяет нам подписаться на события вывода команды без прямой компиляции против внутренних классов ddmlib.
private fun setupDeviceLogcat(device: Any, callback: (String) -> Unit) { val receiverClass = Class.forName("com.android.ddmlib.IShellOutputReceiver") val proxyReceiver = java.lang.reflect.Proxy.newProxyInstance( receiverClass.classLoader, arrayOf(receiverClass) ) { _, method, args -> if (method.name == "addOutput") { val data = args[0] as ByteArray val offset = args[1] as Int val length = args[2] as Int callback(String(data, offset, length)) } null } val executeMethod = device.javaClass.getMethod("executeShellCommand", String::class.java, receiverClass) Thread { executeMethod.invoke(device, "logcat -v threadtime", proxyReceiver) }.apply { isDaemon = true start() } }
UI: Строим панель управления
Разработка интерфейса для плагина отличается от Android: вместо XML-лейаутов и View/Compose здесь используется Swing и набор компонентов от JetBrains (JB-компоненты). Если в Android мы привыкли к RecyclerView, то здесь его роль часто выполняют JBList или динамически наполняемые JPanel. Вместо ConstraintLayout чаще используются BorderLayout или GridBagLayout. В общем, пришлось со скрипом вспоминать десктоп Java, хотя пишем все на Kotlin. Процесс начинается с LogCatToolWindowFactory, которая служит входной точкой и создает панель при запуске.
Точка входа: ToolWindowFactory
Наш главный класс должен реализовывать интерфейс ToolWindowFactory. Его единственная задача - создать содержимое окна при обращении пользователя. Мы создаем экземпляр нашей панели и регистрируем его в менеджере контента.
class LogExtToolWindowFactory : ToolWindowFactory { override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { val logExtPanel = LogExtPanel(project) val content = ContentFactory.getInstance().createContent(logExtPanel, "", false) toolWindow.contentManager.addContent(content) // регистрируем панель для очистки ресурсов при закрытии Disposer.register(toolWindow.contentManager, logExtPanel) } }
Основной экран: LogExtPane
Сама панель наследуется от стандартного JPanel (обычно с BorderLayout). Сердцем плагина является ConsoleView - текстовый движок Студии, который поддерживает раскраску строк и кликабельные ссылки. Мы размещаем его в центре панели, а сверху и сбоку добавляем элементы управления.
class LogExtPanel(private val project: Project) : JPanel(BorderLayout()), Disposable { // Используем готовый ConsoleView из IntelliJ SDK private val consoleView: ConsoleView = object : ConsoleViewImpl(project, true) { override fun createCompositeFilter(): com.intellij.execution.filters.CompositeFilter { val compositeFilter = com.intellij.execution.filters.CompositeFilter(project) compositeFilter.addFilter(LogSourceLinkFilter(project)) return compositeFilter } } init { // Компонуем интерфейс add(createHeader(), BorderLayout.NORTH) // Выбор девайса и процесса add(consoleView.component, BorderLayout.CENTER) // Поле с логами createToolbar() // Добавляем кнопки управления } }

Для работы плагина пользователю необходимо управлять настройками. Вот базовые элементы.
Диалоговые окна: DialogWrapper
Для создания окон настроек (например, TagFilterDialog) используем DialogWrapper. Это обертка над стандартными диалогами Swing, которая берет на себя управление кнопками OK/Cancel и размерами окна.
class LogExportDialog(private val project: Project) : DialogWrapper(project) { private val globalSettings = LogExtGlobalSettingsService.getInstance() private val pathField = TextFieldWithBrowseButton() private val minimizeCheck = JBCheckBox("Minimize for AI", globalSettings.state.minimizeForAi) init { title = "Export Logs" pathField.text = globalSettings.state.lastExportPath ?: "" pathField.addActionListener { .... } init() } override fun createCenterPanel(): JComponent { val panel = FormBuilder.createFormBuilder() .addLabeledComponent("Export to:", pathField) .addComponent(minimizeCheck) .panel panel.preferredSize = Dimension(600, panel.preferredSize.height) return panel } .... override fun doOKAction() { val state = globalSettings.state .... super.doOKAction() } }
Настройки
Куда же сохранять настройки плагина? К примеру в плагине предусмотрена настройка цветов для каждого уровня логирования. Предпочтения пользователя сохраняются используя Storage при помощи PersistentStateComponent.
@Service(Service.Level.PROJECT) @State(name = "LogExtSettings", storages = [Storage("logext_plugin_settings.xml")]) // <- Вот где храним class LogExtSettingsService : PersistentStateComponent<LogExtSettingsService.State> { class State { // это наши разные настройки var ignoredTags: MutableSet<String> = mutableSetOf() var levelColors: MutableMap<String, LevelAttributes> = mutableMapOf() var showDate: Boolean = true var showTime: Boolean = true var showMillis: Boolean = true var showPid: Boolean = true var showTid: Boolean = true var tagWidth: Int = 23 var maxHistorySize: Int = 100000 var clearLogOnStart: Boolean = true var openOnStart: Boolean = true var lastExportPath: String? = null var lastTagsPath: String? = null var minimizeForAi: Boolean = false var showDuplicateTags: Boolean = false } private var myState = State() override fun getState(): State = myState // тут банально override fun loadState(state: State) { // тут тоже банально myState = state } }

Итог
Создание плагина началось с желания упростить ежедневную работу с логами. TAO LogExt помог мне сделать процесс дебага более наглядным и удобным. Я уверен что есть еще много фич которые можно встроить в плагин для удобства. Буду рад конструктивной критике и фидбекам.
Исходники проекта открыты на GitHub в репозитории https://github.com/lordtao/LogExt. Скачать и установить плагин можно либо из релиза https://github.com/lordtao/LogExt/releases, из каталога плагинов https://plugins.jetbrains.com/plugin/32270-tao-logext или найти в Android Studio в Settings -> Plugins -> Marketplace поиском TAO LogExt.
Благодарю за помощь в вычитке и критических замечаниях Gemini

