diff --git a/build.gradle.kts b/build.gradle.kts
index 049a59c..53d0bcc 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -12,13 +12,28 @@ recipeDependencies {
parserClasspath("org.slf4j:slf4j-api:2.+")
parserClasspath("log4j:log4j:1.+")
- parserClasspath("org.apache.logging.log4j:log4j-core:2.+")
- parserClasspath("org.apache.logging.log4j:log4j-api:2.+")
+ parserClasspath("org.apache.logging.log4j:log4j-core:2.24.3")
+ parserClasspath("org.apache.logging.log4j:log4j-api:2.24.3")
parserClasspath("commons-logging:commons-logging:1.+")
parserClasspath("ch.qos.logback:logback-classic:1.3.+")
parserClasspath("org.projectlombok:lombok:1.18.+")
}
+repositories {
+ mavenCentral()
+ mavenLocal()
+ // Temporarily add Apache Snapshot repository for Log4j artifacts
+ maven {
+ setUrl("https://repository.apache.org/snapshots")
+ mavenContent {
+ // Excessive 404 result codes in `repository.apache.org` will ban the worker IP
+ // https://infra.apache.org/infra-ban.html
+ snapshotsOnly()
+ excludeGroupAndSubgroups("org.openrewrite")
+ }
+ }
+}
+
dependencies {
compileOnly("org.projectlombok:lombok:latest.release")
annotationProcessor("org.projectlombok:lombok:latest.release")
@@ -27,13 +42,15 @@ dependencies {
implementation(platform("org.openrewrite:rewrite-bom:${rewriteVersion}"))
implementation("org.openrewrite:rewrite-java")
+ implementation("org.openrewrite:rewrite-maven")
implementation("org.openrewrite.recipe:rewrite-java-dependencies:${rewriteVersion}")
implementation("org.openrewrite.recipe:rewrite-static-analysis:${rewriteVersion}")
runtimeOnly("org.openrewrite:rewrite-java-17")
implementation("log4j:log4j:1.+")
- implementation("org.apache.logging.log4j:log4j-core:2.+")
+ implementation("org.apache.logging.log4j:log4j-core:2.24.3")
implementation("org.slf4j:slf4j-api:2.+")
+ implementation("org.apache.logging.log4j:log4j-converter-config:0.3.0-SNAPSHOT")
annotationProcessor("org.openrewrite:rewrite-templating:$rewriteVersion")
implementation("org.openrewrite:rewrite-templating:$rewriteVersion")
@@ -46,7 +63,7 @@ dependencies {
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:latest.release")
testImplementation("org.openrewrite:rewrite-kotlin:${rewriteVersion}")
- testImplementation("org.openrewrite:rewrite-maven")
+ testImplementation("org.openrewrite:rewrite-properties:${rewriteVersion}")
testImplementation("org.openrewrite:rewrite-test")
testImplementation("org.openrewrite:rewrite-java-tck")
diff --git a/src/main/java/org/openrewrite/java/logging/ConvertConfiguration.java b/src/main/java/org/openrewrite/java/logging/ConvertConfiguration.java
new file mode 100644
index 0000000..d5d19fd
--- /dev/null
+++ b/src/main/java/org/openrewrite/java/logging/ConvertConfiguration.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * Licensed under the Moderne Source Available License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://docs.moderne.io/licensing/moderne-source-available-license
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.openrewrite.java.logging;
+
+import lombok.AllArgsConstructor;
+import org.apache.logging.converter.config.ConfigurationConverter;
+import org.jspecify.annotations.Nullable;
+import org.openrewrite.*;
+import org.openrewrite.text.PlainText;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Converts a logging configuration file from one format to another.
+ */
+@AllArgsConstructor
+public class ConvertConfiguration extends Recipe {
+
+ @Option(displayName = "Pattern for the files to convert",
+ description = "If set, only the files that match this pattern will be converted.",
+ example = "**/log4j.properties",
+ required = false)
+ @Nullable
+ String filePattern;
+
+ @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")
+ String inputFormat;
+
+ @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")
+ String outputFormat;
+
+ private static final ConfigurationConverter converter = ConfigurationConverter.getInstance();
+
+ @Override
+ public String getDisplayName() {
+ return "Convert logging configuration";
+ }
+
+ @Override
+ public String getDescription() {
+ 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.";
+ }
+
+ @Override
+ public int maxCycles() {
+ return 1;
+ }
+
+ @Override
+ public TreeVisitor, ExecutionContext> getVisitor() {
+ return
+ Preconditions.check(new FindSourceFiles(filePattern),
+ new TreeVisitor() {
+
+ @Override
+ public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
+ if (tree instanceof SourceFile) {
+ SourceFile sourceFile = (SourceFile) tree;
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(sourceFile.printAllAsBytes());
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+ converter.convert(inputStream, inputFormat, outputStream, outputFormat);
+
+ String utf8 = new String(outputStream.toByteArray(), StandardCharsets.UTF_8);
+ if (tree instanceof PlainText) {
+ return ((PlainText) tree).withText(utf8).withCharset(StandardCharsets.UTF_8);
+ }
+ return PlainText.builder()
+ .id(sourceFile.getId())
+ .charsetBomMarked(sourceFile.isCharsetBomMarked())
+ .charsetName(StandardCharsets.UTF_8.name())
+ .checksum(sourceFile.getChecksum())
+ .fileAttributes(sourceFile.getFileAttributes())
+ .markers(sourceFile.getMarkers())
+ .sourcePath(sourceFile.getSourcePath())
+ .text(utf8)
+ .build();
+ }
+ return super.visit(tree, ctx);
+ }
+ });
+ }
+}
diff --git a/src/main/resources/META-INF/rewrite/log4j.yml b/src/main/resources/META-INF/rewrite/log4j.yml
index a673e0a..b8dcb96 100644
--- a/src/main/resources/META-INF/rewrite/log4j.yml
+++ b/src/main/resources/META-INF/rewrite/log4j.yml
@@ -17,8 +17,8 @@
---
type: specs.openrewrite.org/v1beta/recipe
name: org.openrewrite.java.logging.log4j.ParameterizedLogging
-displayName: Parameterize Log4j 2.x logging statements
-description: Use Log4j 2.x parameterized logging, which can significantly boost performance for messages that
+displayName: Parameterize Log4j API 2 logging statements
+description: Use Log4j API 2 parameterized logging, which can significantly boost performance for messages that
otherwise would be assembled with String concatenation. Particularly impactful when the log level is not enabled, as
no work is done to assemble the message.
tags:
@@ -57,8 +57,22 @@ recipeList:
---
type: specs.openrewrite.org/v1beta/recipe
name: org.openrewrite.java.logging.log4j.Log4j1ToLog4j2
-displayName: Migrate Log4j 1.x to Log4j 2.x
-description: Migrates Log4j 1.x to Log4j 2.x.
+displayName: Migrate Log4j 1 to Log4j API/Log4j Core 2
+description: Transforms code written using Log4j 1 to use Log4j API 2
+ and switches the logging backend from Log4j 1 to Log4j Core 2.
+tags:
+ - logging
+ - log4j
+recipeList:
+ - org.openrewrite.java.logging.log4j.Log4j1ToLog4jApi
+ - org.openrewrite.java.logging.log4j.Log4j1ToLog4jCore2
+---
+type: specs.openrewrite.org/v1beta/recipe
+name: org.openrewrite.java.logging.log4j.Log4j1ToLog4jApi
+displayName: Migrate Log4j 1 to Log4j API 2
+description: "Transforms code written using Log4j 1 to use Log4j API 2.
+ Should be used together with a recipe that switches the logging implementation to a more modern one:
+ e.g. JBoss LogManager, Log4j Core 2 or Logback."
tags:
- logging
- log4j
@@ -71,7 +85,6 @@ recipeList:
- org.openrewrite.java.ChangeMethodTargetToStatic:
methodPattern: org.apache.log4j.Logger getRootLogger()
fullyQualifiedTargetTypeName: org.apache.logging.log4j.LogManager
-
- org.openrewrite.java.logging.log4j.LoggerSetLevelToConfiguratorRecipe
- org.openrewrite.java.ChangeMethodName:
methodPattern: org.apache.log4j.Priority isGreaterOrEqual(org.apache.log4j.Priority)
@@ -80,7 +93,6 @@ recipeList:
- org.openrewrite.java.ChangeType:
oldFullyQualifiedTypeName: org.apache.log4j.Priority
newFullyQualifiedTypeName: org.apache.logging.log4j.Level
-
- org.openrewrite.java.ChangeMethodTargetToStatic:
methodPattern: org.apache.log4j.Category getInstance(Class)
fullyQualifiedTargetTypeName: org.apache.logging.log4j.LogManager
@@ -93,7 +105,6 @@ recipeList:
- org.openrewrite.java.ChangeType:
oldFullyQualifiedTypeName: org.apache.log4j.Category
newFullyQualifiedTypeName: org.apache.logging.log4j.Logger
-
- org.openrewrite.java.ChangePackage:
oldPackageName: org.apache.log4j
newPackageName: org.apache.logging.log4j
@@ -103,27 +114,41 @@ recipeList:
artifactId: log4j-api
version: 2.x
onlyIfUsing: org.apache.log4j.*
+---
+type: specs.openrewrite.org/v1beta/recipe
+name: org.openrewrite.java.logging.log4j.Log4j1ToLog4jCore2
+displayName: Migrate Log4j 1 to Log4j Core 2
+description: Replaces Log4j 1 as logging implementation with Log4j Core 2.
+ This recipe does not replace code occurrences of Log4j 1 and should be only used if Log4j 1 is not used
+ directly as logging API.
+recipeList:
+ # Guarantees alignment between Log4j API and Log4j Core version, regardless of the Maven conflict resolution
+ # algorithm used.
+ - org.openrewrite.maven.AddManagedDependency:
+ groupId: org.apache.logging.log4j
+ artifactId: log4j-bom
+ version: 2.x
+ type: pom
+ scope: import
- org.openrewrite.java.dependencies.AddDependency:
groupId: org.apache.logging.log4j
artifactId: log4j-core
version: 2.x
- onlyIfUsing: org.apache.log4j.*
+ scope: runtime
+ # Removes Log4j 1 and replacements
- org.openrewrite.java.dependencies.RemoveDependency:
groupId: log4j
artifactId: log4j
+ - org.openrewrite.java.dependencies.RemoveDependency:
+ groupId: org.apache.logging.log4j
+ artifactId: log4j-1.2-api
+ - org.openrewrite.java.dependencies.RemoveDependency:
+ groupId: org.slf4j
+ artifactId: log4j-over-slf4j
- org.openrewrite.java.dependencies.RemoveDependency:
groupId: ch.qos.reload4j
artifactId: reload4j
- - org.openrewrite.java.dependencies.AddDependency:
- groupId: org.apache.logging.log4j
- artifactId: log4j-api
- version: 2.x
- onlyIfUsing: org.apache.logging.log4j.*
- - org.openrewrite.java.dependencies.AddDependency:
- groupId: org.apache.logging.log4j
- artifactId: log4j-core
- version: 2.x
- onlyIfUsing: org.apache.logging.log4j.*
+ # Switch the target of SLF4J-to-X bridges
- org.openrewrite.java.dependencies.ChangeDependency:
oldGroupId: org.slf4j
oldArtifactId: slf4j-log4j12
@@ -136,8 +161,47 @@ recipeList:
newGroupId: org.apache.logging.log4j
newArtifactId: log4j-slf4j-impl
newVersion: 2.x
+ #
+ - org.openrewrite.java.dependencies.UpgradeDependencyVersion:
+ groupId: commons-logging
+ artifactId: commons-logging
+ newVersion: latest.release
- org.openrewrite.java.logging.log4j.UpgradeLog4J2DependencyVersion
-
+ - org.openrewrite.java.logging.log4j.Log4j1ConfigurationToLog4jCore2
+---
+type: specs.openrewrite.org/v1beta/recipe
+name: org.openrewrite.java.logging.log4j.Log4j1ConfigurationToLog4jCore2
+displayName: Migrate Log4j 1 configuration to Log4j Core 2 format
+description: Migrates Log4j 1 configuration files to the Log4j Core 2 XML format.
+recipeList:
+ - org.openrewrite.java.logging.log4j.V1PropertiesToV2Xml
+ - org.openrewrite.java.logging.log4j.V1XmlToV2Xml
+---
+type: specs.openrewrite.org/v1beta/recipe
+name: org.openrewrite.java.logging.log4j.V1PropertiesToV2Xml
+displayName: Migrate `log4j.properties` files to `log4j2.xml` format
+description: Migrates `log4j.properties` files to the `log4j2.xml` format.
+recipeList:
+ - org.openrewrite.java.logging.ConvertConfiguration:
+ filePattern: "**/log4j.properties"
+ inputFormat: "v1:properties"
+ outputFormat: "v2:xml"
+ - org.openrewrite.RenameFile:
+ fileMatcher: "**/log4j.properties"
+ fileName: "log4j2.xml"
+---
+type: specs.openrewrite.org/v1beta/recipe
+name: org.openrewrite.java.logging.log4j.V1XmlToV2Xml
+displayName: Migrate `log4j.xml` files to `log4j2.xml` format
+description: Migrates `log4j.xml` files to the `log4j2.xml` format.
+recipeList:
+ - org.openrewrite.java.logging.ConvertConfiguration:
+ filePattern: "**/log4j.xml"
+ inputFormat: "v1:xml"
+ outputFormat: "v2:xml"
+ - org.openrewrite.RenameFile:
+ fileMatcher: "**/log4j.xml"
+ fileName: "log4j2.xml"
---
type: specs.openrewrite.org/v1beta/recipe
name: org.openrewrite.java.logging.log4j.UpgradeLog4J2DependencyVersion
diff --git a/src/main/resources/META-INF/rewrite/logback.yml b/src/main/resources/META-INF/rewrite/logback.yml
index 36c2587..f661323 100644
--- a/src/main/resources/META-INF/rewrite/logback.yml
+++ b/src/main/resources/META-INF/rewrite/logback.yml
@@ -17,8 +17,10 @@
---
type: specs.openrewrite.org/v1beta/recipe
name: org.openrewrite.java.logging.logback.Log4jToLogback
-displayName: Migrate Log4j 2.x to Logback
-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.
+displayName: Migrate Log4j API/Log4j Core 2 to SLF4J/Logback
+description: |
+ Migrates usage of Apache Log4j API with Log4j Core 2 implementation to SLF4J with Logback implementation.
+ Note, this currently does not modify the Log4j Core 2 configuration files.
tags:
- logging
- log4j
diff --git a/src/test/java/org/openrewrite/java/logging/ConvertConfigurationTest.java b/src/test/java/org/openrewrite/java/logging/ConvertConfigurationTest.java
new file mode 100644
index 0000000..9788657
--- /dev/null
+++ b/src/test/java/org/openrewrite/java/logging/ConvertConfigurationTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * Licensed under the Moderne Source Available License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://docs.moderne.io/licensing/moderne-source-available-license
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.openrewrite.java.logging;
+
+import org.junit.jupiter.api.Test;
+import org.openrewrite.DocumentExample;
+import org.openrewrite.test.RewriteTest;
+
+import static org.openrewrite.test.SourceSpecs.text;
+
+class ConvertConfigurationTest implements RewriteTest {
+
+ @DocumentExample
+ @Test
+ void convertsLog4j1ToLog4j2Configuration() {
+ rewriteRun(spec -> spec.recipe(new ConvertConfiguration("file.txt", "v1:properties", "v2:xml")),
+ text(
+ """
+ # Console appender
+ log4j.appender.CONSOLE = org.apache.log4j.ConsoleAppender
+ log4j.appender.CONSOLE.Follow = true
+ log4j.appender.CONSOLE.Target = System.err
+ log4j.appender.CONSOLE.layout = org.apache.log4j.PatternLayout
+ log4j.appender.CONSOLE.layout.ConversionPattern = %d [%t] %-5p %c - %m%n%ex
+ # Rolling file appender
+ log4j.appender.ROLLING = org.apache.log4j.RollingFileAppender
+ log4j.appender.ROLLING.File = file.log
+ log4j.appender.ROLLING.MaxBackupIndex = 30
+ # Exactly 10 GiB
+ log4j.appender.ROLLING.MaxFileSize = 10737418240
+ log4j.appender.ROLLING.layout = org.apache.log4j.SimpleLayout
+
+ # Loggers
+ log4j.rootLogger = INFO, CONSOLE
+
+ log4j.logger.org.openrewrite = DEBUG, CONSOLE, ROLLING
+ log4j.additivity.org.openrewrite = false
+ """,
+ """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """));
+ }
+}
diff --git a/src/test/java/org/openrewrite/java/logging/log4j/Log4j1ToLog4j2Test.java b/src/test/java/org/openrewrite/java/logging/log4j/Log4j1ToLog4j2Test.java
index b398bef..6215fb7 100644
--- a/src/test/java/org/openrewrite/java/logging/log4j/Log4j1ToLog4j2Test.java
+++ b/src/test/java/org/openrewrite/java/logging/log4j/Log4j1ToLog4j2Test.java
@@ -26,9 +26,11 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.openrewrite.java.Assertions.*;
import static org.openrewrite.maven.Assertions.pomXml;
+import static org.openrewrite.properties.Assertions.properties;
class Log4j1ToLog4j2Test implements RewriteTest {
@@ -308,6 +310,17 @@ class Test {
com.mycompany.app
my-app
1
+
+
+
+ org.apache.logging.log4j
+ log4j-bom
+ 2.24.3
+ pom
+ import
+
+
+
org.apache.httpcomponents
@@ -321,13 +334,12 @@ class Test {
org.apache.logging.log4j
- log4j-core
- %1$s
+ log4j-slf4j-impl
org.apache.logging.log4j
- log4j-slf4j-impl
- %1$s
+ log4j-core
+ runtime
@@ -338,4 +350,28 @@ class Test {
)
);
}
+
+ @Test
+ void rewriteConfigurationFile() {
+ rewriteRun(mavenProject("project", srcMainResources(properties("""
+ log4j.appender.FILE = org.apache.log4j.FileAppender
+ log4j.appender.FILE.file = file.log
+ log4j.appender.FILE.layout = org.apache.log4j.SimpleLayout
+ log4j.rootLogger = INFO, FILE
+ """, """
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """, spec -> spec.path("log4j.properties").afterRecipe(s -> assertThat(s.getSourcePath()).hasFileName("log4j2.xml"))))));
+ }
}
diff --git a/src/test/java/org/openrewrite/java/logging/log4j/Slf4jToLog4jTest.java b/src/test/java/org/openrewrite/java/logging/log4j/Slf4jToLog4jTest.java
index 6fa1916..472b51b 100644
--- a/src/test/java/org/openrewrite/java/logging/log4j/Slf4jToLog4jTest.java
+++ b/src/test/java/org/openrewrite/java/logging/log4j/Slf4jToLog4jTest.java
@@ -54,11 +54,11 @@ void loggerUsage() {
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
-
+
class Test {
private static final Logger LOGGER = LoggerFactory.getLogger(Test.class);
private static final Marker MARKER = MarkerFactory.getMarker("MARKER");
-
+
public static void main(String[] args) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("logger message");
@@ -72,11 +72,11 @@ public static void main(String[] args) {
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
-
+
class Test {
private static final Logger LOGGER = LogManager.getLogger(Test.class);
private static final Marker MARKER = MarkerManager.getMarker("MARKER");
-
+
public static void main(String[] args) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("logger message");
@@ -96,7 +96,7 @@ void mdcConversion() {
java(
"""
import org.slf4j.MDC;
-
+
class Test {
void method() {
MDC.put("key", "value");
@@ -111,7 +111,7 @@ void method() {
"""
import org.apache.logging.log4j.CloseableThreadContext;
import org.apache.logging.log4j.ThreadContext;
-
+
class Test {
void method() {
ThreadContext.put("key", "value");
@@ -136,7 +136,7 @@ void logBuilder() {
import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.event.Level;
-
+
class Test {
void method(Logger logger, Marker marker, Throwable t) {
logger.atLevel(Level.INFO)
@@ -150,7 +150,7 @@ void method(Logger logger, Marker marker, Throwable t) {
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
-
+
class Test {
void method(Logger logger, Marker marker, Throwable t) {
logger.atLevel(Level.INFO)
@@ -222,7 +222,7 @@ void changeLombokLogAnnotation() {
java(
"""
import lombok.extern.slf4j.Slf4j;
-
+
@Slf4j
class Test {
void method() {
@@ -232,7 +232,7 @@ void method() {
""",
"""
import lombok.extern.log4j.Log4j2;
-
+
@Log4j2
class Test {
void method() {