Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Log4j 1 to Log4j 2 configuration file conversion #202

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
19 changes: 18 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,21 @@ recipeDependencies {
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")
}
}
}

Comment on lines +22 to +36
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly best removed before a merge.

Suggested change
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")
Expand All @@ -27,12 +42,14 @@ 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("org.apache.logging.log4j:log4j-core:2.+")
implementation("org.slf4j:slf4j-api:2.+")
implementation("org.apache.logging.log4j:log4j-converter-config:0.3.0-SNAPSHOT")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed likely best to merge this once released; thanks for creating the recipe before then!

Suggested change
implementation("org.apache.logging.log4j:log4j-converter-config:0.3.0-SNAPSHOT")
implementation("org.apache.logging.log4j:log4j-converter-config:latest.release")


annotationProcessor("org.openrewrite:rewrite-templating:$rewriteVersion")
implementation("org.openrewrite:rewrite-templating:$rewriteVersion")
Expand All @@ -50,7 +67,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}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This rewrite-bom manages the version here, so we can leave that out already.

Suggested change
testImplementation("org.openrewrite:rewrite-properties:${rewriteVersion}")
testImplementation("org.openrewrite:rewrite-properties")

testImplementation("org.openrewrite:rewrite-test")
testImplementation("org.openrewrite:rewrite-java-tck")

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/openrewrite/java/logging/AddLogger.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public static AddLogger addLog4j2Logger(J.ClassDeclaration scope, String loggerN
.builder(getModifiers(scope) + " Logger #{} = LogManager.getLogger(#{}.class);")
.contextSensitive()
.imports("org.apache.logging.log4j.Logger", "org.apache.logging.log4j.LogManager")
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "log4j-api-2.23"))
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "log4j-api-2"))
.build()
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* 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
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* 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.quark.Quark;
import org.openrewrite.remote.Remote;
import org.openrewrite.text.PlainTextParser;
import org.openrewrite.tree.ParseError;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;

/**
* Converts a logging configuration file from one format to another.
*/
ppkarwasz marked this conversation as resolved.
Show resolved Hide resolved
ppkarwasz marked this conversation as resolved.
Show resolved Hide resolved
@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;

@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.";
}

private static final ConfigurationConverter converter = ConfigurationConverter.getInstance();

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new FindSourceFiles(filePattern), new TreeVisitor<Tree, ExecutionContext>() {
@Override
public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
if (tree instanceof ParseError || tree instanceof Quark || tree instanceof Remote) {
return tree;
}
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);

return PlainTextParser.convert(sourceFile)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the output files are actually XML; should we use the XML parser here to read the converted contents such that subsequent recipes can optionally target their contents as part of the same recipe run?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is exactly the main question I had for you. The log4j-converter-config artifact can convert to multiple formats (right now log4j2.xml, log4j2.json, log4j2.yaml and log4j2.properties are supported).

In the "Log4j 1 to Log4j Core 2" recipe I only use the XML output, but the ConvertConfiguration recipe could be used by users for other conversions. Should I make a switch here on outputFormat and use PlainText only if the format is not one of the recognized ones?

.withText(utf8)
.withCharset(StandardCharsets.UTF_8);
}
return super.visit(tree, ctx);
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public JavaTemplate getErrorTemplate(String message, ExecutionContext ctx) {
case Log4J2:
return JavaTemplate
.builder("#{any(org.apache.logging.log4j.Logger)}.error(" + message + ", #{any(java.lang.Throwable)})")
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "log4j-api-2.23"))
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "log4j-api-2"))
.build();
case COMMONS:
return JavaTemplate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ public JavaTemplate getErrorTemplateNoException(ExecutionContext ctx) {
case Log4J2:
return JavaTemplate
.builder("#{any(org.apache.logging.log4j.Logger)}.error(#{any(String)});")
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "log4j-api-2.23"))
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "log4j-api-2"))
.build();
case JUL:
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ private JavaTemplate getInfoTemplate(ExecutionContext ctx) {
return JavaTemplate
.builder("#{any(org.apache.logging.log4j.Logger)}." + levelOrDefault + "(#{any(String)})")
.javaParser(JavaParser.fromJavaVersion()
.classpathFromResources(ctx, "log4j-api-2.23"))
.classpathFromResources(ctx, "log4j-api-2"))
.build();
case JUL:
default:
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
102 changes: 83 additions & 19 deletions src/main/resources/META-INF/rewrite/log4j.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
6 changes: 4 additions & 2 deletions src/main/resources/META-INF/rewrite/logback.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading