From 9d620bf5a10805e017e0cdd21cb848b1d1390733 Mon Sep 17 00:00:00 2001 From: rombirli Date: Fri, 16 Oct 2026 11:51:34 +0200 Subject: [PATCH 1/5] Restrict rule to main code --- .../org/sonar/l10n/java/rules/java/S9436.html | 10 ++++------ .../org/sonar/l10n/java/rules/java/S9436.json | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9436.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9436.html index 933283bf328..042ffb66c56 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9436.html +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9436.html @@ -12,17 +12,15 @@

Why is this an issue?

This rule raises an issue when a class declares more than one logger field, for SLF4J, Log4j, Log4j 2, Commons Logging and java.util.logging.

Exceptions

+

This rule targets production code only. A test class often declares one logger per class under test, so it can capture the output of each of them. +Those loggers belong to the classes being exercised, and keeping them does not split the logging of a production class.

A nested or inner class is a class of its own, so a logger it declares is not counted against the logger of the enclosing class. Only the fields declared directly in the same class body are counted together.

A non-static logger field declared next to a static one still raises an issue, because the class still ends up with two loggers, regardless of the modifiers. Rule {rule:java:S1312} covers the modifiers a logger field should have.

-

A non-final logger field that is not initialized where it is declared does not count. Such a field is a configurable property, assigned at startup -or injected from outside, often exposed through a getter and a setter. It is not a second category that the class gives itself, and it usually cannot -be removed without changing the API of the class. A final field is different: it can only be assigned in an initializer or a constructor -of the class, so it does count.

A logger whose name is derived from another logger of the same class, as in LoggerFactory.getLogger(LOG.getName() + ".config"), does -not count either. That call creates a child category, which inherits the level and the appenders configured for the parent, so the output of the class -is still governed as one unit and the problem described above does not arise. Reusing the name as it is, as in +not count. That call creates a child category, which inherits the level and the appenders configured for the parent, so the output of the class is +still governed as one unit and the problem described above does not arise. Reusing the name as it is, as in LoggerFactory.getLogger(LOG.getName()), builds the same category rather than a child one and is still reported.

How to fix it

Keep one logger and remove the other field, then point the calls that used the removed logger at the remaining one.

diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9436.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9436.json index 24d7e43c067..229b2d53018 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9436.json +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9436.json @@ -13,7 +13,7 @@ "defaultSeverity": "Major", "ruleSpecification": "RSPEC-9436", "sqKey": "S9436", - "scope": "All", + "scope": "Main", "quickfix": "infeasible", "code": { "impacts": { From e2bc1eb3e10c75b74d3a9e53d485375c061e5728 Mon Sep 17 00:00:00 2001 From: rombirli Date: Fri, 16 Oct 2026 11:59:47 +0200 Subject: [PATCH 2/5] Add failing reproducer --- .../src/main/java/checks/MoreThanOneLoggerCheckSample.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/java-checks-test-sources/default/src/main/java/checks/MoreThanOneLoggerCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/MoreThanOneLoggerCheckSample.java index 5bb356f9199..175c6ff1cb7 100644 --- a/java-checks-test-sources/default/src/main/java/checks/MoreThanOneLoggerCheckSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/MoreThanOneLoggerCheckSample.java @@ -171,8 +171,10 @@ interface InterfaceWithTwoLoggers { class ConfigurableLoggerProperty { - private static final Logger LOG = LoggerFactory.getLogger(ConfigurableLoggerProperty.class); // Compliant - private Logger contextLogger; // Compliant, not initialized here, it is a configurable property + private static final Logger LOG = LoggerFactory.getLogger(ConfigurableLoggerProperty.class); +// ^^^> {{Logger already declared here.}} + private Logger contextLogger; // Noncompliant {{Remove this logger and use the one already declared in this class.}} +// ^^^^^^^^^^^^^ Logger getContextLogger() { return contextLogger; From 80f3ccfa8962c81fa6856739a99e6e8e70d79206 Mon Sep 17 00:00:00 2001 From: rombirli Date: Fri, 16 Oct 2026 12:00:47 +0200 Subject: [PATCH 3/5] Report uninitialized logger fields --- .../org/sonar/java/checks/MoreThanOneLoggerCheck.java | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/MoreThanOneLoggerCheck.java b/java-checks/src/main/java/org/sonar/java/checks/MoreThanOneLoggerCheck.java index 37db248b05e..06ba8b1c962 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/MoreThanOneLoggerCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/MoreThanOneLoggerCheck.java @@ -72,20 +72,11 @@ private static List loggerFields(ClassTree classTree) { List loggerFields = classTree.members().stream() .filter(member -> member.is(Tree.Kind.VARIABLE)) .map(VariableTree.class::cast) - .filter(MoreThanOneLoggerCheck::isDeclaredLogger) + .filter(field -> isLoggerType(field.type().symbolType())) .toList(); return withoutDerivedLoggers(loggerFields); } - /** - * A non-final logger field without an initializer is a configurable property, assigned at startup - * or injected from outside, rather than a category the class gives itself. A final one has to be - * assigned in an initializer or a constructor, so it is a category of the class. - */ - private static boolean isDeclaredLogger(VariableTree field) { - return (field.initializer() != null || field.symbol().isFinal()) && isLoggerType(field.type().symbolType()); - } - /** * Drops the loggers whose name is derived from another logger of the same class, such as * {@code LoggerFactory.getLogger(LOG.getName() + ".config")}. Those create a child category, From dce70635bab6180cb8b891f468dc7392dddcd6f1 Mon Sep 17 00:00:00 2001 From: rombirli Date: Fri, 16 Oct 2026 12:22:53 +0200 Subject: [PATCH 4/5] Update ruling expectations --- .../test/resources/expected/eclipse-jetty/java-S9436.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 its/ruling/src/test/resources/expected/eclipse-jetty/java-S9436.json diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S9436.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S9436.json new file mode 100644 index 00000000000..9495cc42755 --- /dev/null +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S9436.json @@ -0,0 +1,5 @@ +{ +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java": [ +207 +] +} From 837c5878fb2f99dbfeee461e33c364c783380eb8 Mon Sep 17 00:00:00 2001 From: rombirli Date: Fri, 16 Oct 2026 13:13:46 +0200 Subject: [PATCH 5/5] Update ruling expectations --- .../expected/eclipse-jetty-similar-to-main/java-S9436.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 its/ruling/src/test/resources/expected/eclipse-jetty-similar-to-main/java-S9436.json diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty-similar-to-main/java-S9436.json b/its/ruling/src/test/resources/expected/eclipse-jetty-similar-to-main/java-S9436.json new file mode 100644 index 00000000000..9495cc42755 --- /dev/null +++ b/its/ruling/src/test/resources/expected/eclipse-jetty-similar-to-main/java-S9436.json @@ -0,0 +1,5 @@ +{ +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java": [ +207 +] +}