惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Heimdal Security Blog
A
Arctic Wolf
K
Kaspersky official blog
V
Vulnerabilities – Threatpost
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Simon Willison's Weblog
Simon Willison's Weblog
L
LINUX DO - 热门话题
MongoDB | Blog
MongoDB | Blog
T
Threat Research - Cisco Blogs
D
Docker
爱范儿
爱范儿
T
Tenable Blog
C
Check Point Blog
B
Blog
C
Cisco Blogs
Vercel News
Vercel News
The Cloudflare Blog
T
Threatpost
NISL@THU
NISL@THU
T
Tor Project blog
V2EX - 技术
V2EX - 技术
P
Palo Alto Networks Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
T
Tailwind CSS Blog
G
GRAHAM CLULEY
P
Privacy & Cybersecurity Law Blog
SecWiki News
SecWiki News
博客园 - 司徒正美
S
Security @ Cisco Blogs
GbyAI
GbyAI
S
Secure Thoughts
Microsoft Security Blog
Microsoft Security Blog
The Register - Security
The Register - Security
Recorded Future
Recorded Future
Cloudbric
Cloudbric
Webroot Blog
Webroot Blog
N
News and Events Feed by Topic
Y
Y Combinator Blog
博客园_首页
T
Troy Hunt's Blog
The Hacker News
The Hacker News
雷峰网
雷峰网
Google DeepMind News
Google DeepMind News
U
Unit 42
AWS News Blog
AWS News Blog
PCI Perspectives
PCI Perspectives
V
Visual Studio Blog
博客园 - 聂微东
有赞技术团队
有赞技术团队
酷 壳 – CoolShell
酷 壳 – CoolShell

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Разработка плагина для Android Studio или Чистим безумную простыню логов в LogCat
Alexandr Tsvetkov · 2026-06-16 · via Все публикации подряд на Хабре

Каждый 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