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/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/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/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
  • 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..c3e48b3 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,257 @@ 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 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 = + """ + 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 = """