Skip to content

Commit 14d9d55

Browse files
jhl221123timtebeekgithub-actions[bot]
authored
Add recipe to change logger fields to private (#221)
* Add ChangeLoggersToPrivate recipe * Add a precondition on using any of the loggers * Cheap checks first, and use ListUtils to change modifiers * Include `ChangeLoggersToPrivate` with `Slf4jBestPractices` * Expect logger to be changed to private in Slf4jBestPracticesTest * Update src/main/java/org/openrewrite/java/logging/ChangeLoggersToPrivate.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --------- Co-authored-by: Tim te Beek <[email protected]> Co-authored-by: Tim te Beek <[email protected]> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent ccd147b commit 14d9d55

File tree

5 files changed

+343
-2
lines changed

5 files changed

+343
-2
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.logging;
17+
18+
import lombok.EqualsAndHashCode;
19+
import lombok.Value;
20+
import org.jspecify.annotations.Nullable;
21+
import org.openrewrite.*;
22+
import org.openrewrite.internal.ListUtils;
23+
import org.openrewrite.java.JavaIsoVisitor;
24+
import org.openrewrite.java.search.UsesType;
25+
import org.openrewrite.java.tree.J;
26+
import org.openrewrite.java.tree.JavaType;
27+
import org.openrewrite.java.tree.Space;
28+
import org.openrewrite.java.tree.TypeUtils;
29+
import org.openrewrite.marker.Markers;
30+
31+
import java.util.Arrays;
32+
import java.util.List;
33+
import java.util.Set;
34+
35+
import static java.util.Collections.emptyList;
36+
import static java.util.stream.Collectors.toSet;
37+
38+
@Value
39+
@EqualsAndHashCode(callSuper = false)
40+
public class ChangeLoggersToPrivate extends Recipe {
41+
42+
private static final Set<String> LOGGER_TYPES = Arrays.stream(LoggingFramework.values())
43+
.map(LoggingFramework::getLoggerType)
44+
.collect(toSet());
45+
46+
@Override
47+
public String getDisplayName() {
48+
return "Change logger fields to `private`";
49+
}
50+
51+
@Override
52+
public String getDescription() {
53+
return "Ensures that logger fields are declared as `private` to encapsulate logging mechanics within the class.";
54+
}
55+
56+
@Override
57+
public TreeVisitor<?, ExecutionContext> getVisitor() {
58+
return Preconditions.check(usesAnyLogger(), new JavaIsoVisitor<ExecutionContext>() {
59+
@Override
60+
public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) {
61+
J.VariableDeclarations mv = super.visitVariableDeclarations(multiVariable, ctx);
62+
if (mv.getTypeExpression() == null ||
63+
!isLoggerType(mv.getTypeExpression().getType()) ||
64+
mv.hasModifier(J.Modifier.Type.Private)) {
65+
return mv;
66+
}
67+
68+
Cursor parent = getCursor().getParentTreeCursor();
69+
if (!(parent.getValue() instanceof J.Block)) {
70+
return mv;
71+
}
72+
73+
parent = parent.getParentTreeCursor();
74+
if (!(parent.getValue() instanceof J.ClassDeclaration)) {
75+
return mv;
76+
}
77+
78+
J.ClassDeclaration classDeclaration = parent.getValue();
79+
if (classDeclaration.getKind() == J.ClassDeclaration.Kind.Type.Interface) {
80+
return mv;
81+
}
82+
83+
List<J.Modifier> mapped = ListUtils.map(mv.getModifiers(), mod -> {
84+
if (mod.getType() == J.Modifier.Type.Public ||
85+
mod.getType() == J.Modifier.Type.Protected ||
86+
mod.getType() == J.Modifier.Type.Private) {
87+
return mod.withType(J.Modifier.Type.Private);
88+
}
89+
return mod;
90+
});
91+
if (mapped == mv.getModifiers()) {
92+
mapped = ListUtils.insert(mapped, new J.Modifier(
93+
Tree.randomId(),
94+
Space.EMPTY,
95+
Markers.EMPTY,
96+
null,
97+
J.Modifier.Type.Private,
98+
emptyList()
99+
), 0);
100+
}
101+
return autoFormat(mv.withModifiers(mapped), mv.getTypeExpression(), ctx, getCursor().getParentTreeCursor());
102+
}
103+
104+
private boolean isLoggerType(@Nullable JavaType type) {
105+
JavaType.FullyQualified fqnType = TypeUtils.asFullyQualified(type);
106+
if (fqnType != null) {
107+
return LOGGER_TYPES.contains(fqnType.getFullyQualifiedName());
108+
}
109+
return false;
110+
}
111+
});
112+
}
113+
114+
private static TreeVisitor<?, ExecutionContext> usesAnyLogger() {
115+
UsesType[] usesTypes = new UsesType[LOGGER_TYPES.size()];
116+
int i = 0;
117+
for (String fqn : LOGGER_TYPES) {
118+
usesTypes[i++] = new UsesType<>(fqn, true);
119+
}
120+
return Preconditions.or(usesTypes);
121+
}
122+
}

