diff --git a/jvm/jvm-analysis-impl/resources/META-INF/JvmAnalysisPlugin.xml b/jvm/jvm-analysis-impl/resources/META-INF/JvmAnalysisPlugin.xml index f935e658dd5e..62b9d9e1929a 100644 --- a/jvm/jvm-analysis-impl/resources/META-INF/JvmAnalysisPlugin.xml +++ b/jvm/jvm-analysis-impl/resources/META-INF/JvmAnalysisPlugin.xml @@ -69,6 +69,13 @@ key="jvm.inspection.logging.placeholder.count.matches.argument.count.display.name" implementationClass="com.intellij.codeInspection.logging.LoggingPlaceholderCountMatchesArgumentCountInspection"/> + + +Reports SLF4J, Log4j2 logging calls, such as logger.info("message: {}", key) with similar log messages. +These calls can be non-distinguishable from each other, and this introduces difficulties to understand where a certain log message is from. +

Example (for Java):

+

+  private static void request1(String text) {
+      log.info("Message: {}", text); //similar call
+      doSomething1();
+  }
+
+  private static void request2(int i) {
+      log.info("Message: {}", i); //similar call
+      doSomething2();
+  }
+
+ + + +

New in 2024.1

+ + \ No newline at end of file diff --git a/jvm/jvm-analysis-impl/resources/messages/JvmAnalysisBundle.properties b/jvm/jvm-analysis-impl/resources/messages/JvmAnalysisBundle.properties index 3276ef9a07b8..ea721a176997 100644 --- a/jvm/jvm-analysis-impl/resources/messages/JvmAnalysisBundle.properties +++ b/jvm/jvm-analysis-impl/resources/messages/JvmAnalysisBundle.properties @@ -173,6 +173,9 @@ jvm.inspection.logging.placeholder.count.matches.argument.count.slf4j.throwable. jvm.inspection.logging.placeholder.count.matches.argument.count.slf4j.throwable.option.yes=Yes jvm.inspection.logging.placeholder.count.matches.argument.count.slf4j.throwable.option.no=No +jvm.inspection.logging.similar.message.display.name=Non-distinguishable logging calls +jvm.inspection.logging.similar.message.problem.descriptor=Similar log messages + jvm.inspection.logging.condition.disagrees.with.log.statement.display.name=Log condition does not match logging call jvm.inspection.logging.condition.disagrees.with.log.statement.problem.descriptor=Level of condition ''{0}'' does not match level of logging call ''{1}'' diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/logging/LoggingSimilarMessageInspection.kt b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/logging/LoggingSimilarMessageInspection.kt new file mode 100644 index 000000000000..8e609e0cce9f --- /dev/null +++ b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/logging/LoggingSimilarMessageInspection.kt @@ -0,0 +1,323 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.codeInspection.logging + +import com.intellij.analysis.JvmAnalysisBundle +import com.intellij.codeInspection.AbstractBaseUastLocalInspectionTool +import com.intellij.codeInspection.LocalInspectionToolSession +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.java.JavaBundle +import com.intellij.java.library.JavaLibraryUtil +import com.intellij.modcommand.ModCommand +import com.intellij.modcommand.ModCommandQuickFix +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiElementVisitor +import com.intellij.psi.SmartPointerManager +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.uast.UastHintedVisitorAdapter +import org.jetbrains.annotations.Nls +import org.jetbrains.uast.* +import org.jetbrains.uast.visitor.AbstractUastNonRecursiveVisitor +import org.jetbrains.uast.visitor.AbstractUastVisitor + +private const val MIN_TEXT_LENGTH = 3 +private const val MAX_PART_COUNT = 10 + +class LoggingSimilarMessageInspection : AbstractBaseUastLocalInspectionTool() { + + //otherwise results will be inconsistent + override fun runForWholeFile(): Boolean { + return true + } + + override fun buildVisitor(holder: ProblemsHolder, + isOnTheFly: Boolean, + session: LocalInspectionToolSession): PsiElementVisitor { + val project = holder.project + if (!(JavaLibraryUtil.hasLibraryClass(project, LoggingUtil.SLF4J_LOGGER) || + JavaLibraryUtil.hasLibraryClass(project, LoggingUtil.LOG4J_LOGGER))) { + return PsiElementVisitor.EMPTY_VISITOR + } + + return UastHintedVisitorAdapter.create(holder.file.language, PlaceholderCountMatchesArgumentCountVisitor(holder), + arrayOf(UFile::class.java), directOnly = true) + } + + inner class PlaceholderCountMatchesArgumentCountVisitor( + private val holder: ProblemsHolder, + ) : AbstractUastNonRecursiveVisitor() { + + override fun visitFile(node: UFile): Boolean { + val calls = collectCalls(node).toMutableSet() + if (calls.isEmpty()) return true + val groupedCalls: List> = calls.groupBy { it.receiver?.tryResolve().toUElementOfType() } + .toMutableMap() + .values.map { group -> + group.groupBy { it.methodName }.values + }.flatten() + val groups: List> = groupedCalls.map { group -> + group.map { MessageLog(it, collectParts(it, LOGGER_TYPE_SEARCHERS.mapFirst(it)).splitWithPlaceholders()) } + .filter { log -> log.parts?.any { it.isConstant && it.text != null } ?: false } + } + for (group in groups) { + if (group.size <= 1) continue + var currentGroups: Set> = setOf(group) + //prefilter + if (group.size > 5) { + val minLength = group + .mapNotNull { it.parts } + .filter { it.isNotEmpty() && it[0].isConstant && it[0].text != null && it[0].text?.isNotBlank() == true } + .minOfOrNull { it[0].text?.length ?: 0 } ?: return true + if (minLength == 0) return true + + currentGroups = group + .groupBy { + val parts = it.parts ?: return@groupBy "" + if (parts.isEmpty()) return@groupBy "" + val part0 = parts[0] + if (!part0.isConstant || part0.text == null || part0.text.length < minLength) return@groupBy "" + part0.text.substring(0, minLength) + } + .values + .toSet() + } + for (currentGroup in currentGroups) { + for (firstIndex in 0..currentGroup.lastIndex) { + for (secondIndex in firstIndex + 1..currentGroup.lastIndex) { + if (similar(currentGroup[firstIndex].parts, currentGroup[secondIndex].parts)) { + registerProblem(holder, currentGroup[firstIndex].call, currentGroup[secondIndex].call) + registerProblem(holder, currentGroup[secondIndex].call, currentGroup[firstIndex].call) + } + } + } + } + } + + return super.visitFile(node) + } + + private fun collectCalls(file: UFile): Set { + val result = mutableSetOf() + file.accept(object : AbstractUastVisitor() { + override fun visitCallExpression(node: UCallExpression): Boolean { + LOGGER_TYPE_SEARCHERS.mapFirst(node) ?: return false + result.add(node) + return true + } + }) + return result + } + } + + private fun collectParts(node: UCallExpression, searcher: LoggerTypeSearcher?): List? { + if (searcher == null) return null + val arguments = node.valueArguments + val method = node.resolveToUElement() as? UMethod ?: return null + val parameters = method.uastParameters + val logStringArgument: UExpression? + if (parameters.isEmpty() || arguments.isEmpty()) { + logStringArgument = findMessageSetterStringArg(node, searcher) ?: return null + } + else { + val index = getLogStringIndex(parameters) ?: return null + logStringArgument = arguments[index - 1] + } + + return LoggingStringPartEvaluator.calculateValue(logStringArgument) + } + + private fun registerProblem(holder: ProblemsHolder, current: UCallExpression, other: UCallExpression) { + val anchor = current.sourcePsi ?: return + val otherElement = other.sourcePsi ?: return + val commonParent = PsiTreeUtil.findCommonParent(anchor, otherElement) ?: return + val textRange = anchor.textRange + val delta = commonParent.textRange?.startOffset ?: return + holder.registerProblem(commonParent, textRange.shiftLeft(delta), + JvmAnalysisBundle.message("jvm.inspection.logging.similar.message.problem.descriptor"), + NavigateToDuplicateFix(otherElement)) + } + +} + +private class NavigateToDuplicateFix(call: PsiElement) : ModCommandQuickFix() { + private val myPointer = SmartPointerManager.getInstance(call.project).createSmartPsiElementPointer(call) + + override fun getFamilyName(): @Nls String { + return JavaBundle.message("navigate.to.duplicate.fix") + } + + override fun perform(project: Project, descriptor: ProblemDescriptor): ModCommand { + val element = myPointer.element + if (element == null) return ModCommand.nop() + return ModCommand.select(element) + } +} + +private fun List?.splitWithPlaceholders(): List? { + if (this == null) return null + val result = mutableListOf() + for (partHolder in this) { + if (partHolder.isConstant && partHolder.text != null) { + val withoutPlaceholders = partHolder.text.split("{}") + for ((index, clearPart) in withoutPlaceholders.withIndex()) { + result.add(LoggingStringPartEvaluator.PartHolder(clearPart, true)) + if (index != withoutPlaceholders.lastIndex) { + result.add(LoggingStringPartEvaluator.PartHolder(clearPart, false)) + } + } + } + else { + result.add(partHolder) + } + } + return result +} + +private class PartHolderIterator(private val parts: List) { + private var i = 0 + private var partial: String? = null + fun hasNext(): Boolean { + return i < parts.size + } + + fun isText(): Boolean { + val partHolder = parts[i] + return partHolder.isConstant && partHolder.text != null + } + + fun move() { + i++ + partial = null + } + + fun current(): String? { + val local = partial + if (local != null) { + return local + } + val partHolder = parts[i] + return partHolder.text + } + + fun isFirst(): Boolean { + return i == 0 + } + + fun move(delta: Int) { + partial = current()?.substring(delta) + } + + fun isLast(): Boolean { + return i == parts.lastIndex + } + + fun previousUnknown(): Boolean { + return if (i == 0) { + false + } + else { + val partHolder = parts[i - 1] + !partHolder.isConstant || partHolder.text == null + } + } +} + +private data class MessageLog(val call: UCallExpression, val parts: List?) + +private fun similar(first: List?, + second: List?): Boolean { + if (first == null || second == null) return false + if (first.isEmpty() || second.isEmpty()) return false + if (first.size >= MAX_PART_COUNT || second.size >= MAX_PART_COUNT) return false + val firstIterator = PartHolderIterator(first) + val secondIterator = PartHolderIterator(second) + var intersection = 0 + while (firstIterator.hasNext() && secondIterator.hasNext()) { + if (!firstIterator.isText()) { + firstIterator.move() + continue + } + + if (!secondIterator.isText()) { + secondIterator.move() + continue + } + + if (firstIterator.current() == secondIterator.current()) { + intersection += firstIterator.current()?.length ?: 0 + firstIterator.move() + secondIterator.move() + continue + } + + if (firstIterator.isFirst() && secondIterator.isFirst()) { + if (firstIterator.current()?.startsWith(secondIterator.current() ?: "") == true) { + val delta = secondIterator.current()?.length ?: 0 + intersection += delta + secondIterator.move() + firstIterator.move(delta) + continue + } + + if (secondIterator.current()?.startsWith(firstIterator.current() ?: "") == true) { + val delta = firstIterator.current()?.length ?: 0 + intersection += delta + firstIterator.move() + secondIterator.move(delta) + continue + } + + return false + } + + if (firstIterator.isLast() && secondIterator.isLast()) { + if (firstIterator.current()?.endsWith(secondIterator.current() ?: "") == true) { + val delta = secondIterator.current()?.length ?: 0 + intersection += delta + firstIterator.move() + secondIterator.move() + continue + } + + if (secondIterator.current()?.endsWith(firstIterator.current() ?: "") == true) { + val delta = firstIterator.current()?.length ?: 0 + intersection += delta + firstIterator.move() + secondIterator.move() + continue + } + + return false + } + + //There can be not only included staff, but intersection. + //This intersection is skipped deliberately because it can be used in real logs + + if (secondIterator.previousUnknown()) { + var delta = firstIterator.current()?.indexOf(secondIterator.current() ?: "") ?: -1 + if (delta != -1) { + delta += secondIterator.current()?.length ?: 0 + intersection += delta + secondIterator.move() + firstIterator.move(delta) + continue + } + } + + if (firstIterator.previousUnknown()) { + var delta = secondIterator.current()?.indexOf(firstIterator.current() ?: "") ?: -1 + if (delta != -1) { + delta += firstIterator.current()?.length ?: 0 + intersection += delta + firstIterator.move() + secondIterator.move(delta) + continue + } + } + + return false + } + + return intersection >= MIN_TEXT_LENGTH +} \ No newline at end of file diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/logging/LoggingStringPartEvaluator.kt b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/logging/LoggingStringPartEvaluator.kt index de9a7f50edb1..6c46555d2499 100644 --- a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/logging/LoggingStringPartEvaluator.kt +++ b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/logging/LoggingStringPartEvaluator.kt @@ -22,7 +22,13 @@ internal class LoggingStringPartEvaluator { companion object { fun calculateValue(expression: UExpression): List? { if (!isString(expression)) return null - return tryJoin(recursiveCalculateValue(expression, Context(depth = 10, maxParts = 20))) + val sourcePsi = expression.sourcePsi ?: return null + val project = sourcePsi.project + return CachedValuesManager.getManager(project).getCachedValue(sourcePsi, CachedValueProvider { + return@CachedValueProvider CachedValueProvider.Result.create( + tryJoin(recursiveCalculateValue(sourcePsi.toUElementOfType(), Context(depth = 10, maxParts = 20))), + PsiModificationTracker.MODIFICATION_COUNT) + }) } private fun recursiveCalculateValue(expression: UExpression?, diff --git a/jvm/jvm-analysis-internal-testFramework/src/com/intellij/jvm/analysis/internal/testFramework/logging/LoggingSimilarMessageInspectionTestBase.kt b/jvm/jvm-analysis-internal-testFramework/src/com/intellij/jvm/analysis/internal/testFramework/logging/LoggingSimilarMessageInspectionTestBase.kt new file mode 100644 index 000000000000..1d2d91701fbb --- /dev/null +++ b/jvm/jvm-analysis-internal-testFramework/src/com/intellij/jvm/analysis/internal/testFramework/logging/LoggingSimilarMessageInspectionTestBase.kt @@ -0,0 +1,7 @@ +package com.intellij.jvm.analysis.internal.testFramework.logging + +import com.intellij.codeInspection.logging.LoggingSimilarMessageInspection + +abstract class LoggingSimilarMessageInspectionTestBase : LoggingInspectionTestBase() { + override val inspection = LoggingSimilarMessageInspection() +} \ No newline at end of file diff --git a/jvm/jvm-analysis-java-tests/testSrc/com/intellij/codeInspection/tests/java/logging/JavaLoggingSimilarMessageInspectionTest.kt b/jvm/jvm-analysis-java-tests/testSrc/com/intellij/codeInspection/tests/java/logging/JavaLoggingSimilarMessageInspectionTest.kt new file mode 100644 index 000000000000..a3bed0ac4305 --- /dev/null +++ b/jvm/jvm-analysis-java-tests/testSrc/com/intellij/codeInspection/tests/java/logging/JavaLoggingSimilarMessageInspectionTest.kt @@ -0,0 +1,501 @@ +package com.intellij.codeInspection.tests.java.logging + +import com.intellij.jvm.analysis.internal.testFramework.logging.LoggingSimilarMessageInspectionTestBase +import com.intellij.jvm.analysis.testFramework.JvmLanguage + +class JavaLoggingSimilarMessageInspectionTest : LoggingSimilarMessageInspectionTestBase() { + + fun `test equals log4j2`() { + myFixture.testHighlighting(JvmLanguage.JAVA, """ + import org.apache.logging.log4j.*; + class Logging { + private static final Logger LOG = LogManager.getLogger(); + + private static void request1(String i) { + String msg = "log messages: " + i; + LOG.info(msg); + } + + private static void request2(int i) { + String msg = "log messages: " + i; + LOG.info(msg); + } + } + """.trimIndent()) + } + + fun `test not completed log4j2`() { + myFixture.testHighlighting(JvmLanguage.JAVA, """ + import org.apache.logging.log4j.*; + class Logging { + private static final Logger LOG = LogManager.getLogger(); + + private static void request1(String i) { + String msg = "{}" + i; + LOG.info(msg); + } + + private static void request2(int i) { + String msg = "{}" + i; + LOG.info(msg); + } + } + """.trimIndent()) + } + + fun `test not completed 2 log4j2`() { + myFixture.testHighlighting(JvmLanguage.JAVA, """ + import org.apache.logging.log4j.*; + class Logging { + private static final Logger LOG = LogManager.getLogger(); + + private static void request1(String i) { + String msg = "{} {}"; + LOG.info(msg); + } + + private static void request2(int i) { + String msg = "{} {}"; + LOG.info(msg); + } + } + """.trimIndent()) + } + + fun `test many equals log4j2`() { + myFixture.testHighlighting(JvmLanguage.JAVA, """ + import org.apache.logging.log4j.*; + class Logging { + private static final Logger LOG = LogManager.getLogger(); + + private static void request1(String i) { + String msg = "log messages2: " + i; + LOG.info(msg); + } + + private static void request2(int i) { + String msg = "log messages: " + i; + LOG.info(msg); + } + + private static void request3(int i) { + String msg = "log messages2: " + i; + LOG.info(msg); + } + + private static void request4(int i) { + String msg = "log messages: " + i; + LOG.info(msg); + } + + private static void request5(int i) { + String msg = "log messages: " + i; + LOG.info(msg); + } + + private static void request6(int i) { + String msg = "log messages: " + i; + LOG.info(msg); + } + + private static void request7(int i) { + String msg = "log messages: " + i; + LOG.info(msg); + } + + private static void request8(int i) { + String msg = "log messages: " + i; + LOG.info(msg); + } + + private static void request9(int i) { + String msg = "log messages: " + i; + LOG.info(msg); + } + + private static void request10(int i) { + String msg = "log messages: " + i; + LOG.info(msg); + } + } + """.trimIndent()) + } + + fun `test equals slf4j`() { + myFixture.testHighlighting(JvmLanguage.JAVA, """ + import org.slf4j.*; + class Logging { + private static Logger LOG = LoggerFactory.getLogger(Logging.class); + + private static void request1(String i) { + String msg = "log messages: " + i; + LOG.info(msg); + } + + private static void request2(int i) { + String msg = "log messages: " + i; + LOG.info(msg); + } + } + """.trimIndent()) + } + + + fun `test setMessage equals slf4j`() { + myFixture.testHighlighting(JvmLanguage.JAVA, """ + import org.slf4j.*; + class Logging { + private static Logger LOG = LoggerFactory.getLogger(Logging.class); + + private static void request1(String i) { + String msg = "log messages: " + i; + LOG.info(msg); + } + + private static void request2(int i) { + String msg = "log messages: " + i; + LOG.info(msg); + } + } + """.trimIndent()) + } + + fun `test not equals level slf4j`() { + myFixture.testHighlighting(JvmLanguage.JAVA, """ + import org.slf4j.*; + import org.slf4j.spi.*; + class X { + + void foo() { + Logger logger = LoggerFactory.getLogger(X.class); + + logger.atError() + .setMessage("aaa {}") + .log(); + + logger.atError() + .setMessage("aaa 2{}") + .log(); + } + } + """.trimIndent()) + } + + fun `test not equals log4j2`() { + myFixture.testHighlighting(JvmLanguage.JAVA, """ + import org.apache.logging.log4j.*; + class Logging { + private static final Logger LOG = LogManager.getLogger(); + + private static void request1(String i) { + String msg = "log 2 messages: " + i; + LOG.info(msg); + } + + private static void request2(int i) { + String msg = "log messages: " + i; + LOG.info(msg); + } + } + """.trimIndent()) + } + + fun `test not equals end log4j2`() { + myFixture.testHighlighting(JvmLanguage.JAVA, """ + import org.apache.logging.log4j.*; + class Logging { + private static final Logger LOG = LogManager.getLogger(); + + private static void request1(String i) { + String msg = i + ": log 2 messages"; + LOG.info(msg); + } + + private static void request2(int i) { + String msg = i + ": log messages"; + LOG.info(msg); + } + } + """.trimIndent()) + } + + fun `test endWith log4j2`() { + myFixture.testHighlighting(JvmLanguage.JAVA, """ + import org.apache.logging.log4j.*; + class Logging { + private static final Logger LOG = LogManager.getLogger(); + + private static void request1(String i) { + String msg = i + "2: log messages"; + LOG.info(msg); + } + + private static void request2(int i) { + String msg = i + ": log messages"; + LOG.info(msg); + } + } + """.trimIndent()) + } + + fun `test endWith2 log4j2`() { + myFixture.testHighlighting(JvmLanguage.JAVA, """ + import org.apache.logging.log4j.*; + class Logging { + private static final Logger LOG = LogManager.getLogger(); + + private static void request1(String i) { + String msg = i + ": log messages"; + LOG.info(msg); + } + + private static void request2(int i) { + String msg = i + "2: log messages"; + LOG.info(msg); + } + } + """.trimIndent()) + } + + fun `test startWith log4j2`() { + myFixture.testHighlighting(JvmLanguage.JAVA, """ + import org.apache.logging.log4j.*; + class Logging { + private static final Logger LOG = LogManager.getLogger(); + + private static void request1(String i) { + String msg = "log messages" + i; + LOG.info(msg); + } + + private static void request2(int i) { + String msg = "log messages2" + i; + LOG.info(msg); + } + } + """.trimIndent()) + } + + fun `test startWith 2 log4j2`() { + myFixture.testHighlighting(JvmLanguage.JAVA, """ + import org.apache.logging.log4j.*; + class Logging { + private static final Logger LOG = LogManager.getLogger(); + + private static void request1(String i) { + String msg = "log messages2" + i; + LOG.info(msg); + } + + private static void request2(int i) { + String msg = "log messages" + i; + LOG.info(msg); + } + } + """.trimIndent()) + } + + fun `test equals end log4j2`() { + myFixture.testHighlighting(JvmLanguage.JAVA, """ + import org.apache.logging.log4j.*; + class Logging { + private static final Logger LOG = LogManager.getLogger(); + + private static void request1(String i) { + String msg = i + ": log messages"; + LOG.info(msg); + } + + private static void request2(int i) { + String msg = i + ": log messages"; + LOG.info(msg); + } + } + """.trimIndent()) + } + + fun `test many equals end log4j2 without highlighting`() { + myFixture.testHighlighting(JvmLanguage.JAVA, """ + import org.apache.logging.log4j.*; + class Logging { + private static final Logger LOG = LogManager.getLogger(); + + private static void request1(String i) { + String msg = i + ": log messages"; + LOG.info(msg); + } + + private static void request2(int i) { + String msg = i + ": log messages"; + LOG.info(msg); + } + private static void request3(int i) { + String msg = i + ": log messages"; + LOG.info(msg); + } + private static void request4(int i) { + String msg = i + ": log messages"; + LOG.info(msg); + } + private static void request5(int i) { + String msg = i + ": log messages"; + LOG.info(msg); + } + private static void request6(int i) { + String msg = i + ": log messages"; + LOG.info(msg); + } + private static void request7(int i) { + String msg = i + ": log messages"; + LOG.info(msg); + } + private static void request8(int i) { + String msg = i + ": log messages"; + LOG.info(msg); + } + private static void request9(int i) { + String msg = i + ": log messages"; + LOG.info(msg); + } + + private static void request10(int i) { + String msg = i + ": log messages"; + LOG.info(msg); + } + } + """.trimIndent()) + } + + fun `test equals middle log4j2`() { + myFixture.testHighlighting(JvmLanguage.JAVA, """ + import org.apache.logging.log4j.*; + class Logging { + private static final Logger LOG = LogManager.getLogger(); + + private static void request1(String i) { + String msg = i + ": log messages: " + i; + LOG.info(msg); + } + + private static void request2(int i) { + String msg = i + ": log messages: " + i; + LOG.info(msg); + } + } + """.trimIndent()) + } + + fun `test equals middle contains log4j2`() { + myFixture.testHighlighting(JvmLanguage.JAVA, """ + import org.apache.logging.log4j.*; + class Logging { + private static final Logger LOG = LogManager.getLogger(); + + private static void request1(String i) { + String msg = i + ": log messages: 2" + i; + LOG.info(msg); + } + + private static void request2(int i) { + String msg = i + ": log messages: " + i; + LOG.info(msg); + } + } + """.trimIndent()) + } + + fun `test equals middle contains 2 log4j2`() { + myFixture.testHighlighting(JvmLanguage.JAVA, """ + import org.apache.logging.log4j.*; + class Logging { + private static final Logger LOG = LogManager.getLogger(); + + private static void request1(String i) { + String msg = i + ": log messages: " + i; + LOG.info(msg); + } + + private static void request2(int i) { + String msg = i + ": log messages: 2" + i; + LOG.info(msg); + } + } + """.trimIndent()) + } + + fun `test equals middle contains 3 log4j2`() { + myFixture.testHighlighting(JvmLanguage.JAVA, """ + import org.apache.logging.log4j.*; + class Logging { + private static final Logger LOG = LogManager.getLogger(); + + private static void request1(String i) { + String msg = i + "2: log messages: " + i; + LOG.info(msg); + } + + private static void request2(int i) { + String msg = i + ": log messages:" + i; + LOG.info(msg); + } + } + """.trimIndent()) + } + + fun `test equals middle contains 4 log4j2`() { + myFixture.testHighlighting(JvmLanguage.JAVA, """ + import org.apache.logging.log4j.*; + class Logging { + private static final Logger LOG = LogManager.getLogger(); + + private static void request1(String i) { + String msg = i + ": log messages:" + i; + LOG.info(msg); + } + + private static void request2(int i) { + String msg = i + "2: log messages:" + i; + LOG.info(msg); + } + } + """.trimIndent()) + } + + fun `test not equals middle log4j2`() { + myFixture.testHighlighting(JvmLanguage.JAVA, """ + import org.apache.logging.log4j.*; + class Logging { + private static final Logger LOG = LogManager.getLogger(); + + private static void request1(String i) { + String msg = i + ": log messages: " + i; + LOG.info(msg); + } + + private static void request2(int i) { + String msg = i + ": log 2 messages: " + i; + LOG.info(msg); + } + } + """.trimIndent()) + } + + fun `test equals log4j2 with placeholders`() { + myFixture.testHighlighting(JvmLanguage.JAVA, """ + import org.apache.logging.log4j.*; + class Logging { + private static final Logger LOG = LogManager.getLogger(); + + private static void request1(String i) { + LOG.info("log messages: ", i); + } + + private static void request2(int i) { + String msg = "log messages: " + i; + LOG.info("log messages: ", i); + } + } + """.trimIndent()) + } +} + diff --git a/jvm/jvm-analysis-kotlin-tests/testSrc/com/intellij/codeInspection/tests/kotlin/logging/KotlinLoggingSimilarMessageInspectionTest.kt b/jvm/jvm-analysis-kotlin-tests/testSrc/com/intellij/codeInspection/tests/kotlin/logging/KotlinLoggingSimilarMessageInspectionTest.kt new file mode 100644 index 000000000000..f77889bc9945 --- /dev/null +++ b/jvm/jvm-analysis-kotlin-tests/testSrc/com/intellij/codeInspection/tests/kotlin/logging/KotlinLoggingSimilarMessageInspectionTest.kt @@ -0,0 +1,51 @@ +package com.intellij.codeInspection.tests.kotlin.logging + +import com.intellij.jvm.analysis.internal.testFramework.logging.LoggingSimilarMessageInspectionTestBase +import com.intellij.jvm.analysis.testFramework.JvmLanguage + +class KotlinLoggingSimilarMessageInspectionTest : LoggingSimilarMessageInspectionTestBase() { + + fun `test equals log4j2`() { + myFixture.testHighlighting(JvmLanguage.KOTLIN, """ + import org.apache.logging.log4j.LogManager + import org.apache.logging.log4j.Logger + + internal class Logging { + private val logger: Logger = LogManager.getLogger() + + private fun request1(i: String) { + val msg = "log messages: {}" + logger.info(msg, i) + } + + private fun request2(i: Int) { + val msg = "log messages: {}" + logger.info(msg, i) + } + } + """.trimIndent()) + } + + fun `test equals slf4j`() { + myFixture.testHighlighting(JvmLanguage.KOTLIN, """ + import org.slf4j.Logger + import org.slf4j.LoggerFactory + + internal class Logging { + private val LOG: Logger = LoggerFactory.getLogger(Logging::class.java) + + private fun request1(i: String) { + val msg = "log messages: {}" + LOG.info(msg, i) + LOG.info("1" + msg, i) + } + + private fun request2(i: Int) { + val msg = "log messages: {}" + LOG.info(msg, i) + } + } + """.trimIndent()) + } +} +