Skip to content

Commit 367362b

Browse files
committed
Add Log4j 1 to Log4j 2 configuration file conversion
This PR uses the [Log4j Configuration Converter API](https://logging.staged.apache.org/log4j/transform/log4j-converter-config.html) to add a `Log4j1ConfigurationToLog4jCore2` rule, which converts `log4j.properties` and `log4j.xml` configuration files into the equivalent `log4j2.xml` configuration files. It also refactors the `Log4j1toLog4j2` recipe into three parts: - `Log4j1toLog4jAPI` performs the migration from the "Log4j 1 API" to Log4j API 2, as described in [Log4j 1 API migration](https://logging.apache.org/log4j/2.x/migrate-from-log4j1.html#api-migration). This recipe performs almost exclusively Java code changes and is usually not required in applications that use JCL or SLF4J as logging interface. - `Log4j1ConfigurationToLog4jCore2` migrates configuration files, as described in the [Log4j 1 Configuration file migration](https://logging.apache.org/log4j/2.x/migrate-from-log4j1.html#configuration-file-migration) section. - `Log4j1ToLog4jCore2` migrates the runtime dependencies to use Log4j Core 2 instead of Log4j 1, as described in [Log4j 1 Backend migration](https://logging.apache.org/log4j/2.x/migrate-from-log4j1.html#backend-migration) and also calls the previous recipe. This is probably the most useful recipe for users that no longer user Log4j 1 directly, but use it as logging implementation. Closes openrewrite#154. Related to apache/logging-log4j2#3220
1 parent 6a714a1 commit 367362b

File tree

7 files changed

+295
-28
lines changed

7 files changed

+295
-28
lines changed

build.gradle.kts

+10-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ recipeDependencies {
1919
parserClasspath("org.projectlombok:lombok:1.18.+")
2020
}
2121

22+
repositories {
23+
mavenCentral()
24+
mavenLocal()
25+
maven("https://repository.apache.org/snapshots")
26+
}
27+
2228
dependencies {
2329
compileOnly("org.projectlombok:lombok:latest.release")
2430
annotationProcessor("org.projectlombok:lombok:latest.release")
@@ -27,13 +33,15 @@ dependencies {
2733

2834
implementation(platform("org.openrewrite:rewrite-bom:${rewriteVersion}"))
2935
implementation("org.openrewrite:rewrite-java")
36+
implementation("org.openrewrite:rewrite-maven")
3037
implementation("org.openrewrite.recipe:rewrite-java-dependencies:${rewriteVersion}")
3138
implementation("org.openrewrite.recipe:rewrite-static-analysis:${rewriteVersion}")
3239
runtimeOnly("org.openrewrite:rewrite-java-17")
3340

3441
implementation("log4j:log4j:1.+")
35-
implementation("org.apache.logging.log4j:log4j-core:2.+")
42+
implementation("org.apache.logging.log4j:log4j-core:2.24.3")
3643
implementation("org.slf4j:slf4j-api:2.+")
44+
implementation("org.apache.logging.log4j:log4j-converter-config:0.3.0-SNAPSHOT")
3745

3846
annotationProcessor("org.openrewrite:rewrite-templating:$rewriteVersion")
3947
implementation("org.openrewrite:rewrite-templating:$rewriteVersion")
@@ -46,7 +54,7 @@ dependencies {
4654
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:latest.release")
4755

4856
testImplementation("org.openrewrite:rewrite-kotlin:${rewriteVersion}")
49-
testImplementation("org.openrewrite:rewrite-maven")
57+
testImplementation("org.openrewrite:rewrite-properties:${rewriteVersion}")
5058
testImplementation("org.openrewrite:rewrite-test")
5159
testImplementation("org.openrewrite:rewrite-java-tck")
5260

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package org.openrewrite.java.logging;
2+
3+
import java.io.ByteArrayInputStream;
4+
import java.io.ByteArrayOutputStream;
5+
import java.nio.charset.StandardCharsets;
6+
import lombok.AllArgsConstructor;
7+
import org.apache.logging.converter.config.ConfigurationConverter;
8+
import org.jspecify.annotations.Nullable;
9+
import org.openrewrite.ExecutionContext;
10+
import org.openrewrite.FindSourceFiles;
11+
import org.openrewrite.NlsRewrite.Description;
12+
import org.openrewrite.NlsRewrite.DisplayName;
13+
import org.openrewrite.Option;
14+
import org.openrewrite.Preconditions;
15+
import org.openrewrite.Recipe;
16+
import org.openrewrite.SourceFile;
17+
import org.openrewrite.Tree;
18+
import org.openrewrite.TreeVisitor;
19+
import org.openrewrite.text.PlainText;
20+
21+
/**
22+
* Converts a logging configuration file from one format to another.
23+
*/
24+
@AllArgsConstructor
25+
public class ConvertConfiguration extends Recipe {
26+
27+
@Option(displayName = "Pattern for the files to convert",
28+
description = "If set, only the files that match this pattern will be converted.",
29+
required = false)
30+
@Nullable
31+
String filePattern;
32+
33+
@Option(displayName = "Input format", description = "The id of the input logging configuration format. See [Log4j documentation](https://logging.staged.apache.org/log4j/transform/log4j-converter-config.html#formats) for a list of supported formats.", example = "v1:properties")
34+
String inputFormat;
35+
36+
@Option(displayName = "Output format", description = "The id of the output logging configuration format. See [Log4j documentation](https://logging.staged.apache.org/log4j/transform/log4j-converter-config.html#formats) for a list of supported formats.", example = "v2:xml")
37+
String outputFormat;
38+
39+
private static final ConfigurationConverter converter = ConfigurationConverter.getInstance();
40+
41+
@Override
42+
public @DisplayName String getDisplayName() {
43+
return "Convert logging configuration";
44+
}
45+
46+
@Override
47+
public @Description String getDescription() {
48+
return "Converts the configuration of a logging backend from one format to another. For example it can convert a Log4j 1 properties configuration file into a Log4j Core 2 XML configuration file.";
49+
}
50+
51+
@Override
52+
public int maxCycles() {
53+
return 1;
54+
}
55+
56+
@Override
57+
public TreeVisitor<?, ExecutionContext> getVisitor() {
58+
return
59+
Preconditions.check(new FindSourceFiles(filePattern),
60+
new TreeVisitor<Tree, ExecutionContext>() {
61+
62+
@Override
63+
public boolean isAcceptable(SourceFile sourceFile, ExecutionContext executionContext) {
64+
return super.isAcceptable(sourceFile, executionContext);
65+
}
66+
67+
@Override
68+
public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext executionContext) {
69+
if (tree instanceof SourceFile) {
70+
SourceFile sourceFile = (SourceFile) tree;
71+
ByteArrayInputStream inputStream = new ByteArrayInputStream(sourceFile.printAllAsBytes());
72+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
73+
74+
converter.convert(inputStream, inputFormat, outputStream, outputFormat);
75+
76+
String utf8 = new String(outputStream.toByteArray(), StandardCharsets.UTF_8);
77+
if (tree instanceof PlainText) {
78+
return ((PlainText) tree).withText(utf8).withCharset(StandardCharsets.UTF_8);
79+
}
80+
return PlainText.builder()
81+
.id(sourceFile.getId())
82+
.charsetBomMarked(sourceFile.isCharsetBomMarked())
83+
.charsetName(StandardCharsets.UTF_8.name())
84+
.checksum(sourceFile.getChecksum())
85+
.fileAttributes(sourceFile.getFileAttributes())
86+
.markers(sourceFile.getMarkers())
87+
.sourcePath(sourceFile.getSourcePath())
88+
.text(utf8)
89+
.build();
90+
}
91+
return super.visit(tree, executionContext);
92+
}
93+
});
94+
}
95+
}

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

+83-19
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
---
1818
type: specs.openrewrite.org/v1beta/recipe
1919
name: org.openrewrite.java.logging.log4j.ParameterizedLogging
20-
displayName: Parameterize Log4j 2.x logging statements
21-
description: Use Log4j 2.x parameterized logging, which can significantly boost performance for messages that
20+
displayName: Parameterize Log4j API 2 logging statements
21+
description: Use Log4j API 2 parameterized logging, which can significantly boost performance for messages that
2222
otherwise would be assembled with String concatenation. Particularly impactful when the log level is not enabled, as
2323
no work is done to assemble the message.
2424
tags:
@@ -57,8 +57,22 @@ recipeList:
5757
---
5858
type: specs.openrewrite.org/v1beta/recipe
5959
name: org.openrewrite.java.logging.log4j.Log4j1ToLog4j2
60-
displayName: Migrate Log4j 1.x to Log4j 2.x
61-
description: Migrates Log4j 1.x to Log4j 2.x.
60+
displayName: Migrate Log4j 1 to Log4j API/Log4j Core 2
61+
description: Transforms code written using Log4j 1 to use Log4j API 2
62+
and switches the logging backend from Log4j 1 to Log4j Core 2.
63+
tags:
64+
- logging
65+
- log4j
66+
recipeList:
67+
- org.openrewrite.java.logging.log4j.Log4j1ToLog4jApi
68+
- org.openrewrite.java.logging.log4j.Log4j1ToLog4jCore2
69+
---
70+
type: specs.openrewrite.org/v1beta/recipe
71+
name: org.openrewrite.java.logging.log4j.Log4j1ToLog4jApi
72+
displayName: Migrate Log4j 1 to Log4j API 2
73+
description: "Transforms code written using Log4j 1 to use Log4j API 2.
74+
Should be used together with a recipe that switches the logging implementation to a more modern one:
75+
e.g. JBoss LogManager, Log4j Core 2 or Logback."
6276
tags:
6377
- logging
6478
- log4j
@@ -71,7 +85,6 @@ recipeList:
7185
- org.openrewrite.java.ChangeMethodTargetToStatic:
7286
methodPattern: org.apache.log4j.Logger getRootLogger()
7387
fullyQualifiedTargetTypeName: org.apache.logging.log4j.LogManager
74-
7588
- org.openrewrite.java.logging.log4j.LoggerSetLevelToConfiguratorRecipe
7689
- org.openrewrite.java.ChangeMethodName:
7790
methodPattern: org.apache.log4j.Priority isGreaterOrEqual(org.apache.log4j.Priority)
@@ -80,7 +93,6 @@ recipeList:
8093
- org.openrewrite.java.ChangeType:
8194
oldFullyQualifiedTypeName: org.apache.log4j.Priority
8295
newFullyQualifiedTypeName: org.apache.logging.log4j.Level
83-
8496
- org.openrewrite.java.ChangeMethodTargetToStatic:
8597
methodPattern: org.apache.log4j.Category getInstance(Class)
8698
fullyQualifiedTargetTypeName: org.apache.logging.log4j.LogManager
@@ -93,7 +105,6 @@ recipeList:
93105
- org.openrewrite.java.ChangeType:
94106
oldFullyQualifiedTypeName: org.apache.log4j.Category
95107
newFullyQualifiedTypeName: org.apache.logging.log4j.Logger
96-
97108
- org.openrewrite.java.ChangePackage:
98109
oldPackageName: org.apache.log4j
99110
newPackageName: org.apache.logging.log4j
@@ -103,27 +114,41 @@ recipeList:
103114
artifactId: log4j-api
104115
version: 2.x
105116
onlyIfUsing: org.apache.log4j.*
117+
---
118+
type: specs.openrewrite.org/v1beta/recipe
119+
name: org.openrewrite.java.logging.log4j.Log4j1ToLog4jCore2
120+
displayName: Migrate Log4j 1 to Log4j Core 2
121+
description: Replaces Log4j 1 as logging implementation with Log4j Core 2.
122+
This recipe does not replace code occurrences of Log4j 1 and should be only used if Log4j 1 is not used
123+
directly as logging API.
124+
recipeList:
125+
# Guarantees alignment between Log4j API and Log4j Core version, regardless of the Maven conflict resolution
126+
# algorithm used.
127+
- org.openrewrite.maven.AddManagedDependency:
128+
groupId: org.apache.logging.log4j
129+
artifactId: log4j-bom
130+
version: 2.x
131+
type: pom
132+
scope: import
106133
- org.openrewrite.java.dependencies.AddDependency:
107134
groupId: org.apache.logging.log4j
108135
artifactId: log4j-core
109136
version: 2.x
110-
onlyIfUsing: org.apache.log4j.*
137+
scope: runtime
138+
# Removes Log4j 1 and replacements
111139
- org.openrewrite.java.dependencies.RemoveDependency:
112140
groupId: log4j
113141
artifactId: log4j
142+
- org.openrewrite.java.dependencies.RemoveDependency:
143+
groupId: org.apache.logging.log4j
144+
artifactId: log4j-1.2-api
145+
- org.openrewrite.java.dependencies.RemoveDependency:
146+
groupId: org.slf4j
147+
artifactId: log4j-over-slf4j
114148
- org.openrewrite.java.dependencies.RemoveDependency:
115149
groupId: ch.qos.reload4j
116150
artifactId: reload4j
117-
- org.openrewrite.java.dependencies.AddDependency:
118-
groupId: org.apache.logging.log4j
119-
artifactId: log4j-api
120-
version: 2.x
121-
onlyIfUsing: org.apache.logging.log4j.*
122-
- org.openrewrite.java.dependencies.AddDependency:
123-
groupId: org.apache.logging.log4j
124-
artifactId: log4j-core
125-
version: 2.x
126-
onlyIfUsing: org.apache.logging.log4j.*
151+
# Switch the target of SLF4J-to-X bridges
127152
- org.openrewrite.java.dependencies.ChangeDependency:
128153
oldGroupId: org.slf4j
129154
oldArtifactId: slf4j-log4j12
@@ -136,8 +161,47 @@ recipeList:
136161
newGroupId: org.apache.logging.log4j
137162
newArtifactId: log4j-slf4j-impl
138163
newVersion: 2.x
164+
#
165+
- org.openrewrite.java.dependencies.UpgradeDependencyVersion:
166+
groupId: commons-logging
167+
artifactId: commons-logging
168+
newVersion: latest.release
139169
- org.openrewrite.java.logging.log4j.UpgradeLog4J2DependencyVersion
140-
170+
- org.openrewrite.java.logging.log4j.Log4j1ConfigurationToLog4jCore2
171+
---
172+
type: specs.openrewrite.org/v1beta/recipe
173+
name: org.openrewrite.java.logging.log4j.Log4j1ConfigurationToLog4jCore2
174+
displayName: Migrate Log4j 1 configuration to Log4j Core 2 format
175+
description: Migrates Log4j 1 configuration files to the Log4j Core 2 XML format.
176+
recipeList:
177+
- org.openrewrite.java.logging.log4j.V1PropertiesToV2Xml
178+
- org.openrewrite.java.logging.log4j.V1XmlToV2Xml
179+
---
180+
type: specs.openrewrite.org/v1beta/recipe
181+
name: org.openrewrite.java.logging.log4j.V1PropertiesToV2Xml
182+
displayName: Migrate `log4j.properties` files to `log4j2.xml` format
183+
description: Migrates `log4j.properties` files to the `log4j2.xml` format.
184+
recipeList:
185+
- org.openrewrite.java.logging.ConvertConfiguration:
186+
filePattern: "**/log4j.properties"
187+
inputFormat: "v1:properties"
188+
outputFormat: "v2:xml"
189+
- org.openrewrite.RenameFile:
190+
fileMatcher: "**/log4j.properties"
191+
fileName: "log4j2.xml"
192+
---
193+
type: specs.openrewrite.org/v1beta/recipe
194+
name: org.openrewrite.java.logging.log4j.V1XmlToV2Xml
195+
displayName: Migrate `log4j.xml` files to `log4j2.xml` format
196+
description: Migrates `log4j.xml` files to the `log4j2.xml` format.
197+
recipeList:
198+
- org.openrewrite.java.logging.ConvertConfiguration:
199+
filePattern: "**/log4j.xml"
200+
inputFormat: "v1:xml"
201+
outputFormat: "v2:xml"
202+
- org.openrewrite.RenameFile:
203+
fileMatcher: "**/log4j.xml"
204+
fileName: "log4j2.xml"
141205
---
142206
type: specs.openrewrite.org/v1beta/recipe
143207
name: org.openrewrite.java.logging.log4j.UpgradeLog4J2DependencyVersion

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
---
1818
type: specs.openrewrite.org/v1beta/recipe
1919
name: org.openrewrite.java.logging.logback.Log4jToLogback
20-
displayName: Migrate Log4j 2.x to Logback
21-
description: Migrates usage of Apache Log4j 2.x to using `logback` as an SLF4J implementation directly. Note, this currently does not modify `log4j.properties` files.
20+
displayName: Migrate Log4j API/Log4j Core 2 to SLF4J/Logback
21+
description: |
22+
Migrates usage of Apache Log4j API with Log4j Core 2 implementation to SLF4J with Logback implementation.
23+
Note, this currently does not modify the Log4j Core 2 configuration files.
2224
tags:
2325
- logging
2426
- log4j
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package org.openrewrite.java.logging;
2+
3+
import static org.openrewrite.test.SourceSpecs.text;
4+
5+
import org.junit.jupiter.api.Test;
6+
import org.openrewrite.DocumentExample;
7+
import org.openrewrite.test.RewriteTest;
8+
9+
class ConvertConfigurationTest implements RewriteTest {
10+
11+
@DocumentExample
12+
@Test
13+
void convertsLog4j1ToLog4j2Configuration() {
14+
rewriteRun(spec -> spec.recipe(new ConvertConfiguration("file.txt", "v1:properties", "v2:xml")),
15+
text("""
16+
# Console appender
17+
log4j.appender.CONSOLE = org.apache.log4j.ConsoleAppender
18+
log4j.appender.CONSOLE.Follow = true
19+
log4j.appender.CONSOLE.Target = System.err
20+
log4j.appender.CONSOLE.layout = org.apache.log4j.PatternLayout
21+
log4j.appender.CONSOLE.layout.ConversionPattern = %d [%t] %-5p %c - %m%n%ex
22+
# Rolling file appender
23+
log4j.appender.ROLLING = org.apache.log4j.RollingFileAppender
24+
log4j.appender.ROLLING.File = file.log
25+
log4j.appender.ROLLING.MaxBackupIndex = 30
26+
# Exactly 10 GiB
27+
log4j.appender.ROLLING.MaxFileSize = 10737418240
28+
log4j.appender.ROLLING.layout = org.apache.log4j.SimpleLayout
29+
30+
# Loggers
31+
log4j.rootLogger = INFO, CONSOLE
32+
33+
log4j.logger.org.openrewrite = DEBUG, CONSOLE, ROLLING
34+
log4j.additivity.org.openrewrite = false
35+
""",
36+
"""
37+
<?xml version='1.0' encoding='UTF-8'?>
38+
<Configuration xmlns="https://logging.apache.org/xml/ns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-config-2.xsd">
39+
<Properties/>
40+
<Appenders>
41+
<RollingFile append="true" bufferSize="8192" bufferedIo="false" fileName="file.log" filePattern="file.log.%i" immediateFlush="true" name="ROLLING">
42+
<PatternLayout alwaysWriteExceptions="false" pattern="%p - %m%n"/>
43+
<SizeBasedTriggeringPolicy size="10.00 GB"/>
44+
<DefaultRolloverStrategy fileIndex="min" max="30"/>
45+
</RollingFile>
46+
<Console follow="true" immediateFlush="true" name="CONSOLE" target="SYSTEM_ERR">
47+
<PatternLayout pattern="%d [%t] %-5p %c - %m%n%ex"/>
48+
</Console>
49+
</Appenders>
50+
<Loggers>
51+
<Root level="INFO">
52+
<AppenderRef ref="CONSOLE"/>
53+
</Root>
54+
<Logger additivity="false" level="DEBUG" name="org.openrewrite">
55+
<AppenderRef ref="CONSOLE"/>
56+
<AppenderRef ref="ROLLING"/>
57+
</Logger>
58+
</Loggers>
59+
</Configuration>
60+
"""));
61+
}
62+
}

0 commit comments

Comments
 (0)