From 239f2bf8a16a9f94b8c32649ee45584d070eb5da Mon Sep 17 00:00:00 2001 From: Yauheni Slizh Date: Wed, 8 Apr 2026 00:00:50 +0200 Subject: [PATCH 1/3] Add Android Log statement support Add android.util.Log as a built-in logging framework option with Log.d(tag, message) format for both Kotlin and Java. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../loggingplugin/services/LogStrategy.kt | 21 ++ .../loggingplugin/settings/LoggingSettings.kt | 1 + .../services/LogInserterServiceTest.kt | 224 ++++++++++++++++++ 3 files changed, 246 insertions(+) diff --git a/src/main/kotlin/com/github/kiolk/loggingplugin/services/LogStrategy.kt b/src/main/kotlin/com/github/kiolk/loggingplugin/services/LogStrategy.kt index e2ce5fd..7230ad2 100644 --- a/src/main/kotlin/com/github/kiolk/loggingplugin/services/LogStrategy.kt +++ b/src/main/kotlin/com/github/kiolk/loggingplugin/services/LogStrategy.kt @@ -44,6 +44,26 @@ class PrintlnStrategy : LogStrategy { override fun getJavaImport(): String? = null } +class AndroidLogStrategy : LogStrategy { + override fun createKotlinLog( + factory: KtPsiFactory, + tag: String, + message: String, + ): String = "Log.d(\"$tag\", \"$message\")" + + override fun createJavaLog( + factory: PsiElementFactory, + tag: String, + message: String, + ): String = "Log.d(\"$tag\", \"$message\");" + + override fun getRemovalPatterns(tag: String): List = listOf("Log.d(\"$tag\"", tag) + + override fun getKotlinImport(): String = "android.util.Log" + + override fun getJavaImport(): String = "android.util.Log" +} + class TimberStrategy : LogStrategy { override fun createKotlinLog( factory: KtPsiFactory, @@ -124,6 +144,7 @@ object LogStrategyFactory { ): LogStrategy { return when (framework) { LoggingSettings.LoggingFramework.PRINTLN -> PrintlnStrategy() + LoggingSettings.LoggingFramework.ANDROID_LOG -> AndroidLogStrategy() LoggingSettings.LoggingFramework.TIMBER -> TimberStrategy() LoggingSettings.LoggingFramework.NAPIER -> NapierStrategy() LoggingSettings.LoggingFramework.CUSTOM -> diff --git a/src/main/kotlin/com/github/kiolk/loggingplugin/settings/LoggingSettings.kt b/src/main/kotlin/com/github/kiolk/loggingplugin/settings/LoggingSettings.kt index f346eaf..01d241d 100644 --- a/src/main/kotlin/com/github/kiolk/loggingplugin/settings/LoggingSettings.kt +++ b/src/main/kotlin/com/github/kiolk/loggingplugin/settings/LoggingSettings.kt @@ -12,6 +12,7 @@ import com.intellij.openapi.project.Project class LoggingSettings : PersistentStateComponent { enum class LoggingFramework(val displayName: String) { PRINTLN("System Println"), + ANDROID_LOG("Android Log"), TIMBER("Timber"), NAPIER("Napier"), CUSTOM("Custom"), diff --git a/src/test/kotlin/com/github/kiolk/loggingplugin/services/LogInserterServiceTest.kt b/src/test/kotlin/com/github/kiolk/loggingplugin/services/LogInserterServiceTest.kt index a38d54b..4c43294 100644 --- a/src/test/kotlin/com/github/kiolk/loggingplugin/services/LogInserterServiceTest.kt +++ b/src/test/kotlin/com/github/kiolk/loggingplugin/services/LogInserterServiceTest.kt @@ -140,6 +140,230 @@ class LogInserterServiceTest : BasePlatformTestCase() { myFixture.checkResult(after) } + fun testInsertKotlinAssignmentAndroidLogLogs() { + val before = + """ + fun test() { + var x = 1 + x = 2 + } + """.trimIndent() + + val after = + """ + import android.util.Log + + fun test() { + var x = 1 + x = 2 + Log.d("TestTag", "x assigned new value: ${'$'}{x}") + } + """.trimIndent() + + val psiFile = myFixture.configureByText("Test.kt", before) as KtFile + + WriteCommandAction.runWriteCommandAction(project) { + service.insertKotlinAssignmentLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.ANDROID_LOG) + } + + myFixture.checkResult(after) + } + + fun testInsertKotlinMethodAndroidLogLogs() { + val before = + """ + fun test(param: String) { + val y = 0 + } + """.trimIndent() + + val after = + """ + import android.util.Log + + fun test(param: String) { + Log.d("TestTag", "test(param=${'$'}{param})") + val y = 0 + } + """.trimIndent() + + val psiFile = myFixture.configureByText("Test.kt", before) as KtFile + + WriteCommandAction.runWriteCommandAction(project) { + service.insertKotlinMethodLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.ANDROID_LOG) + } + + myFixture.checkResult(after) + } + + fun testRemoveKotlinAndroidLogLogs() { + val before = + """ + fun test() { + Log.d("TestTag", "some log") + var x = 1 + Log.d("OtherTag", "other log") + } + """.trimIndent() + + val after = + """ + fun test() { + var x = 1 + Log.d("OtherTag", "other log") + } + """.trimIndent() + + val psiFile = myFixture.configureByText("Test.kt", before) as KtFile + + WriteCommandAction.runWriteCommandAction(project) { + service.removeLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.ANDROID_LOG) + } + + myFixture.checkResult(after) + } + + fun testRemoveAndroidLogInsideScopeFunctionKeepsBlock() { + val before = + """ + fun test() { + args.productUUID?.apply { + productUUID = this + Log.d("TestTag", "productUUID assigned new value: ${'$'}{productUUID}") + } + } + """.trimIndent() + + val after = + """ + fun test() { + args.productUUID?.apply { + productUUID = this + } + } + """.trimIndent() + + val psiFile = myFixture.configureByText("Test.kt", before) as KtFile + + WriteCommandAction.runWriteCommandAction(project) { + service.removeLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.ANDROID_LOG) + } + + myFixture.checkResult(after) + } + + fun testRemoveKotlinAndroidLogLogsAlsoRemovesImport() { + val before = + """ + import android.util.Log + + fun test() { + Log.d("TestTag", "some log") + var x = 1 + } + """.trimIndent() + + val after = + """ + fun test() { + var x = 1 + } + """.trimIndent() + + val psiFile = myFixture.configureByText("Test.kt", before) as KtFile + + WriteCommandAction.runWriteCommandAction(project) { + service.removeLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.ANDROID_LOG) + } + + myFixture.checkResult(after) + } + + fun testRemoveKotlinAndroidLogLogsKeepsImportWhenOtherLogsRemain() { + val before = + """ + import android.util.Log + + fun test() { + Log.d("TestTag", "some log") + Log.d("OtherTag", "other log") + } + """.trimIndent() + + val after = + """ + import android.util.Log + + fun test() { + Log.d("OtherTag", "other log") + } + """.trimIndent() + + val psiFile = myFixture.configureByText("Test.kt", before) as KtFile + + WriteCommandAction.runWriteCommandAction(project) { + service.removeLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.ANDROID_LOG) + } + + myFixture.checkResult(after) + } + + fun testInsertAndroidLogLogsWithImport() { + val before = + """ + package com.example + + fun test() { + var x = 1 + x = 2 + } + """.trimIndent() + + val after = + """ + package com.example + + import android.util.Log + + fun test() { + var x = 1 + x = 2 + Log.d("TestTag", "x assigned new value: ${'$'}{x}") + } + """.trimIndent() + + val psiFile = myFixture.configureByText("Test.kt", before) as KtFile + + WriteCommandAction.runWriteCommandAction(project) { + service.insertKotlinAssignmentLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.ANDROID_LOG) + } + + myFixture.checkResult(after) + } + + fun testInsertAndroidLogLogsWithExistingImport() { + val content = + """ + package com.example + + import android.util.Log + + fun test() { + var x = 1 + x = 2 + Log.d("TestTag", "x assigned new value: ${'$'}{x}") + } + """.trimIndent() + + val psiFile = myFixture.configureByText("Test.kt", content) as KtFile + + WriteCommandAction.runWriteCommandAction(project) { + service.insertKotlinAssignmentLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.ANDROID_LOG) + } + + myFixture.checkResult(content) + } + fun testInsertKotlinAssignmentTimberLogs() { val before = """ From a544d8b7c3120dbacfde9ba47d19e489fd321324 Mon Sep 17 00:00:00 2001 From: Yauheni Slizh Date: Wed, 8 Apr 2026 00:08:34 +0200 Subject: [PATCH 2/3] Bump version to 1.1.0 and update documentation Add Android Log and Custom framework to all docs: CHANGELOG, README, MARKETPLACE, and plugin.xml. Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 8 ++++++++ MARKETPLACE.md | 9 ++++++--- README.md | 20 ++++++++++++++++---- build.gradle.kts | 2 +- src/main/resources/META-INF/plugin.xml | 4 ++-- 5 files changed, 33 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 597ec59..e0dd3b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.1.0] - 2026-04-08 + +### Added +- Android Log support (`Log.d(tag, message)` from `android.util.Log`) as a built-in logging framework +- Custom logging framework support with user-defined templates using `{tag}` and `{message}` placeholders +- Custom template UI fields in the tool window (Kotlin template, Java template, optional import path) +- Java import fallback for unresolvable classes (needed for custom framework imports) + ## [1.0.1] - 2026-02-19 ### Fixed diff --git a/MARKETPLACE.md b/MARKETPLACE.md index 5a40351..84d3f15 100644 --- a/MARKETPLACE.md +++ b/MARKETPLACE.md @@ -37,9 +37,10 @@ Debugging complex applications often requires adding temporary logging statement ### 📦 Multiple Logging Frameworks - **System.out.println**: Classic debugging output (default) +- **Android Log**: Native Android logging — `Log.d("Tag", "message")` - **Timber**: Popular Android logging library — `Timber.tag("Tag").d("message")` - **Napier**: Kotlin Multiplatform logging library — `Napier.d("message", tag = "Tag")` -- **Coming Soon**: Log4j, SLF4J, and custom frameworks +- **Custom**: Define your own templates with `{tag}` and `{message}` placeholders ### ⚙️ Flexible Configuration - **Method Execution Tracking**: Log when methods are called @@ -165,6 +166,7 @@ Access via **LoggingOptions** tool window: | **Track Assignments** | Log variable assignments | ✅ Enabled | | **Log Tag** | Custom prefix for logs | "Myfancy log" | | **Logging Framework** | Choose output method | System.out.println | +| **Custom Templates** | Define your own log format (when Custom is selected) | `Log.d("{tag}", "{message}")` | --- @@ -177,7 +179,7 @@ Access via **LoggingOptions** tool window: - **Platforms**: Windows, macOS, Linux ### Smart Features -- **Import Management**: Automatically adds imports on insertion and removes them when no logs remain (Timber, Napier) +- **Import Management**: Automatically adds imports on insertion and removes them when no logs remain (Android Log, Timber, Napier, Custom) - **Scope Detection**: Works on current class or entire file - **Safe Removal**: Only removes logs inserted by this plugin, preserving logs from other tags - **Scope Function Awareness**: When removing a log inside an `apply`/`let`/`run` block, only the log line is removed — the block is preserved @@ -206,8 +208,10 @@ Access via **LoggingOptions** tool window: - Use keyboard shortcuts for faster workflow ### Tip 4: Framework-Specific Benefits +- **Android Log**: Automatically adds/removes `import android.util.Log`, uses `Log.d("Tag", "message")` — native Android logging - **Timber**: Automatically adds/removes `import timber.log.Timber`, uses `Timber.tag("Tag").d("message")` - **Napier**: Automatically adds/removes `import io.github.aakira.napier.Napier`, uses `Napier.d("message", tag = "Tag")` — ideal for Kotlin Multiplatform projects +- **Custom**: Define your own log format with `{tag}` and `{message}` placeholders, with an optional import path - **println**: Simple and works everywhere, no dependencies --- @@ -229,7 +233,6 @@ Access via **LoggingOptions** tool window: ### Coming Soon - ✨ Support for Log4j and SLF4J - ✨ More Kotlin Multiplatform framework integrations -- ✨ Custom log templates - ✨ Log level configuration (DEBUG, INFO, WARN, ERROR) - ✨ Smart duplicate detection - ✨ Bulk operations across multiple files diff --git a/README.md b/README.md index 188103b..8702066 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Log Injector for IntelliJ IDEA -[![Version](https://img.shields.io/badge/version-1.0.1-blue.svg)](https://github.com/Kiolk/Log-Injector) +[![Version](https://img.shields.io/badge/version-1.1.0-blue.svg)](https://github.com/Kiolk/Log-Injector) [![IntelliJ Platform](https://img.shields.io/badge/IntelliJ-2024.3-orange.svg)](https://www.jetbrains.com/idea/) An IntelliJ IDEA plugin that automatically inserts and removes logging statements in your Java and Kotlin code. Save time and improve debugging efficiency by adding comprehensive logging with just a few clicks. @@ -13,8 +13,10 @@ An IntelliJ IDEA plugin that automatically inserts and removes logging statement - **Multi-Language Support**: Works with both Java and Kotlin files - **Multiple Logging Frameworks**: - System.out.println (default) + - Android Log (`Log.d` from `android.util.Log`) - Timber (Android logging library) - Napier (Kotlin Multiplatform logging library) + - Custom (user-defined templates with `{tag}` and `{message}` placeholders) - **Automatic Import Management**: Imports are added when inserting logs and removed automatically when all logs for a framework are removed ### 📊 Tracking Options @@ -70,7 +72,7 @@ Access the plugin settings through the **LoggingOptions** tool window on the rig - **Track Method Execution**: Enable/disable method entry logging - **Track Assignments**: Enable/disable variable assignment logging - **Log Tag**: Set a custom prefix for your log statements (default: "Myfancy log") -- **Logging Framework**: Choose between System.out.println, Timber, or Napier +- **Logging Framework**: Choose between System.out.println, Android Log, Timber, Napier, or Custom ### Examples @@ -279,7 +281,16 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file ## Changelog -### Version 1.0.0 (Current) +### Version 1.1.0 (Current) +- Android Log support (`android.util.Log`) +- Custom logging framework support with user-defined templates +- Custom template UI in the tool window + +### Version 1.0.1 +- Napier logging framework support +- Scope function block removal fix + +### Version 1.0.0 - Initial release - Support for Java and Kotlin - Method execution tracking @@ -292,8 +303,9 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file Future features under consideration: - [x] Napier logging framework support +- [x] Android Log support +- [x] Custom log templates - [ ] More logging framework support (Log4j, SLF4J, etc.) -- [ ] Custom log templates - [ ] Smart log placement (avoid duplicates) - [ ] Log level configuration - [ ] Bulk operations across multiple files diff --git a/build.gradle.kts b/build.gradle.kts index d4a7e8e..79a0c07 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { id("org.jlleitschuh.gradle.ktlint") version "12.1.2" } -version = "1.0.1" +version = "1.1.0" group = "com.github.kiolk.loggingplugin" repositories { diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 3259dff..23793a9 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -15,7 +15,7 @@
  • One-Click Log Insertion: Automatically add logging statements to all methods in a class
  • Quick Removal: Remove all inserted logs just as easily
  • Multi-Language Support: Works seamlessly with both Java and Kotlin files
  • -
  • Multiple Frameworks: Choose between System.out.println, Timber, or Napier logging
  • +
  • Multiple Frameworks: Choose between System.out.println, Android Log, Timber, Napier, or define your own Custom logging format
  • Flexible Tracking: Track method execution and variable assignments
  • Customizable: Set your own log tags for easy filtering
  • @@ -33,7 +33,7 @@
  • Track method execution - Log when methods are called
  • Track assignments - Log variable assignments
  • Custom log tags - Set your preferred log prefix
  • -
  • Logging framework - Choose between println, Timber, and Napier
  • +
  • Logging framework - Choose between println, Android Log, Timber, Napier, or Custom
  • Automatic import management - Imports are added on insertion and removed when no longer needed
  • From 6c33d76ff5036ac631833b54eb05fc6209381643 Mon Sep 17 00:00:00 2001 From: Yauheni Slizh Date: Wed, 8 Apr 2026 00:21:31 +0200 Subject: [PATCH 3/3] Fix import removal when class name appears as substring Use word-boundary regex instead of simple string contains when checking if an import is still used. Fixes android.util.Log import not being removed when words like "Logger" or "logging" appear in the file. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../services/LogInserterService.kt | 6 +++-- .../services/LogInserterServiceTest.kt | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/github/kiolk/loggingplugin/services/LogInserterService.kt b/src/main/kotlin/com/github/kiolk/loggingplugin/services/LogInserterService.kt index c24a8ba..7deb1ed 100644 --- a/src/main/kotlin/com/github/kiolk/loggingplugin/services/LogInserterService.kt +++ b/src/main/kotlin/com/github/kiolk/loggingplugin/services/LogInserterService.kt @@ -282,11 +282,12 @@ class LogInserterService(private val project: Project) { ) { if (importPath == null) return val className = importPath.substringAfterLast('.') + val pattern = Regex("\\b${Regex.escape(className)}\\b") val hasRemainingUsage = file.text .lines() .filter { !it.trimStart().startsWith("import ") } - .any { it.contains(className) } + .any { pattern.containsMatchIn(it) } if (hasRemainingUsage) return file.importList?.imports ?.find { it.importPath?.pathStr == importPath } @@ -299,11 +300,12 @@ class LogInserterService(private val project: Project) { ) { if (importPath == null) return val className = importPath.substringAfterLast('.') + val pattern = Regex("\\b${Regex.escape(className)}\\b") val hasRemainingUsage = file.text .lines() .filter { !it.trimStart().startsWith("import ") } - .any { it.contains(className) } + .any { pattern.containsMatchIn(it) } if (hasRemainingUsage) return file.importList ?.findSingleClassImportStatement(importPath) diff --git a/src/test/kotlin/com/github/kiolk/loggingplugin/services/LogInserterServiceTest.kt b/src/test/kotlin/com/github/kiolk/loggingplugin/services/LogInserterServiceTest.kt index 4c43294..c3e48b3 100644 --- a/src/test/kotlin/com/github/kiolk/loggingplugin/services/LogInserterServiceTest.kt +++ b/src/test/kotlin/com/github/kiolk/loggingplugin/services/LogInserterServiceTest.kt @@ -279,6 +279,33 @@ class LogInserterServiceTest : BasePlatformTestCase() { myFixture.checkResult(after) } + fun testRemoveKotlinAndroidLogLogsRemovesImportWhenClassNameAppearsAsSubstring() { + val before = + """ + import android.util.Log + + fun test() { + Log.d("TestTag", "some log") + val logging = "Logger" + } + """.trimIndent() + + val after = + """ + fun test() { + val logging = "Logger" + } + """.trimIndent() + + val psiFile = myFixture.configureByText("Test.kt", before) as KtFile + + WriteCommandAction.runWriteCommandAction(project) { + service.removeLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.ANDROID_LOG) + } + + myFixture.checkResult(after) + } + fun testRemoveKotlinAndroidLogLogsKeepsImportWhenOtherLogsRemain() { val before = """