src/main/resources/META-INF/rewrite/examples.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,27 @@ examples:
128128
language: java
129129
---
130130
type: specs.openrewrite.org/v1beta/example
131+
recipeName: org.openrewrite.java.logging.ChangeLoggersToPrivate
132+
examples:
133+
- description: ''
134+
sources:
135+
- before: |
136+
import org.slf4j.Logger;
137+
import org.slf4j.LoggerFactory;
138+
139+
class Test {
140+
public static final Logger LOGGER = LoggerFactory.getLogger(Test.class);
141+
}
142+
after: |
143+
import org.slf4j.Logger;
144+
import org.slf4j.LoggerFactory;
145+
146+
class Test {
147+
private static final Logger LOGGER = LoggerFactory.getLogger(Test.class);
148+
}
149+
language: java
150+
---
151+
type: specs.openrewrite.org/v1beta/example
131152
recipeName: org.openrewrite.java.logging.ParameterizedLogging
132153
examples:
133154
- description: ''

src/main/resources/META-INF/rewrite/slf4j.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ recipeList:
143143
- org.openrewrite.java.logging.slf4j.Slf4jLogShouldBeConstant
144144
- org.openrewrite.java.logging.slf4j.CompleteExceptionLogging
145145
- org.openrewrite.java.logging.CatchBlockLogLevel
146+
- org.openrewrite.java.logging.ChangeLoggersToPrivate
146147
- org.openrewrite.java.logging.slf4j.WrapExpensiveLogStatementsInConditionals
147148
---
148149
type: specs.openrewrite.org/v1beta/recipe
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.logging;
17+
18+
import org.junit.jupiter.api.Test;
19+
import org.openrewrite.DocumentExample;
20+
import org.openrewrite.test.RecipeSpec;
21+
import org.openrewrite.test.RewriteTest;
22+
23+
import static org.openrewrite.java.Assertions.java;
24+
25+
class ChangeLoggersToPrivateTest implements RewriteTest {
26+
27+
@Override
28+
public void defaults(RecipeSpec spec) {
29+
spec.recipe(new ChangeLoggersToPrivate());
30+
}
31+
32+
@DocumentExample
33+
@Test
34+
void changePublicSlf4jLoggerPrivate() {
35+
rewriteRun(
36+
//language=java
37+
java(
38+
"""
39+
import org.slf4j.Logger;
40+
import org.slf4j.LoggerFactory;
41+
42+
class Test {
43+
public static final Logger LOGGER = LoggerFactory.getLogger(Test.class);
44+
}
45+
""",
46+
"""
47+
import org.slf4j.Logger;
48+
import org.slf4j.LoggerFactory;
49+
50+
class Test {
51+
private static final Logger LOGGER = LoggerFactory.getLogger(Test.class);
52+
}
53+
"""
54+
)
55+
);
56+
}
57+
58+
@Test
59+
void changePublicLog4j2LoggerPrivate() {
60+
rewriteRun(
61+
//language=java
62+
java(
63+
"""
64+
import org.apache.logging.log4j.Logger;
65+
import org.apache.logging.log4j.LogManager;
66+
67+
class Test {
68+
public static final Logger LOGGER = LogManager.getLogger(Test.class);
69+
}
70+
""",
71+
"""
72+
import org.apache.logging.log4j.Logger;
73+
import org.apache.logging.log4j.LogManager;
74+
75+
class Test {
76+
private static final Logger LOGGER = LogManager.getLogger(Test.class);
77+
}
78+
"""
79+
)
80+
);
81+
}
82+
83+
@Test
84+
void changeProtectedLog4jLoggerPrivate() {
85+
rewriteRun(
86+
//language=java
87+
java(
88+
"""
89+
import org.apache.log4j.Logger;
90+
91+
class Test {
92+
protected Logger log = Logger.getLogger(Test.class);
93+
}
94+
""",
95+
"""
96+
import org.apache.log4j.Logger;
97+
98+
class Test {
99+
private Logger log = Logger.getLogger(Test.class);
100+
}
101+
"""
102+
)
103+
);
104+
}
105+
106+
@Test
107+
void changeDefaultJulLoggerPrivate() {
108+
rewriteRun(
109+
//language=java
110+
java(
111+
"""
112+
import java.util.logging.Logger;
113+
114+
class Test {
115+
static final Logger LOG = Logger.getLogger(Test.class.getName());
116+
}
117+
""",
118+
"""
119+
import java.util.logging.Logger;
120+
121+
class Test {
122+
private static final Logger LOG = Logger.getLogger(Test.class.getName());
123+
}
124+
"""
125+
)
126+
);
127+
}
128+
129+
@Test
130+
void keepExistingPrivateLogger() {
131+
rewriteRun(
132+
//language=java
133+
java(
134+
"""
135+
import org.slf4j.Logger;
136+
import org.slf4j.LoggerFactory;
137+
138+
class Test {
139+
private final Logger logger = LoggerFactory.getLogger(Test.class);
140+
}
141+
"""
142+
)
143+
);
144+
}
145+
146+
@Test
147+
void notALoggerField() {
148+
rewriteRun(
149+
//language=java
150+
java(
151+
"""
152+
class Test {
153+
public String name = "test";
154+
protected int count = 0;
155+
}
156+
"""
157+
)
158+
);
159+
}
160+
161+
@Test
162+
void loggerInInterfaceShouldNotChange() {
163+
rewriteRun(
164+
//language=java
165+
java(
166+
"""
167+
import org.slf4j.Logger;
168+
import org.slf4j.LoggerFactory;
169+
170+
interface Constants {
171+
Logger logger = LoggerFactory.getLogger(Constants.class);
172+
}
173+
"""
174+
)
175+
);
176+
}
177+
178+
@Test
179+
void localVariableLoggerShouldNotChange() {
180+
rewriteRun(
181+
//language=java
182+
java(
183+
"""
184+
import org.slf4j.Logger;
185+
import org.slf4j.LoggerFactory;
186+
187+
class Test {
188+
public void doSomething() {
189+
Logger localLog = LoggerFactory.getLogger(Test.class);
190+
localLog.info("Hello");
191+
}
192+
}
193+
"""
194+
)
195+
);
196+
}
197+
}

src/test/java/org/openrewrite/java/logging/slf4j/Slf4jBestPracticesTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ void test() {
6363
import org.slf4j.Logger;
6464
import org.slf4j.LoggerFactory;
6565
class Test {
66-
Logger logger = LoggerFactory.getLogger(Test.class);
66+
private Logger logger = LoggerFactory.getLogger(Test.class);
6767
void test() {
6868
Object obj1 = new Object();
6969
Object obj2 = new Object();
@@ -105,7 +105,7 @@ void test() {
105105
import org.slf4j.Logger;
106106
import org.slf4j.LoggerFactory;
107107
class Test {
108-
Logger logger = LoggerFactory.getLogger(Test.class);
108+
private Logger logger = LoggerFactory.getLogger(Test.class);
109109
void test() {
110110
try {
111111
throw new IllegalStateException("oops");

0 commit comments

Comments
 (0)