├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── comment-pr.yml │ ├── publish.yml │ ├── receive-pr.yml │ ├── repository-backup.yml │ └── stale.yml ├── .gitignore ├── LICENSE.md ├── LICENSE ├── apache-license-v2.txt └── moderne-source-available-license.md ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle ├── licenseHeader.txt └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lombok.config ├── settings.gradle.kts ├── src ├── main │ ├── java │ │ └── org │ │ │ └── openrewrite │ │ │ └── java │ │ │ └── logging │ │ │ ├── AddLogger.java │ │ │ ├── CatchBlockLogLevel.java │ │ │ ├── ChangeLoggersToPrivate.java │ │ │ ├── ChangeLombokLogAnnotation.java │ │ │ ├── LoggingFramework.java │ │ │ ├── ParameterizedLogging.java │ │ │ ├── PrintStackTraceToLogError.java │ │ │ ├── SystemErrToLogging.java │ │ │ ├── SystemOutToLogging.java │ │ │ ├── SystemPrintToLogging.java │ │ │ ├── jul │ │ │ └── LoggerLevelArgumentToMethod.java │ │ │ ├── log4j │ │ │ ├── ConvertJulEntering.java │ │ │ ├── ConvertJulExiting.java │ │ │ ├── LoggerSetLevelToConfigurator.java │ │ │ ├── LoggingExceptionConcatenation.java │ │ │ ├── PrependRandomName.java │ │ │ └── package-info.java │ │ │ ├── logback │ │ │ ├── ConfigureLoggerLevel.java │ │ │ ├── Log4jAppenderToLogback.java │ │ │ ├── Log4jLayoutToLogback.java │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ └── slf4j │ │ │ ├── ChangeLogLevel.java │ │ │ ├── CompleteExceptionLogging.java │ │ │ ├── JulGetLoggerToLoggerFactory.java │ │ │ ├── JulIsLoggableToIsEnabled.java │ │ │ ├── JulLevelAllToTrace.java │ │ │ ├── JulParameterizedArguments.java │ │ │ ├── JulToSlf4jLambdaSupplier.java │ │ │ ├── JulToSlf4jLambdaSupplierWithThrowable.java │ │ │ ├── JulToSlf4jSimpleCallsWithThrowable.java │ │ │ ├── LoggersNamedForEnclosingClass.java │ │ │ ├── Slf4jLogShouldBeConstant.java │ │ │ ├── WrapExpensiveLogStatementsInConditionals.java │ │ │ └── package-info.java │ └── resources │ │ └── META-INF │ │ └── rewrite │ │ ├── category.yml │ │ ├── classpath.tsv.zip │ │ ├── examples.yml │ │ ├── log4j.yml │ │ ├── logback.yml │ │ └── slf4j.yml └── test │ └── java │ └── org │ └── openrewrite │ └── java │ └── logging │ ├── AddLoggerTest.java │ ├── CatchBlockLogLevelTest.java │ ├── ChangeLoggersToPrivateTest.java │ ├── ParameterizedLoggingTest.java │ ├── PrintStackTraceToLogErrorTest.java │ ├── SystemErrToLoggingTest.java │ ├── SystemOutToLoggingTest.java │ ├── jul │ └── LoggerLevelArgumentToMethodTest.java │ ├── log4j │ ├── CommonsLoggingToLog4jTest.java │ ├── ConvertJulEnteringTest.java │ ├── ConvertJulExitingTest.java │ ├── JulToLog4jTest.java │ ├── Log4j1ToLog4j2Test.java │ ├── LoggingExceptionConcatenationTest.java │ ├── PrependRandomNameTest.java │ └── Slf4jToLog4jTest.java │ ├── logback │ ├── ConfigureLoggerLevelTest.java │ ├── Log4jAppenderToLogbackTest.java │ └── Log4jLayoutToLogbackTest.java │ └── slf4j │ ├── ChangeLogLevelTest.java │ ├── CommonsLoggingToSlf4j1Test.java │ ├── CompleteExceptionLoggingTest.java │ ├── JulParameterizedArgumentsTest.java │ ├── JulToSlf4jTest.java │ ├── Log4j1ToSlf4j1Test.java │ ├── Log4j2ToSlf4j1Test.java │ ├── LoggersNamedForEnclosingClassTest.java │ ├── Slf4jBestPracticesTest.java │ ├── Slf4jLogShouldBeConstantTest.java │ └── WrapExpensiveLogStatementsInConditionalsTest.java └── suppressions.xml /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | insert_final_newline = true 5 | trim_trailing_whitespace = true 6 | 7 | [src/test*/java/**.java] 8 | indent_size = 4 9 | ij_continuation_indent_size = 2 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: github-actions 5 | directory: / 6 | schedule: 7 | interval: daily 8 | commit-message: 9 | prefix: "chore(ci)" 10 | - package-ecosystem: gradle 11 | directory: / 12 | schedule: 13 | interval: daily 14 | commit-message: 15 | prefix: "chore(ci)" 16 | open-pull-requests-limit: 0 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: ci 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | tags-ignore: 9 | - "*" 10 | pull_request: 11 | branches: 12 | - main 13 | workflow_dispatch: {} 14 | schedule: 15 | - cron: 0 18 * * * 16 | 17 | concurrency: 18 | group: ci-${{ github.ref }} 19 | cancel-in-progress: true 20 | 21 | jobs: 22 | build: 23 | uses: openrewrite/gh-automation/.github/workflows/ci-gradle.yml@main 24 | secrets: 25 | gradle_enterprise_access_key: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} 26 | ossrh_username: ${{ secrets.OSSRH_USERNAME }} 27 | ossrh_token: ${{ secrets.OSSRH_TOKEN }} 28 | ossrh_signing_key: ${{ secrets.OSSRH_SIGNING_KEY }} 29 | ossrh_signing_password: ${{ secrets.OSSRH_SIGNING_PASSWORD }} 30 | OPS_GITHUB_ACTIONS_WEBHOOK: ${{ secrets.OPS_GITHUB_ACTIONS_WEBHOOK }} 31 | -------------------------------------------------------------------------------- /.github/workflows/comment-pr.yml: -------------------------------------------------------------------------------- 1 | name: comment-pr 2 | 3 | # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#using-data-from-the-triggering-workflow 4 | on: 5 | workflow_run: 6 | workflows: ["receive-pr"] 7 | types: 8 | - completed 9 | 10 | # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ 11 | # Since this pull request has write permissions on the target repo, we should **NOT** execute any untrusted code. 12 | jobs: 13 | post-suggestions: 14 | if: ${{ github.event.workflow_run.conclusion == 'success' }} 15 | uses: openrewrite/gh-automation/.github/workflows/comment-pr.yml@main 16 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: publish 3 | 4 | on: 5 | push: 6 | tags: 7 | - v[0-9]+.[0-9]+.[0-9]+ 8 | - v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+ 9 | 10 | concurrency: 11 | group: publish-${{ github.ref }} 12 | cancel-in-progress: false 13 | 14 | jobs: 15 | release: 16 | uses: openrewrite/gh-automation/.github/workflows/publish-gradle.yml@main 17 | secrets: 18 | gradle_enterprise_access_key: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} 19 | ossrh_username: ${{ secrets.OSSRH_USERNAME }} 20 | ossrh_token: ${{ secrets.OSSRH_TOKEN }} 21 | ossrh_signing_key: ${{ secrets.OSSRH_SIGNING_KEY }} 22 | ossrh_signing_password: ${{ secrets.OSSRH_SIGNING_PASSWORD }} 23 | -------------------------------------------------------------------------------- /.github/workflows/receive-pr.yml: -------------------------------------------------------------------------------- 1 | name: receive-pr 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize] 6 | branches: 7 | - main 8 | 9 | concurrency: 10 | group: '${{ github.workflow }} @ ${{ github.ref }}' 11 | cancel-in-progress: true 12 | 13 | # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ 14 | # Since this pull request receives untrusted code, we should **NOT** have any secrets in the environment. 15 | jobs: 16 | upload-patch: 17 | uses: openrewrite/gh-automation/.github/workflows/receive-pr.yml@main -------------------------------------------------------------------------------- /.github/workflows/repository-backup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: repository-backup 3 | on: 4 | workflow_dispatch: {} 5 | schedule: 6 | - cron: 0 17 * * * 7 | 8 | concurrency: 9 | group: backup-${{ github.ref }} 10 | cancel-in-progress: false 11 | 12 | jobs: 13 | repository-backup: 14 | uses: openrewrite/gh-automation/.github/workflows/repository-backup.yml@main 15 | secrets: 16 | bucket_mirror_target: ${{ secrets.S3_GITHUB_REPOSITORY_BACKUPS_BUCKET_NAME }} 17 | bucket_access_key_id: ${{ secrets.S3_GITHUB_REPOSITORY_BACKUPS_ACCESS_KEY_ID }} 18 | bucket_secret_access_key: ${{ secrets.S3_GITHUB_REPOSITORY_BACKUPS_SECRET_ACCESS_KEY }} 19 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: stale 3 | 4 | on: 5 | workflow_dispatch: {} 6 | schedule: 7 | - cron: 36 4 * * 1 8 | 9 | jobs: 10 | build: 11 | uses: openrewrite/gh-automation/.github/workflows/stale.yml@main 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .gradle/ 3 | out/ 4 | .idea/ 5 | src/main/generated/ 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | LICENSE/moderne-source-available-license.md -------------------------------------------------------------------------------- /LICENSE/moderne-source-available-license.md: -------------------------------------------------------------------------------- 1 | # Moderne Source Available License 2 | 3 | ## Acceptance 4 | 5 | This Agreement sets forth the terms and conditions on which the Licensor makes available the Software. By installing, downloading, copying, accessing, using, creating derivative works of, or distributing any of the Software, You agree to all of the terms and conditions of this Agreement. 6 | If You are receiving the Software on behalf of an entity, sole proprietorship or other organization, You represent and warrant that You have the authority to agree to this Agreement on behalf of such organization. 7 | 8 | The Licensor reserves the right to update this Agreement from time to time. 9 | 10 | The terms below have the meanings set forth below for purposes of this Agreement: 11 | 12 | ## Definitions 13 | 14 | Agreement: this Moderne Source Available License Agreement. 15 | 16 | Control: ownership, directly or indirectly, of substantially all the assets of an entity, or the power to direct its management or policies by vote, contract, or otherwise. 17 | 18 | License: a license as described in the License or Patent paragraph below. 19 | 20 | Licensor: Moderne, Inc. on behalf of itself and its subsidiaries worldwide. 21 | 22 | Modify, Modified, or Modification: copy from or adapt all or part of the work in a fashion requiring copyright permission (including, without limitation, creating a derivative work via linking or otherwise) other than making an exact unlinked copy. The resulting work is called a Modified version of the earlier work or a Modification. 23 | 24 | Software: The Licensor's software components provided to You by the Licensor under this Agreement. 25 | 26 | Trademark: the trademarks, service marks, and any other similar rights. 27 | 28 | You: the recipient of the Software pursuant to this Agreement, including both (a) the individual agreeing to this Agreement and (b) if receiving the Software or agreeing on behalf of or representing an entity, sole proprietorship or other organization, that organization. 29 | 30 | Your Company: any legal entity, sole proprietorship, or other kind of organization that You work for or represent, plus all organizations that have Control over, are under the Control of, or are under common Control with that organization. 31 | 32 | Your Licenses: means all the Licenses granted to You under this Agreement. 33 | 34 | 35 | ## License 36 | 37 | The Licensor grants You a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license under the Licensor's copyrights to use, copy, distribute internally, and prepare derivative works of the Software, in each case subject to, and conditional on compliance with, the limitations and conditions below. 38 | 39 | ## Limitations 40 | 41 | You may not and will not make the functionality of the Software or a Modified version available to third parties as a service or distribute the Software or a Modified version in a manner that makes the functionality of the Software directly or indirectly available to third parties. 42 | 43 | Making the functionality of the Software or Modified version available to third parties includes, without limitation, enabling third parties to interact with the functionality of the Software or Modified version in distributed form or remotely through a computer network, offering a product or service, the value of which derives to any extent from the value of the Software or Modified version, or offering a product or service that accomplishes for users any purpose of the Software or Modified version. 44 | 45 | Examples of third parties that are prohibited under this limitation include but are not limited to: 46 | * Sourcegraph and Sourcegraph Batch Changes 47 | * Amazon Q Code Transformer 48 | * Broadcom Application Advisor 49 | 50 | You may not and will not alter, remove, or obscure any licensing, copyright, or other notices of the Licensor in the Software. Any use of the Licensor's Trademarks is subject to applicable law (but no license is granted hereunder with respect to the Licensor's Trademarks). 51 | 52 | ## Patents 53 | 54 | The Licensor grants You a license, under any of the Licensor's patent claims covering the Software, to make, have made, use, import and have imported the Software, in each case subject to, and conditional upon compliance with, the limitations and conditions in this Agreement. This license does not cover any patent claims that You cause to be infringed by Modifications or additions to the Software. If You or Your Company make any claim that the Software (or any Modification owned by or available from the Licensor) infringes or contributes to infringement of any patent, the patent license granted under this Agreement ends immediately. 55 | 56 | ## Notices 57 | 58 | You must ensure that anyone who gets a copy of any part of the Software or a Modification from You also gets a copy of the terms and conditions in this Agreement. 59 | 60 | If You modify the Software, You must include in any Modified copies of the Software prominent notices stating that You have Modified the Software. 61 | 62 | ## No Other Rights 63 | 64 | The terms and conditions of this Agreement do not imply any licenses other than those expressly granted in this Agreement. 65 | 66 | ## Termination 67 | 68 | If You violate this Agreement or if You engage in any activity regarding the Software beyond or in conflict with the terms of this Agreement, such activity is not licensed, and Your Licenses will automatically terminate. If the Licensor provides You with a notice of your violation and gives you an option to cure, and You cease and cure all violations of this License no later than 30 days after You receive that notice, Your Licenses will be reinstated retroactively. However, if You violate this Agreement after such reinstatement, any additional violation of this Agreement will cause your Licenses to terminate automatically and permanently. 69 | 70 | ## No Liability 71 | 72 | **_AS FAR AS THE LAW ALLOWS, THE SOFTWARE COMES :undefined:AS IS,:undefined: WITHOUT ANY WARRANTY, INCLUDING, WITHOUT LIMITATION, ANY WARRANTY WHATSOEVER, INCLUDING WITHOUT LIMITATION ANY WARRANTY OF MERCHANTABILITY, FITNESS FOR ANY PARTICULAR PURPOSE, TITLE OR NON-INFRINGEMENT. FURTHERMORE THE LICENSOR WILL NOT BE LIABLE TO YOU FOR ANY INCIDENTAL OR CONSEQUENTIAL DAMAGES (OR, TO THE EXTENT LEGALLY PERMISSIBLE, ANY OTHER DAMAGES) ARISING OUT OF THIS AGREEMENT OR IN CONNECTION WITH THE SOFTWARE, UNDER ANY KIND OF LEGAL CLAIM (INCLUDING, WITHOUT LIMITATION, CONTRACT, TORT OR NEGLIGENCE)._** 73 | 74 | ## Governing Law and Jurisdiction 75 | 76 | This Agreement will be construed and enforced in all respects in accordance with the laws of the State of Delaware, U.S.A., without reference to its choice of law rules. The courts located in Delaware have exclusive jurisdiction for all purposes relating to this Agreement, provided that the Licensor may apply to any court of competent jurisdiction for injunctive relief. 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 | 6 | OpenRewrite Logo 7 | 8 | 9 |

10 | 11 |
12 |

rewrite-logging-frameworks

13 |
14 | 15 |
16 | 17 | 18 | [![ci](https://github.com/openrewrite/rewrite-logging-frameworks/actions/workflows/ci.yml/badge.svg)](https://github.com/openrewrite/rewrite-logging-frameworks/actions/workflows/ci.yml) 19 | [![Maven Central](https://img.shields.io/maven-central/v/org.openrewrite.recipe/rewrite-logging-frameworks.svg)](https://mvnrepository.com/artifact/org.openrewrite.recipe/rewrite-logging-frameworks) 20 | [![Revved up by Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.openrewrite.org/scans) 21 | [![Contributing Guide](https://img.shields.io/badge/Contributing-Guide-informational)](https://github.com/openrewrite/.github/blob/main/CONTRIBUTING.md) 22 |
23 | 24 | ### What is this? 25 | 26 | This project implements a [Rewrite module](https://github.com/openrewrite/rewrite) that performs common Java logging framework tasks, like migrating from Log4j, Apache Commons Logging, and JUL, to SLF4J. 27 | 28 | Browse [a selection of recipes available through this module in the recipe catalog](https://docs.openrewrite.org/recipes/java/logging). 29 | 30 | ## Contributing 31 | 32 | We appreciate all types of contributions. See the [contributing guide](https://github.com/openrewrite/.github/blob/main/CONTRIBUTING.md) for detailed instructions on how to get started. 33 | 34 | ### Licensing 35 | 36 | For more information about licensing, please visit our [licensing page](https://docs.openrewrite.org/licensing/openrewrite-licensing). 37 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.openrewrite.build.recipe-library") version "latest.release" 3 | id("org.openrewrite.build.moderne-source-available-license") version "latest.release" 4 | } 5 | 6 | group = "org.openrewrite.recipe" 7 | description = "Enforce logging best practices and migrate between logging frameworks. Automatically." 8 | 9 | val rewriteVersion = rewriteRecipe.rewriteVersion.get() 10 | 11 | recipeDependencies { 12 | parserClasspath("org.slf4j:slf4j-api:2.+") 13 | parserClasspath("log4j:log4j:1.+") 14 | parserClasspath("org.apache.logging.log4j:log4j-core:2.+") 15 | parserClasspath("org.apache.logging.log4j:log4j-api:2.+") 16 | parserClasspath("commons-logging:commons-logging:1.+") 17 | parserClasspath("ch.qos.logback:logback-classic:1.3.+") 18 | parserClasspath("org.projectlombok:lombok:1.18.+") 19 | } 20 | 21 | dependencies { 22 | compileOnly("org.projectlombok:lombok:latest.release") 23 | annotationProcessor("org.projectlombok:lombok:latest.release") 24 | 25 | implementation("org.kohsuke:wordnet-random-name:latest.release") 26 | 27 | implementation(platform("org.openrewrite:rewrite-bom:${rewriteVersion}")) 28 | implementation("org.openrewrite:rewrite-java") 29 | implementation("org.openrewrite:rewrite-xml") 30 | implementation("org.openrewrite.recipe:rewrite-java-dependencies:${rewriteVersion}") 31 | implementation("org.openrewrite.recipe:rewrite-static-analysis:${rewriteVersion}") 32 | runtimeOnly("org.openrewrite:rewrite-java-17") 33 | 34 | implementation("org.apache.logging.log4j:log4j-core:2.+") 35 | implementation("org.slf4j:slf4j-api:2.+") 36 | 37 | annotationProcessor("org.openrewrite:rewrite-templating:$rewriteVersion") 38 | implementation("org.openrewrite:rewrite-templating:$rewriteVersion") 39 | compileOnly("com.google.errorprone:error_prone_core:2.+") { 40 | exclude("com.google.auto.service", "auto-service-annotations") 41 | exclude("io.github.eisop","dataflow-errorprone") 42 | } 43 | 44 | compileOnly("log4j:log4j:1.+") { 45 | because("log4j 1 has critical vulnerabilities but we need the type for the refaster recipe during compilation") 46 | } 47 | testRuntimeOnly("log4j:log4j:1.+") // Necessary to match for now; explore alternatives for Refaster classpath in the future 48 | testRuntimeOnly("ch.qos.logback:logback-classic:1.3.+") 49 | 50 | testImplementation("org.junit.jupiter:junit-jupiter-api:latest.release") 51 | testImplementation("org.junit.jupiter:junit-jupiter-params:latest.release") 52 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:latest.release") 53 | 54 | testImplementation("org.openrewrite:rewrite-kotlin") 55 | testImplementation("org.openrewrite:rewrite-maven") 56 | testImplementation("org.openrewrite:rewrite-test") 57 | 58 | testImplementation("org.assertj:assertj-core:latest.release") 59 | } 60 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.caching=true 2 | org.gradle.parallel=true 3 | kotlin.stdlib.default.dependency=false 4 | -------------------------------------------------------------------------------- /gradle/licenseHeader.txt: -------------------------------------------------------------------------------- 1 | Copyright ${year} the original author or authors. 2 |

3 | Licensed under the Moderne Source Available License (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 |

7 | https://docs.moderne.io/licensing/moderne-source-available-license 8 |

9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openrewrite/rewrite-logging-frameworks/1e14228375c9b918d5f82da7737d574b8eb111d7/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionSha256Sum=7197a12f450794931532469d4ff21a59ea2c1cd59a3ec3f89c035c3c420a6999 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /lombok.config: -------------------------------------------------------------------------------- 1 | # https://projectlombok.org/features/configuration 2 | config.stopBubbling = true 3 | lombok.addNullAnnotations = CUSTOM:org.openrewrite.internal.lang.NonNull:org.openrewrite.internal.lang.Nullable 4 | lombok.copyableAnnotations += org.openrewrite.internal.lang.Nullable 5 | lombok.copyableAnnotations += org.openrewrite.internal.lang.NonNull 6 | lombok.anyConstructor.addConstructorProperties=true 7 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenLocal() 4 | gradlePluginPortal() 5 | } 6 | } 7 | 8 | rootProject.name = "rewrite-logging-frameworks" 9 | 10 | plugins { 11 | id("com.gradle.develocity") version "latest.release" 12 | id("com.gradle.common-custom-user-data-gradle-plugin") version "latest.release" 13 | } 14 | 15 | develocity { 16 | server = "https://ge.openrewrite.org/" 17 | 18 | val isCiServer = System.getenv("CI")?.equals("true") ?: false 19 | val accessKey = System.getenv("GRADLE_ENTERPRISE_ACCESS_KEY") 20 | val authenticated = !accessKey.isNullOrBlank() 21 | buildCache { 22 | remote(develocity.buildCache) { 23 | isEnabled = true 24 | isPush = isCiServer && authenticated 25 | } 26 | } 27 | 28 | buildScan { 29 | capture { 30 | fileFingerprints = true 31 | } 32 | publishing { 33 | onlyIf { 34 | authenticated 35 | } 36 | } 37 | uploadInBackground = !isCiServer 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/AddLogger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.openrewrite.ExecutionContext; 19 | import org.openrewrite.TreeVisitor; 20 | import org.openrewrite.internal.ListUtils; 21 | import org.openrewrite.java.JavaIsoVisitor; 22 | import org.openrewrite.java.JavaParser; 23 | import org.openrewrite.java.JavaTemplate; 24 | import org.openrewrite.java.JavaVisitor; 25 | import org.openrewrite.java.format.AutoFormatVisitor; 26 | import org.openrewrite.java.search.FindFieldsOfType; 27 | import org.openrewrite.java.search.FindInheritedFields; 28 | import org.openrewrite.java.tree.J; 29 | import org.openrewrite.java.tree.Statement; 30 | 31 | import java.util.Comparator; 32 | import java.util.function.Function; 33 | 34 | import static java.util.Objects.requireNonNull; 35 | 36 | /** 37 | * @author Edward Harman 38 | */ 39 | public class AddLogger extends JavaIsoVisitor { 40 | private final J.ClassDeclaration scope; 41 | private final String loggerType; 42 | private final String factoryType; 43 | private final String loggerName; 44 | private final JavaTemplate template; 45 | 46 | public AddLogger(J.ClassDeclaration scope, String loggerType, String factoryType, String loggerName, Function, JavaTemplate> function) { 47 | this.scope = scope; 48 | this.loggerType = loggerType; 49 | this.factoryType = factoryType; 50 | this.loggerName = loggerName; 51 | this.template = function.apply(this); 52 | } 53 | 54 | public static TreeVisitor addLogger(J.ClassDeclaration scope, LoggingFramework loggingFramework, String loggerName, ExecutionContext ctx) { 55 | switch (loggingFramework) { 56 | case Log4J1: 57 | return addLog4j1Logger(scope, loggerName, ctx); 58 | case Log4J2: 59 | return addLog4j2Logger(scope, loggerName, ctx); 60 | case JUL: 61 | return addJulLogger(scope, loggerName, ctx); 62 | case SLF4J: 63 | default: 64 | return addSlf4jLogger(scope, loggerName, ctx); 65 | } 66 | } 67 | 68 | public static AddLogger addSlf4jLogger(J.ClassDeclaration scope, String loggerName, ExecutionContext ctx) { 69 | return new AddLogger(scope, "org.slf4j.Logger", "org.slf4j.LoggerFactory", loggerName, visitor -> 70 | JavaTemplate 71 | .builder(getModifiers(scope) + " Logger #{} = LoggerFactory.getLogger(#{}.class);") 72 | .contextSensitive() 73 | .imports("org.slf4j.Logger", "org.slf4j.LoggerFactory") 74 | .javaParser(JavaParser.fromJavaVersion() 75 | .classpathFromResources(ctx, "slf4j-api-2.1.+")) 76 | .build() 77 | ); 78 | } 79 | 80 | public static AddLogger addJulLogger(J.ClassDeclaration scope, String loggerName, @SuppressWarnings("unused") ExecutionContext ctx) { 81 | return new AddLogger(scope, "java.util.logging.Logger", "java.util.logging.LogManager", loggerName, visitor -> 82 | JavaTemplate 83 | .builder(getModifiers(scope) + " Logger #{} = LogManager.getLogManager().getLogger(\"#{}\");") 84 | .contextSensitive() 85 | .imports("java.util.logging.Logger", "java.util.logging.LogManager") 86 | .build() 87 | ); 88 | } 89 | 90 | public static AddLogger addLog4j1Logger(J.ClassDeclaration scope, String loggerName, ExecutionContext ctx) { 91 | return new AddLogger(scope, "org.apache.log4j.Logger", "org.apache.log4j.LogManager", loggerName, visitor -> 92 | JavaTemplate 93 | .builder(getModifiers(scope) + " Logger #{} = LogManager.getLogger(#{}.class);") 94 | .contextSensitive() 95 | .imports("org.apache.log4j.Logger", "org.apache.log4j.LogManager") 96 | .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "log4j-1.2.+")) 97 | .build() 98 | ); 99 | } 100 | 101 | public static AddLogger addLog4j2Logger(J.ClassDeclaration scope, String loggerName, ExecutionContext ctx) { 102 | return new AddLogger(scope, "org.apache.logging.log4j.Logger", "org.apache.logging.log4j.LogManager", loggerName, visitor -> 103 | JavaTemplate 104 | .builder(getModifiers(scope) + " Logger #{} = LogManager.getLogger(#{}.class);") 105 | .contextSensitive() 106 | .imports("org.apache.logging.log4j.Logger", "org.apache.logging.log4j.LogManager") 107 | .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "log4j-api-2.+")) 108 | .build() 109 | ); 110 | } 111 | 112 | private static String getModifiers(J.ClassDeclaration scope) { 113 | boolean innerClass = scope.getType() != null && scope.getType().getOwningClass() != null; 114 | return innerClass && !scope.hasModifier(J.Modifier.Type.Static) ? "private final" : "private static final"; 115 | } 116 | 117 | @Override 118 | public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { 119 | J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx); 120 | 121 | if (cd == scope) { 122 | if (!FindInheritedFields.find(cd, loggerType).isEmpty() || !FindFieldsOfType.find(cd, loggerType).isEmpty()) { 123 | return cd; 124 | } 125 | 126 | //noinspection ComparatorMethodParameterNotUsed 127 | Comparator firstAfterEnumValueSet = (unused, o2) -> o2 instanceof J.EnumValueSet ? 1 : -1; 128 | cd = template.apply(updateCursor(cd), cd.getBody().getCoordinates().addStatement(firstAfterEnumValueSet), loggerName, cd.getSimpleName()); 129 | 130 | // ensure the appropriate number of blank lines on next statement after new field 131 | J.ClassDeclaration formatted = (J.ClassDeclaration) new AutoFormatVisitor().visitNonNull(cd, ctx, requireNonNull(getCursor().getParent())); 132 | cd = cd.withBody(cd.getBody().withStatements(ListUtils.map(cd.getBody().getStatements(), (i, stat) -> { 133 | if (i == 1) { 134 | return stat.withPrefix(formatted.getBody().getStatements().get(i).getPrefix()); 135 | } 136 | return stat; 137 | }))); 138 | 139 | maybeAddImport(loggerType); 140 | maybeAddImport(factoryType); 141 | } 142 | 143 | return cd; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/CatchBlockLogLevel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.openrewrite.*; 21 | import org.openrewrite.java.JavaIsoVisitor; 22 | import org.openrewrite.java.MethodMatcher; 23 | import org.openrewrite.java.search.UsesMethod; 24 | import org.openrewrite.java.tree.J; 25 | import org.openrewrite.java.tree.JavaType; 26 | import org.openrewrite.java.tree.TypeUtils; 27 | 28 | import java.util.concurrent.atomic.AtomicBoolean; 29 | 30 | @Value 31 | @EqualsAndHashCode(callSuper = false) 32 | public class CatchBlockLogLevel extends Recipe { 33 | 34 | @Override 35 | public String getDisplayName() { 36 | return "Catch block log level"; 37 | } 38 | 39 | @Override 40 | public String getDescription() { 41 | return "Sometimes exceptions are caught and logged at the wrong log level. This will set the log level of " + 42 | "logging statements within a catch block not containing an exception to \"warn\", and the log level of " + 43 | "logging statements containing an exception to \"error\". " + 44 | "This supports SLF4J, Log4J1, Log4j2, and Logback."; 45 | } 46 | 47 | private static final MethodMatcher SLF4J_MATCHER = new MethodMatcher("org.slf4j.Logger *(..)"); 48 | private static final MethodMatcher LOG4J1_MATCHER = new MethodMatcher("org.apache.log4j.Category *(..)"); 49 | private static final MethodMatcher LOG4J2_MATCHER = new MethodMatcher("org.apache.logging.log4j.Logger *(..)"); 50 | private static final MethodMatcher LOGBACK_MATCHER = new MethodMatcher("ch.qos.logback.classic.Logger *(..)"); 51 | 52 | @Override 53 | public TreeVisitor getVisitor() { 54 | TreeVisitor check = Preconditions.or( 55 | new UsesMethod<>(SLF4J_MATCHER), 56 | new UsesMethod<>(LOG4J1_MATCHER), 57 | new UsesMethod<>(LOG4J2_MATCHER), 58 | new UsesMethod<>(LOGBACK_MATCHER)); 59 | 60 | return Preconditions.check(check, new JavaIsoVisitor() { 61 | @Override 62 | public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { 63 | J.MethodInvocation m = super.visitMethodInvocation(method, ctx); 64 | if (!(SLF4J_MATCHER.matches(m) || LOG4J1_MATCHER.matches(m) || LOG4J2_MATCHER.matches(m) || LOGBACK_MATCHER.matches(m))) { 65 | return m; 66 | } 67 | Object maybeCatch = getCursor().dropParentUntil(it -> 68 | it == Cursor.ROOT_VALUE || 69 | it instanceof J.Try.Catch || it instanceof J.Try || 70 | it instanceof J.MethodDeclaration || it instanceof J.Lambda || it instanceof J.ClassDeclaration).getValue(); 71 | if (!(maybeCatch instanceof J.Try.Catch)) { 72 | return m; 73 | } 74 | if (referencesException(m, ctx)) { 75 | if ("info".equals(m.getSimpleName()) || "debug".equals(m.getSimpleName()) || "trace".equals(m.getSimpleName()) || "warn".equals(m.getSimpleName())) { 76 | JavaType.Method mt = m.getMethodType() == null ? null : m.getMethodType().withName("error"); 77 | m = m.withName(m.getName().withSimpleName("error").withType(mt)) 78 | .withMethodType(mt); 79 | } 80 | } else { 81 | if ("info".equals(m.getSimpleName()) || "debug".equals(m.getSimpleName()) || "trace".equals(m.getSimpleName())) { 82 | JavaType.Method mt = m.getMethodType() == null ? null : m.getMethodType().withName("warn"); 83 | m = m.withName(m.getName().withSimpleName("warn").withType(mt)) 84 | .withMethodType(mt); 85 | } 86 | } 87 | return m; 88 | } 89 | 90 | private boolean referencesException(J j, ExecutionContext ctx) { 91 | AtomicBoolean found = new AtomicBoolean(false); 92 | new JavaIsoVisitor() { 93 | @Override 94 | public J.Identifier visitIdentifier(J.Identifier identifier, ExecutionContext ctx) { 95 | J.Identifier i = super.visitIdentifier(identifier, ctx); 96 | found.set(found.get() || TypeUtils.isAssignableTo("java.lang.Throwable", i.getType())); 97 | return i; 98 | } 99 | }.visit(j, ctx); 100 | return found.get(); 101 | } 102 | }); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/ChangeLoggersToPrivate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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 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 getVisitor() { 58 | return Preconditions.check(usesAnyLogger(), new JavaIsoVisitor() { 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 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 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 | } 123 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/ChangeLombokLogAnnotation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.AllArgsConstructor; 19 | import org.jspecify.annotations.Nullable; 20 | import org.openrewrite.Option; 21 | import org.openrewrite.Recipe; 22 | import org.openrewrite.java.ChangeType; 23 | 24 | import java.util.List; 25 | import java.util.stream.Collectors; 26 | import java.util.stream.Stream; 27 | 28 | @AllArgsConstructor 29 | public class ChangeLombokLogAnnotation extends Recipe { 30 | 31 | @Override 32 | public String getDisplayName() { 33 | return "Replace any Lombok log annotations with target logging framework annotation"; 34 | } 35 | 36 | @Override 37 | public String getDescription() { 38 | return "Replace Lombok annotations such as `@CommonsLog` and `@Log4j` with the target logging framework annotation, or `@Sl4fj` if not provided."; 39 | } 40 | 41 | @Option(displayName = "Logging framework", 42 | description = "The logging framework to use.", 43 | valid = {"SLF4J", "Log4J1", "Log4J2", "JUL", "COMMONS"}, 44 | required = false) 45 | @Nullable 46 | private String loggingFramework; 47 | 48 | @Override 49 | public List getRecipeList() { 50 | String targetLogAnnotationType = getTargetAnnotationType(loggingFramework); 51 | return Stream.of( 52 | "lombok.extern.java.Log", 53 | "lombok.extern.apachecommons.CommonsLog", 54 | "lombok.extern.flogger.Flogger", 55 | "lombok.extern.jbosslog.JBossLog", 56 | "lombok.extern.log4j.Log4j", 57 | "lombok.extern.log4j.Log4j2", 58 | "lombok.extern.slf4j.Slf4j", 59 | "lombok.extern.slf4j.XSlf4j", 60 | "lombok.CustomLog") 61 | .filter(annotationType -> !annotationType.equals(targetLogAnnotationType)) 62 | .map(annotationType -> new ChangeType(annotationType, targetLogAnnotationType, true)) 63 | .collect(Collectors.toList()); 64 | } 65 | 66 | private static String getTargetAnnotationType(@Nullable String loggingFramework) { 67 | if (loggingFramework != null) { 68 | switch (LoggingFramework.fromOption(loggingFramework)) { 69 | case Log4J1: 70 | return "lombok.extern.log4j.Log4j"; 71 | case Log4J2: 72 | return "lombok.extern.log4j.Log4j2"; 73 | case JUL: 74 | return "lombok.extern.java.Log"; 75 | case COMMONS: 76 | return "lombok.extern.apachecommons.CommonsLog"; 77 | } 78 | } 79 | return "lombok.extern.slf4j.Slf4j"; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/LoggingFramework.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.jspecify.annotations.Nullable; 19 | import org.openrewrite.ExecutionContext; 20 | import org.openrewrite.java.JavaParser; 21 | import org.openrewrite.java.JavaTemplate; 22 | 23 | public enum LoggingFramework { 24 | SLF4J("org.slf4j.Logger"), 25 | Log4J1("org.apache.log4j.Logger"), 26 | Log4J2("org.apache.logging.log4j.Logger"), 27 | JUL("java.util.logging.Logger"), 28 | COMMONS("org.apache.commons.logging.Log"); 29 | 30 | private final String loggerType; 31 | 32 | LoggingFramework(String loggerType) { 33 | this.loggerType = loggerType; 34 | } 35 | 36 | public String getLoggerType() { 37 | return loggerType; 38 | } 39 | 40 | public static LoggingFramework fromOption(@Nullable String option) { 41 | if (option != null) { 42 | for (LoggingFramework value : values()) { 43 | if (value.toString().equalsIgnoreCase(option)) { 44 | return value; 45 | } 46 | } 47 | } 48 | return SLF4J; 49 | } 50 | 51 | public JavaTemplate getErrorTemplate(String message, ExecutionContext ctx) { 52 | switch (this) { 53 | case SLF4J: 54 | return JavaTemplate 55 | .builder("#{any(org.slf4j.Logger)}.error(" + message + ", #{any(java.lang.Throwable)})") 56 | .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "slf4j-api-2.1.+")) 57 | .build(); 58 | case Log4J1: 59 | return JavaTemplate 60 | .builder("#{any(org.apache.log4j.Category)}.error(" + message + ", #{any(java.lang.Throwable)})") 61 | .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "log4j-1.2.+")) 62 | .build(); 63 | 64 | case Log4J2: 65 | return JavaTemplate 66 | .builder("#{any(org.apache.logging.log4j.Logger)}.error(" + message + ", #{any(java.lang.Throwable)})") 67 | .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "log4j-api-2.+")) 68 | .build(); 69 | case COMMONS: 70 | return JavaTemplate 71 | .builder("#{any(org.apache.commons.logging.Log)}.error(" + message + ", #{any(java.lang.Throwable)})") 72 | .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "commons-logging-1.3.+")) 73 | .build(); 74 | case JUL: 75 | default: 76 | return JavaTemplate 77 | .builder("#{any(java.util.logging.Logger)}.log(Level.SEVERE, " + message + ", #{any(java.lang.Throwable)})") 78 | .imports("java.util.logging.Level") 79 | .build(); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/PrintStackTraceToLogError.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.java.AnnotationMatcher; 23 | import org.openrewrite.java.JavaIsoVisitor; 24 | import org.openrewrite.java.MethodMatcher; 25 | import org.openrewrite.java.search.FindFieldsOfType; 26 | import org.openrewrite.java.search.UsesType; 27 | import org.openrewrite.java.service.AnnotationService; 28 | import org.openrewrite.java.tree.J; 29 | import org.openrewrite.java.tree.Space; 30 | import org.openrewrite.marker.Markers; 31 | 32 | import java.util.Collections; 33 | import java.util.Set; 34 | 35 | @Value 36 | @EqualsAndHashCode(callSuper = false) 37 | public class PrintStackTraceToLogError extends Recipe { 38 | @Option(displayName = "Add logger", 39 | description = "Add a logger field to the class if it isn't already present.", 40 | required = false) 41 | @Nullable 42 | Boolean addLogger; 43 | 44 | @Option(displayName = "Logger name", 45 | description = "The name of the logger to use when generating a field.", 46 | required = false, 47 | example = "log") 48 | @Nullable 49 | String loggerName; 50 | 51 | @Option(displayName = "Logging framework", 52 | description = "The logging framework to use.", 53 | valid = {"SLF4J", "Log4J1", "Log4J2", "JUL", "COMMONS"}, 54 | required = false) 55 | @Nullable 56 | String loggingFramework; 57 | 58 | @Override 59 | public String getDisplayName() { 60 | return "Use logger instead of `printStackTrace()`"; 61 | } 62 | 63 | @Override 64 | public String getDescription() { 65 | return "When a logger is present, log exceptions rather than calling `printStackTrace()`."; 66 | } 67 | 68 | @Override 69 | public TreeVisitor getVisitor() { 70 | MethodMatcher printStackTrace = new MethodMatcher("java.lang.Throwable printStackTrace(..)"); 71 | LoggingFramework framework = LoggingFramework.fromOption(loggingFramework); 72 | AnnotationMatcher lombokLogAnnotationMatcher = new AnnotationMatcher("@lombok.extern..*"); 73 | 74 | JavaIsoVisitor visitor = new JavaIsoVisitor() { 75 | @Override 76 | public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { 77 | J.MethodInvocation m = super.visitMethodInvocation(method, ctx); 78 | if (printStackTrace.matches(m)) { 79 | Cursor classCursor = getCursor().dropParentUntil(J.ClassDeclaration.class::isInstance); 80 | AnnotationService annotationService = service(AnnotationService.class); 81 | Set loggers = FindFieldsOfType.find(classCursor.getValue(), framework.getLoggerType()); 82 | if (!loggers.isEmpty()) { 83 | J.Identifier logField = loggers.iterator().next().getVariables().get(0).getName(); 84 | m = replaceMethodInvocation(m, logField, ctx); 85 | } else if (annotationService.matches(classCursor, lombokLogAnnotationMatcher)) { 86 | String fieldName = loggerName == null ? "log" : loggerName; 87 | J.Identifier logField = new J.Identifier(Tree.randomId(), Space.SINGLE_SPACE, Markers.EMPTY, Collections.emptyList(), fieldName, null, null); 88 | m = replaceMethodInvocation(m, logField, ctx); 89 | } else if (addLogger != null && addLogger) { 90 | doAfterVisit(AddLogger.addLogger(classCursor.getValue(), framework, loggerName == null ? "logger" : loggerName, ctx)); 91 | } 92 | } 93 | return m; 94 | } 95 | 96 | private J.MethodInvocation replaceMethodInvocation(J.MethodInvocation m, J.Identifier logField, ExecutionContext ctx) { 97 | if (framework == LoggingFramework.JUL) { 98 | maybeAddImport("java.util.logging.Level"); 99 | } 100 | return framework.getErrorTemplate("\"Exception\"", ctx).apply( 101 | new Cursor(getCursor().getParent(), m), 102 | m.getCoordinates().replace(), 103 | logField, 104 | m.getSelect()); 105 | } 106 | }; 107 | return Repeat.repeatUntilStable(addLogger != null && addLogger ? visitor : Preconditions.check( 108 | Preconditions.or( 109 | new UsesType<>(framework.getLoggerType(), null), 110 | new UsesType<>("lombok.extern..*", null)) 111 | , visitor)); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/SystemPrintToLogging.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.Getter; 20 | import lombok.Value; 21 | import org.jspecify.annotations.Nullable; 22 | import org.openrewrite.Option; 23 | import org.openrewrite.Recipe; 24 | 25 | import java.util.Arrays; 26 | import java.util.List; 27 | 28 | @Value 29 | @EqualsAndHashCode(callSuper = false) 30 | public class SystemPrintToLogging extends Recipe { 31 | @Option(displayName = "Add logger", 32 | description = "Add a logger field to the class if it isn't already present.", 33 | required = false) 34 | @Nullable 35 | Boolean addLogger; 36 | 37 | @Option(displayName = "Logger name", 38 | description = "The name of the logger to use when generating a field.", 39 | required = false, 40 | example = "log") 41 | @Nullable 42 | String loggerName; 43 | 44 | @Option(displayName = "Logging framework", 45 | description = "The logging framework to use.", 46 | valid = {"SLF4J", "Log4J1", "Log4J2", "JUL", "COMMONS"}, 47 | required = false) 48 | @Nullable 49 | String loggingFramework; 50 | 51 | @Option(displayName = "Level", 52 | description = "The logging level to turn `System.out` print statements into.", 53 | valid = {"trace", "debug", "info"}, 54 | required = false) 55 | @Nullable 56 | String level; 57 | 58 | @Getter(lazy = true) 59 | List recipeList = Arrays.asList( 60 | new SystemErrToLogging(addLogger, loggerName, loggingFramework), 61 | new SystemOutToLogging(addLogger, loggerName, loggingFramework, level), 62 | new PrintStackTraceToLogError(addLogger, loggerName, loggingFramework) 63 | ); 64 | 65 | @Override 66 | public String getDisplayName() { 67 | return "Use logger instead of system print statements"; 68 | } 69 | 70 | @Override 71 | public String getDescription() { 72 | return "Replace `System.out` and `System.err` print statements with a logger."; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/log4j/ConvertJulEntering.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.log4j; 17 | 18 | import lombok.EqualsAndHashCode; 19 | import lombok.Value; 20 | import org.openrewrite.ExecutionContext; 21 | import org.openrewrite.Preconditions; 22 | import org.openrewrite.Recipe; 23 | import org.openrewrite.TreeVisitor; 24 | import org.openrewrite.java.JavaIsoVisitor; 25 | import org.openrewrite.java.MethodMatcher; 26 | import org.openrewrite.java.search.UsesMethod; 27 | import org.openrewrite.java.tree.Expression; 28 | import org.openrewrite.java.tree.J; 29 | import org.openrewrite.java.tree.JavaType; 30 | import org.openrewrite.java.tree.Space; 31 | import org.openrewrite.marker.Markers; 32 | 33 | import java.util.ArrayList; 34 | import java.util.List; 35 | 36 | import static org.openrewrite.Tree.randomId; 37 | 38 | /** 39 | * This recipe rewrites JUL's {@link java.util.logging.Logger#entering} method. 40 | */ 41 | @Value 42 | @EqualsAndHashCode(callSuper = false) 43 | public class ConvertJulEntering extends Recipe { 44 | 45 | private static final MethodMatcher METHOD_MATCHER = new MethodMatcher("java.util.logging.Logger entering(String, String, ..)", false); 46 | 47 | @Override 48 | public String getDisplayName() { 49 | return "Rewrites JUL's Logger#entering method to Log4j API"; 50 | } 51 | 52 | @Override 53 | public String getDescription() { 54 | return "Replaces JUL's Logger#entering method calls to Log4j API Logger#traceEntry calls."; 55 | } 56 | 57 | @Override 58 | public TreeVisitor getVisitor() { 59 | return Preconditions.check(new UsesMethod<>(METHOD_MATCHER), new JavaIsoVisitor() { 60 | @Override 61 | public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { 62 | J.MethodInvocation m = super.visitMethodInvocation(method, ctx); 63 | if (METHOD_MATCHER.matches(m)) { 64 | List originalArgs = m.getArguments(); 65 | int originalArgCount = originalArgs.size(); 66 | if (3 < originalArgCount) { 67 | return m; 68 | } 69 | List modifiedArgs = new ArrayList<>(); 70 | List modifiedTypes = new ArrayList<>(); 71 | if (2 < originalArgCount) { 72 | modifiedArgs.add(buildNullString()); 73 | modifiedTypes.add(JavaType.Primitive.String); 74 | modifiedArgs.add(originalArgs.get(2)); 75 | modifiedTypes.add(JavaType.buildType("java.lang.Object[]")); 76 | } 77 | JavaType.Method mt = m.getMethodType().withParameterTypes(modifiedTypes); 78 | JavaType.FullyQualified dt = mt.getDeclaringType().withFullyQualifiedName("org.apache.logging.log4j.Logger"); 79 | return m.withMethodType(mt) 80 | .withName(m.getName().withSimpleName("traceEntry")) 81 | .withArguments(modifiedArgs) 82 | .withDeclaringType(dt); 83 | } 84 | return m; 85 | } 86 | 87 | private J.Literal buildNullString() { 88 | return new J.Literal(randomId(), Space.EMPTY, Markers.EMPTY, null, "null", null, JavaType.Primitive.String); 89 | } 90 | } 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/log4j/ConvertJulExiting.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.log4j; 17 | 18 | import lombok.EqualsAndHashCode; 19 | import lombok.Value; 20 | import org.openrewrite.ExecutionContext; 21 | import org.openrewrite.Preconditions; 22 | import org.openrewrite.Recipe; 23 | import org.openrewrite.TreeVisitor; 24 | import org.openrewrite.java.JavaIsoVisitor; 25 | import org.openrewrite.java.MethodMatcher; 26 | import org.openrewrite.java.search.UsesMethod; 27 | import org.openrewrite.java.tree.*; 28 | 29 | import java.util.ArrayList; 30 | import java.util.List; 31 | 32 | /** 33 | * This recipe rewrites JUL's {@link java.util.logging.Logger#entering} method. 34 | */ 35 | @Value 36 | @EqualsAndHashCode(callSuper = false) 37 | public class ConvertJulExiting extends Recipe { 38 | 39 | private static final MethodMatcher METHOD_MATCHER = new MethodMatcher("java.util.logging.Logger exiting(String, String, ..)", false); 40 | 41 | @Override 42 | public String getDisplayName() { 43 | return "Rewrites JUL's Logger#exiting method to Log4j API"; 44 | } 45 | 46 | @Override 47 | public String getDescription() { 48 | return "Replaces JUL's Logger#exiting method calls to Log4j API Logger#traceEntry calls."; 49 | } 50 | 51 | @Override 52 | public TreeVisitor getVisitor() { 53 | return Preconditions.check(new UsesMethod<>(METHOD_MATCHER), new JavaIsoVisitor() { 54 | @Override 55 | public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { 56 | J.MethodInvocation m = super.visitMethodInvocation(method, ctx); 57 | if (METHOD_MATCHER.matches(m) && m.getMethodType() != null) { 58 | List> originalArgs = m.getPadding() 59 | .getArguments() 60 | .getPadding() 61 | .getElements(); 62 | List originalTypes = m.getMethodType().getParameterTypes(); 63 | int originalArgCount = originalArgs.size(); 64 | if (3 < originalArgCount) { 65 | return m; 66 | } 67 | List modifiedArgs = new ArrayList<>(); 68 | List modifiedTypes = new ArrayList<>(); 69 | if (originalArgCount > 2) { 70 | modifiedArgs.add(originalArgs.get(2).getElement().withPrefix(Space.EMPTY)); 71 | modifiedTypes.add(originalTypes.get(2)); 72 | } 73 | JavaType.Method mt = m.getMethodType().withParameterTypes(modifiedTypes); 74 | JavaType.FullyQualified dt = mt.getDeclaringType() 75 | .withFullyQualifiedName("org.apache.logging.log4j.Logger"); 76 | return m.withMethodType(mt) 77 | .withName(m.getName().withSimpleName("traceExit")) 78 | .withArguments(modifiedArgs) 79 | .withDeclaringType(dt); 80 | } 81 | return m; 82 | } 83 | }); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/log4j/LoggerSetLevelToConfigurator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.log4j; 17 | 18 | import com.google.errorprone.refaster.annotation.AfterTemplate; 19 | import com.google.errorprone.refaster.annotation.BeforeTemplate; 20 | import org.apache.logging.log4j.core.config.Configurator; 21 | import org.openrewrite.java.template.RecipeDescriptor; 22 | 23 | @RecipeDescriptor( 24 | name = "Convert Log4j `Logger.setLevel` to Log4j2 `Configurator.setLevel`", 25 | description = "Converts `org.apache.log4j.Logger.setLevel` to `org.apache.logging.log4j.core.config.Configurator.setLevel`.") 26 | public class LoggerSetLevelToConfigurator { 27 | 28 | @BeforeTemplate 29 | void before(org.apache.log4j.Logger logger, org.apache.log4j.Level level) { 30 | logger.setLevel(level); 31 | } 32 | 33 | @AfterTemplate 34 | void after(org.apache.logging.log4j.Logger logger, org.apache.logging.log4j.Level level) { 35 | Configurator.setLevel(logger, level); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/log4j/LoggingExceptionConcatenation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.log4j; 17 | 18 | import com.google.errorprone.refaster.annotation.AfterTemplate; 19 | import com.google.errorprone.refaster.annotation.BeforeTemplate; 20 | import org.apache.logging.log4j.Logger; 21 | import org.openrewrite.java.template.RecipeDescriptor; 22 | 23 | @RecipeDescriptor( 24 | name = "Log exceptions as parameters rather than as string concatenations", 25 | description = "By using the exception as another parameter you get the whole stack trace." 26 | ) 27 | public class LoggingExceptionConcatenation { 28 | 29 | @BeforeTemplate 30 | void before(Logger logger, String s, Exception e) { 31 | logger.error(s + e); 32 | } 33 | 34 | @AfterTemplate 35 | void after(Logger logger, String s, Exception e) { 36 | logger.error(s, e); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/log4j/PrependRandomName.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.log4j; 17 | 18 | import com.fasterxml.jackson.annotation.JsonIgnore; 19 | import org.kohsuke.randname.RandomNameGenerator; 20 | import org.openrewrite.ExecutionContext; 21 | import org.openrewrite.Preconditions; 22 | import org.openrewrite.Recipe; 23 | import org.openrewrite.TreeVisitor; 24 | import org.openrewrite.java.JavaIsoVisitor; 25 | import org.openrewrite.java.MethodMatcher; 26 | import org.openrewrite.java.search.UsesMethod; 27 | import org.openrewrite.java.tree.J; 28 | import org.openrewrite.java.tree.JavaType; 29 | 30 | public class PrependRandomName extends Recipe { 31 | private static final MethodMatcher logStatement = new MethodMatcher("org.apache.log4j.Category *(Object, ..)"); 32 | 33 | private final int seed; 34 | 35 | @JsonIgnore 36 | private transient final RandomNameGenerator randomName; 37 | 38 | public PrependRandomName(int seed) { 39 | this.seed = seed; 40 | randomName = new RandomNameGenerator(seed); 41 | } 42 | 43 | @Override 44 | public String getDisplayName() { 45 | return "Prepend a random name to each Log4J statement"; 46 | } 47 | 48 | @Override 49 | public String getDescription() { 50 | return "To make finding the callsite of a logging statement easier in code search."; 51 | } 52 | 53 | @Override 54 | public TreeVisitor getVisitor() { 55 | return Preconditions.check(new UsesMethod<>(logStatement), new JavaIsoVisitor() { 56 | @Override 57 | public J.Literal visitLiteral(J.Literal literal, ExecutionContext ctx) { 58 | Object parent = getCursor().dropParentUntil(J.class::isInstance).getValue(); 59 | //noinspection ConstantConditions 60 | if (parent instanceof J.MethodInvocation && 61 | logStatement.matches((J.MethodInvocation) parent) && 62 | JavaType.Primitive.String == literal.getType() && 63 | !literal.getValue().toString().startsWith("<")) { 64 | String value = "<" + randomName.next() + "> " + literal.getValue().toString(); 65 | return literal.withValue(value) 66 | .withValueSource("\"" + value + "\""); 67 | } 68 | return super.visitLiteral(literal, ctx); 69 | } 70 | }); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/log4j/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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 | @NullMarked 17 | @NonNullFields 18 | package org.openrewrite.java.logging.log4j; 19 | 20 | import org.jspecify.annotations.NullMarked; 21 | import org.openrewrite.internal.lang.NonNullFields; 22 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/logback/ConfigureLoggerLevel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.logback; 17 | 18 | import lombok.EqualsAndHashCode; 19 | import lombok.Value; 20 | import org.openrewrite.*; 21 | import org.openrewrite.internal.ListUtils; 22 | import org.openrewrite.xml.XPathMatcher; 23 | import org.openrewrite.xml.XmlIsoVisitor; 24 | import org.openrewrite.xml.tree.Content; 25 | import org.openrewrite.xml.tree.Xml; 26 | 27 | import java.util.List; 28 | 29 | @Value 30 | @EqualsAndHashCode(callSuper = false) 31 | public class ConfigureLoggerLevel extends Recipe { 32 | 33 | @Override 34 | public String getDisplayName() { 35 | return "Configure logback logger level"; 36 | } 37 | 38 | @Override 39 | public String getDescription() { 40 | return "Within logback.xml configuration files sets the specified log level for a particular class. " + 41 | "Will not create a logback.xml if one does not already exist."; 42 | } 43 | 44 | @Option(displayName = "Class name", 45 | description = "The fully qualified class name to configure the log level for", 46 | example = "com.example.MyClass") 47 | String className; 48 | 49 | @Option(displayName = "Log level", 50 | description = "The log level to set for the class", 51 | valid = {"trace", "debug", "info", "warn", "error", "off"}, 52 | example = "off") 53 | LogLevel logLevel; 54 | 55 | public enum LogLevel { 56 | trace, 57 | debug, 58 | info, 59 | warn, 60 | error, 61 | off 62 | } 63 | 64 | @Override 65 | public TreeVisitor getVisitor() { 66 | return Preconditions.check(new FindSourceFiles("**/logback.xml"), new XmlIsoVisitor() { 67 | 68 | @Override 69 | public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { 70 | Xml.Document x = super.visitDocument(document, ctx); 71 | if(x == document && !getCursor().getMessage("found", false)) { 72 | // No tag already exists for the specified logger, we need to create a new one 73 | Xml.Tag l = Xml.Tag.build("\n"); 74 | l = autoFormat(l, ctx, new Cursor(getCursor(), x.getRoot())); 75 | //noinspection unchecked 76 | x = x.withRoot(x.getRoot().withContent(ListUtils.concat((List)x.getRoot().getContent(), l))); 77 | } 78 | return x; 79 | } 80 | 81 | final XPathMatcher loggerMatcher = new XPathMatcher("/configuration/logger[@name='" + className + "']"); 82 | 83 | @Override 84 | public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { 85 | Xml.Tag t = super.visitTag(tag, ctx); 86 | if (loggerMatcher.matches(getCursor())) { 87 | getCursor().putMessageOnFirstEnclosing(Xml.Document.class, "found", true); 88 | t = t.withAttributes(ListUtils.map(t.getAttributes(), a -> { 89 | if(a != null && "level".equals(a.getKeyAsString()) && !logLevel.name().equals(a.getValueAsString())) { 90 | return a.withValue(a.getValue().withValue(logLevel.name())); 91 | } 92 | return a; 93 | })); 94 | } 95 | return t; 96 | } 97 | }); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/logback/Log4jAppenderToLogback.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.logback; 17 | 18 | import org.openrewrite.*; 19 | import org.openrewrite.internal.ListUtils; 20 | import org.openrewrite.java.*; 21 | import org.openrewrite.java.search.UsesType; 22 | import org.openrewrite.java.tree.J; 23 | import org.openrewrite.java.tree.JavaType; 24 | import org.openrewrite.java.tree.TypeUtils; 25 | 26 | public class Log4jAppenderToLogback extends Recipe { 27 | @Override 28 | public String getDisplayName() { 29 | return "Migrate Log4j 2.x Appender to logback-classic equivalents"; 30 | } 31 | 32 | @Override 33 | public String getDescription() { 34 | return "Migrates custom Log4j 2.x Appender components to `logback-classic`. This recipe operates on the following assumptions: " + 35 | "1.) The contents of the `append()` method remains unchanged. " + 36 | "2.) The `requiresLayout()` method is not used in logback and can be removed. " + 37 | "3.) In logback, the `stop()` method is the equivalent of log4j's close() method. " + 38 | "For more details, see this page from logback: [`Migration from log4j`](http://logback.qos.ch/manual/migrationFromLog4j.html)."; 39 | } 40 | 41 | @Override 42 | public TreeVisitor getVisitor() { 43 | return Preconditions.check(new UsesType<>("org.apache.log4j.AppenderSkeleton", null), new JavaIsoVisitor() { 44 | @Override 45 | public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { 46 | doAfterVisit(new ChangeMethodName("org.apache.log4j.Layout format(..)", "doLayout", null, null).getVisitor()); 47 | return super.visitCompilationUnit(cu, ctx); 48 | } 49 | 50 | @Override 51 | public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { 52 | J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx); 53 | 54 | if (cd.getExtends() != null && cd.getExtends().getType() != null) { 55 | JavaType.FullyQualified fullyQualifiedExtends = TypeUtils.asFullyQualified(cd.getExtends().getType()); 56 | if (fullyQualifiedExtends != null && "org.apache.log4j.AppenderSkeleton".equals(fullyQualifiedExtends.getFullyQualifiedName())) { 57 | 58 | maybeRemoveImport("org.apache.log4j.AppenderSkeleton"); 59 | maybeAddImport("ch.qos.logback.core.AppenderBase"); 60 | maybeAddImport("ch.qos.logback.classic.spi.ILoggingEvent"); 61 | 62 | doAfterVisit(new ChangeType("org.apache.log4j.spi.LoggingEvent", "ch.qos.logback.classic.spi.ILoggingEvent", null).getVisitor()); 63 | doAfterVisit(new ChangeType("org.apache.log4j.Layout", "ch.qos.logback.core.LayoutBase", null).getVisitor()); 64 | 65 | cd = JavaTemplate.builder("AppenderBase") 66 | .contextSensitive() 67 | .imports("ch.qos.logback.core.AppenderBase", "ch.qos.logback.classic.spi.ILoggingEvent") 68 | .javaParser(JavaParser.fromJavaVersion().dependsOn( 69 | "package ch.qos.logback.classic.spi;public interface ILoggingEvent{ }", 70 | "package org.apache.log4j.spi;public class LoggingEvent { public String getRenderedMessage() {return null;}}")) 71 | .build() 72 | .apply(new Cursor(getCursor().getParent(), cd), cd.getCoordinates().replaceExtendsClause()); 73 | 74 | // should be covered by maybeAddImport, todo 75 | doAfterVisit(new AddImport<>("ch.qos.logback.core.AppenderBase", null, false)); 76 | } 77 | 78 | cd = cd.withBody(cd.getBody().withStatements(ListUtils.map(cd.getBody().getStatements(), statement -> { 79 | if (statement instanceof J.MethodDeclaration) { 80 | J.MethodDeclaration method = (J.MethodDeclaration) statement; 81 | if ("requiresLayout".equals(method.getSimpleName())) { 82 | return null; 83 | } else if ("close".equals(method.getSimpleName())) { 84 | if (method.getBody() != null && method.getBody().getStatements().isEmpty()) { 85 | return null; 86 | } 87 | 88 | return method.withName(method.getName().withSimpleName("stop")); 89 | } 90 | } 91 | return statement; 92 | }))); 93 | 94 | } 95 | 96 | return cd; 97 | } 98 | }); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/logback/Log4jLayoutToLogback.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.logback; 17 | 18 | import org.openrewrite.*; 19 | import org.openrewrite.internal.ListUtils; 20 | import org.openrewrite.java.*; 21 | import org.openrewrite.java.search.UsesType; 22 | import org.openrewrite.java.tree.J; 23 | import org.openrewrite.java.tree.JavaType; 24 | import org.openrewrite.java.tree.TypeUtils; 25 | 26 | import java.time.Duration; 27 | 28 | import static java.util.Objects.requireNonNull; 29 | 30 | public class Log4jLayoutToLogback extends Recipe { 31 | @Override 32 | public String getDisplayName() { 33 | return "Migrate Log4j 2.x Layout to logback-classic equivalents"; 34 | } 35 | 36 | @Override 37 | public String getDescription() { 38 | return "Migrates custom Log4j 2.x Layout components to `logback-classic`. This recipe operates on the following assumptions: " + 39 | "1. A logback-classic layout must extend the `LayoutBase` class. " + 40 | "2. log4j's `format()` is renamed to `doLayout()` in a logback-classic layout. " + 41 | "3. LoggingEvent `getRenderedMessage()` is converted to LoggingEvent `getMessage()`. " + 42 | "4. The log4j ignoresThrowable() method is not needed and has no equivalent in logback-classic. " + 43 | "5. The activateOptions() method merits further discussion. In log4j, a layout will have its activateOptions() method invoked by log4j configurators, that is PropertyConfigurator or DOMConfigurator just after all the options of the layout have been set. Thus, the layout will have an opportunity to check that its options are coherent and if so, proceed to fully initialize itself. " + 44 | "6. In logback-classic, layouts must implement the LifeCycle interface which includes a method called start(). The start() method is the equivalent of log4j's activateOptions() method. " + 45 | "For more details, see this page from logback: [`Migration from log4j`](http://logback.qos.ch/manual/migrationFromLog4j.html)."; 46 | } 47 | 48 | @Override 49 | public Duration getEstimatedEffortPerOccurrence() { 50 | return Duration.ofMinutes(15); 51 | } 52 | 53 | @Override 54 | public TreeVisitor getVisitor() { 55 | return Preconditions.check(new UsesType<>("org.apache.log4j.Layout", null), new JavaIsoVisitor() { 56 | @Override 57 | public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { 58 | doAfterVisit(new ChangeMethodName("org.apache.log4j.Layout format(..)", "doLayout", true, null).getVisitor()); 59 | doAfterVisit(new ChangeMethodName("org.apache.log4j.spi.LoggingEvent getRenderedMessage()", "getMessage", true, null).getVisitor()); 60 | return super.visitCompilationUnit(cu, ctx); 61 | } 62 | 63 | @Override 64 | public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { 65 | J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx); 66 | if (cd.getExtends() != null && cd.getExtends().getType() != null) { 67 | JavaType.FullyQualified fullyQualifiedExtends = TypeUtils.asFullyQualified(cd.getExtends().getType()); 68 | if (fullyQualifiedExtends != null && "org.apache.log4j.Layout".equals(fullyQualifiedExtends.getFullyQualifiedName())) { 69 | 70 | maybeRemoveImport("org.apache.log4j.Layout"); 71 | maybeRemoveImport("org.apache.log4j.LoggingEvent"); 72 | maybeAddImport("ch.qos.logback.core.LayoutBase"); 73 | maybeAddImport("ch.qos.logback.classic.spi.ILoggingEvent"); 74 | 75 | doAfterVisit(new ChangeType("org.apache.log4j.spi.LoggingEvent", "ch.qos.logback.classic.spi.ILoggingEvent", null).getVisitor()); 76 | 77 | cd = JavaTemplate.builder("LayoutBase") 78 | .contextSensitive() 79 | .imports("ch.qos.logback.core.LayoutBase", "ch.qos.logback.classic.spi.ILoggingEvent") 80 | .javaParser(JavaParser.fromJavaVersion().dependsOn( 81 | "package ch.qos.logback.classic.spi;public interface ILoggingEvent{ }", 82 | "package org.apache.log4j.spi;public class LoggingEvent { public String getRenderedMessage() {return null;}}")) 83 | .build() 84 | .apply(new Cursor(getCursor().getParent(), cd), cd.getCoordinates().replaceExtendsClause()); 85 | 86 | // should be covered by maybeAddImport, todo 87 | doAfterVisit(new AddImport<>("ch.qos.logback.core.LayoutBase", null, false)); 88 | } 89 | 90 | cd = cd.withBody(cd.getBody().withStatements(ListUtils.map(cd.getBody().getStatements(), statement -> { 91 | if (statement instanceof J.MethodDeclaration) { 92 | J.MethodDeclaration method = (J.MethodDeclaration) statement; 93 | if ("ignoresThrowable".equals(method.getSimpleName())) { 94 | return null; 95 | } else if ("activateOptions".equals(method.getSimpleName())) { 96 | if (method.getBody() != null && method.getBody().getStatements().isEmpty()) { 97 | return null; 98 | } 99 | J.ClassDeclaration enclosingClass = getCursor().firstEnclosing(J.ClassDeclaration.class); 100 | assert enclosingClass != null; 101 | doAfterVisit(new ImplementInterface<>(enclosingClass, "ch.qos.logback.core.spi.LifeCycle")); 102 | J.MethodDeclaration m = method.withName(method.getName().withSimpleName("start")); 103 | if (method.getMethodType() != null) { 104 | m = m.withMethodType(requireNonNull(m.getMethodType()).withName("start")); 105 | } 106 | return m; 107 | } 108 | } 109 | return statement; 110 | }))); 111 | 112 | } 113 | 114 | return cd; 115 | 116 | } 117 | 118 | }); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/logback/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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 | @NullMarked 17 | @NonNullFields 18 | package org.openrewrite.java.logging.logback; 19 | 20 | import org.jspecify.annotations.NullMarked; 21 | import org.openrewrite.internal.lang.NonNullFields; 22 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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 | @NullMarked 17 | @NonNullFields 18 | package org.openrewrite.java.logging; 19 | 20 | import org.jspecify.annotations.NullMarked; 21 | import org.openrewrite.internal.lang.NonNullFields; 22 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/slf4j/ChangeLogLevel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.slf4j; 17 | 18 | import lombok.EqualsAndHashCode; 19 | import lombok.Value; 20 | import org.jspecify.annotations.Nullable; 21 | import org.openrewrite.*; 22 | import org.openrewrite.internal.StringUtils; 23 | import org.openrewrite.java.ChangeMethodName; 24 | import org.openrewrite.java.JavaIsoVisitor; 25 | import org.openrewrite.java.MethodMatcher; 26 | import org.openrewrite.java.search.UsesMethod; 27 | import org.openrewrite.java.tree.Expression; 28 | import org.openrewrite.java.tree.J; 29 | 30 | import java.util.List; 31 | 32 | @Value 33 | @EqualsAndHashCode(callSuper = false) 34 | public class ChangeLogLevel extends Recipe { 35 | 36 | @Override 37 | public String getDisplayName() { 38 | return "Change SLF4J log level"; 39 | } 40 | 41 | @Override 42 | public String getDescription() { 43 | return "Change the log level of SLF4J log statements."; 44 | } 45 | 46 | @Option(displayName = "From", 47 | description = "The log level to change from.", 48 | example = "INFO") 49 | Level from; 50 | 51 | @Option(displayName = "To", 52 | description = "The log level to change to.", 53 | example = "DEBUG") 54 | Level to; 55 | 56 | @Option(displayName = "Starts with", 57 | description = "Only change log statements that start with this string. When omitted all log statements of " + 58 | "the specified level are changed.", 59 | example = "LaunchDarkly", 60 | required = false) 61 | @Nullable 62 | String startsWith; 63 | 64 | public enum Level { 65 | TRACE, 66 | DEBUG, 67 | INFO, 68 | WARN, 69 | ERROR 70 | } 71 | 72 | @Override 73 | public TreeVisitor getVisitor() { 74 | String methodPattern = "org.slf4j.Logger " + from.name().toLowerCase() + "(..)"; 75 | return Preconditions.check(new UsesMethod<>(methodPattern), new JavaIsoVisitor() { 76 | final MethodMatcher logMatcher = new MethodMatcher(methodPattern); 77 | @Override 78 | public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { 79 | J.MethodInvocation m = super.visitMethodInvocation(method, ctx); 80 | if (!logMatcher.matches(m)) { 81 | return m; 82 | } 83 | List args = m.getArguments(); 84 | if (args.isEmpty()) { 85 | return m; 86 | } 87 | J.Literal lit = leftMostLiteral(args.get(0)); 88 | if (lit == null || lit.getValue() == null) { 89 | return m; 90 | } 91 | if (!StringUtils.isBlank(startsWith) && !lit.getValue().toString().startsWith(startsWith)) { 92 | return m; 93 | } 94 | m = (J.MethodInvocation) new ChangeMethodName(methodPattern, to.name().toLowerCase(), true, null) 95 | .getVisitor() 96 | .visitNonNull(m, ctx); 97 | return m; 98 | } 99 | }); 100 | } 101 | 102 | J.@Nullable Literal leftMostLiteral(Expression arg) { 103 | if (arg instanceof J.Literal) { 104 | return (J.Literal) arg; 105 | } 106 | if (arg instanceof J.Binary) { 107 | return leftMostLiteral(((J.Binary) arg).getLeft()); 108 | } 109 | return null; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/slf4j/JulGetLoggerToLoggerFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.slf4j; 17 | 18 | import com.google.errorprone.refaster.annotation.AfterTemplate; 19 | import com.google.errorprone.refaster.annotation.BeforeTemplate; 20 | import org.openrewrite.java.template.RecipeDescriptor; 21 | import org.slf4j.LoggerFactory; 22 | 23 | import java.util.logging.Logger; 24 | 25 | @RecipeDescriptor( 26 | name = "Replace JUL Logger creation with SLF4J LoggerFactory", 27 | description = "Replace calls to `Logger.getLogger` with `LoggerFactory.getLogger`." 28 | ) 29 | public class JulGetLoggerToLoggerFactory { 30 | @RecipeDescriptor( 31 | name = "Replace JUL `Logger.getLogger(Some.class.getName())` with SLF4J's `LoggerFactory.getLogger(Some.class)`", 32 | description = "Replace calls to `java.util.logging.Logger.getLogger(Some.class.getName())` with `org.slf4j.LoggerFactory.getLogger(Some.class)`." 33 | ) 34 | public static class GetLoggerClassNameToLoggerFactory { 35 | @BeforeTemplate 36 | Logger before(Class clazz) { 37 | return Logger.getLogger(clazz.getName()); 38 | } 39 | 40 | @AfterTemplate 41 | org.slf4j.Logger after(Class clazz) { 42 | return LoggerFactory.getLogger(clazz); 43 | } 44 | } 45 | 46 | @RecipeDescriptor( 47 | name = "Replace JUL `Logger.getLogger(Some.class.getCanonicalName())` with SLF4J's `LoggerFactory.getLogger(Some.class)`", 48 | description = "Replace calls to `java.util.logging.Logger.getLogger(Some.class.getCanonicalName())` with `org.slf4j.LoggerFactory.getLogger(Some.class)`." 49 | ) 50 | public static class GetLoggerClassCanonicalNameToLoggerFactory { 51 | @BeforeTemplate 52 | Logger before(Class clazz) { 53 | return Logger.getLogger(clazz.getCanonicalName()); 54 | } 55 | 56 | @AfterTemplate 57 | org.slf4j.Logger after(Class clazz) { 58 | return LoggerFactory.getLogger(clazz); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/slf4j/JulIsLoggableToIsEnabled.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.slf4j; 17 | 18 | import com.google.errorprone.refaster.annotation.AfterTemplate; 19 | import com.google.errorprone.refaster.annotation.BeforeTemplate; 20 | import org.openrewrite.java.template.RecipeDescriptor; 21 | 22 | import java.util.logging.Level; 23 | import java.util.logging.Logger; 24 | 25 | @RecipeDescriptor( 26 | name = "Replace JUL active Level check with corresponding SLF4J method calls", 27 | description = "Replace calls to `Logger.isLoggable(Level)` with the corresponding SLF4J method calls." 28 | ) 29 | public class JulIsLoggableToIsEnabled { 30 | @RecipeDescriptor( 31 | name = "Replace JUL `Logger.isLoggable(Level.ALL)` with SLF4J's `Logger.isTraceEnabled`", 32 | description = "Replace calls to `java.util.logging.Logger.isLoggable(Level.ALL)` with `org.slf4j.Logger.isTraceEnabled()`." 33 | ) 34 | public static class LoggerIsLoggableLevelAll { 35 | @BeforeTemplate 36 | boolean before(Logger logger) { 37 | return logger.isLoggable(Level.ALL); 38 | } 39 | 40 | @AfterTemplate 41 | boolean after(org.slf4j.Logger logger) { 42 | return logger.isTraceEnabled(); 43 | } 44 | } 45 | 46 | @RecipeDescriptor( 47 | name = "Replace JUL `Logger.isLoggable(Level.FINEST)` with SLF4J's `Logger.isTraceEnabled`", 48 | description = "Replace calls to `java.util.logging.Logger.isLoggable(Level.FINEST)` with `org.slf4j.Logger.isTraceEnabled()`." 49 | ) 50 | public static class LoggerIsLoggableLevelFinest { 51 | @BeforeTemplate 52 | boolean before(Logger logger) { 53 | return logger.isLoggable(Level.FINEST); 54 | } 55 | 56 | @AfterTemplate 57 | boolean after(org.slf4j.Logger logger) { 58 | return logger.isTraceEnabled(); 59 | } 60 | } 61 | 62 | @RecipeDescriptor( 63 | name = "Replace JUL `Logger.isLoggable(Level.FINER)` with SLF4J's `Logger.isTraceEnabled()`", 64 | description = "Replace calls to `java.util.logging.Logger.isLoggable(Level.FINER)` with `org.slf4j.Logger.isTraceEnabled()`." 65 | ) 66 | public static class LoggerIsLoggableLevelFiner { 67 | @BeforeTemplate 68 | boolean before(Logger logger) { 69 | return logger.isLoggable(Level.FINER); 70 | } 71 | 72 | @AfterTemplate 73 | boolean after(org.slf4j.Logger logger) { 74 | return logger.isTraceEnabled(); 75 | } 76 | } 77 | 78 | @RecipeDescriptor( 79 | name = "Replace JUL `Logger.isLoggable(Level.FINE)` with SLF4J's `Logger.isDebugEnabled()`", 80 | description = "Replace calls to `java.util.logging.Logger.isLoggable(Level.FINE)` with `org.slf4j.Logger.isDebugEnabled()`." 81 | ) 82 | public static class LoggerIsLoggableLevelFine { 83 | @BeforeTemplate 84 | boolean before(Logger logger) { 85 | return logger.isLoggable(Level.FINE); 86 | } 87 | 88 | @AfterTemplate 89 | boolean after(org.slf4j.Logger logger) { 90 | return logger.isDebugEnabled(); 91 | } 92 | } 93 | 94 | @RecipeDescriptor( 95 | name = "Replace JUL `Logger.isLoggable(Level.CONFIG)` with SLF4J's `Logger.isInfoEnabled()`", 96 | description = "Replace calls to `java.util.logging.Logger.isLoggable(Level.CONFIG)` with `org.slf4j.Logger.isInfoEnabled()`." 97 | ) 98 | public static class LoggerIsLoggableLevelConfig { 99 | @BeforeTemplate 100 | boolean before(Logger logger) { 101 | return logger.isLoggable(Level.CONFIG); 102 | } 103 | 104 | @AfterTemplate 105 | boolean after(org.slf4j.Logger logger) { 106 | return logger.isInfoEnabled(); 107 | } 108 | } 109 | 110 | @RecipeDescriptor( 111 | name = "Replace JUL `Logger.isLoggable(Level.INFO)` with SLF4J's `Logger.isInfoEnabled()`", 112 | description = "Replace calls to `java.util.logging.Logger.isLoggable(Level.INFO)` with `org.slf4j.Logger.isInfoEnabled()`." 113 | ) 114 | public static class LoggerIsLoggableLevelInfo { 115 | @BeforeTemplate 116 | boolean before(Logger logger) { 117 | return logger.isLoggable(Level.INFO); 118 | } 119 | 120 | @AfterTemplate 121 | boolean after(org.slf4j.Logger logger) { 122 | return logger.isInfoEnabled(); 123 | } 124 | } 125 | 126 | @RecipeDescriptor( 127 | name = "Replace JUL `Logger.isLoggable(Level.WARNING)` with SLF4J's `Logger.isWarnEnabled()`", 128 | description = "Replace calls to `java.util.logging.Logger.isLoggable(Level.WARNING)` with `org.slf4j.Logger.isWarnEnabled()`." 129 | ) 130 | public static class LoggerIsLoggableLevelWarning { 131 | @BeforeTemplate 132 | boolean before(Logger logger) { 133 | return logger.isLoggable(Level.WARNING); 134 | } 135 | 136 | @AfterTemplate 137 | boolean after(org.slf4j.Logger logger) { 138 | return logger.isWarnEnabled(); 139 | } 140 | } 141 | 142 | @RecipeDescriptor( 143 | name = "Replace JUL `Logger.isLoggable(Level.SEVERE)` with SLF4J's `Logger.isErrorEnabled()`", 144 | description = "Replace calls to `java.util.logging.Logger.isLoggable(Level.SEVERE)` with `org.slf4j.Logger.isErrorEnabled()`." 145 | ) 146 | public static class LoggerIsLoggableLevelSevere { 147 | @BeforeTemplate 148 | boolean before(Logger logger) { 149 | return logger.isLoggable(Level.SEVERE); 150 | } 151 | 152 | @AfterTemplate 153 | boolean after(org.slf4j.Logger logger) { 154 | return logger.isErrorEnabled(); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/slf4j/JulLevelAllToTrace.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.slf4j; 17 | 18 | import com.google.errorprone.refaster.annotation.AfterTemplate; 19 | import com.google.errorprone.refaster.annotation.BeforeTemplate; 20 | import org.openrewrite.java.template.RecipeDescriptor; 21 | 22 | import java.util.logging.Level; 23 | import java.util.logging.Logger; 24 | 25 | @RecipeDescriptor( 26 | name = "Replace JUL `Level.ALL` logging with SLF4J's trace level", 27 | description = "Replace `java.util.logging.Logger#log(Level.ALL, String)` with `org.slf4j.Logger#trace(String)`." 28 | ) 29 | public class JulLevelAllToTrace { 30 | @BeforeTemplate 31 | void before(Logger logger, String message) { 32 | logger.log(Level.ALL, message); 33 | } 34 | 35 | @AfterTemplate 36 | void after(org.slf4j.Logger logger, String message) { 37 | logger.trace(message); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/slf4j/JulParameterizedArguments.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.slf4j; 17 | 18 | import org.jspecify.annotations.Nullable; 19 | import org.openrewrite.ExecutionContext; 20 | import org.openrewrite.Preconditions; 21 | import org.openrewrite.Recipe; 22 | import org.openrewrite.TreeVisitor; 23 | import org.openrewrite.java.JavaIsoVisitor; 24 | import org.openrewrite.java.JavaParser; 25 | import org.openrewrite.java.JavaTemplate; 26 | import org.openrewrite.java.MethodMatcher; 27 | import org.openrewrite.java.search.UsesMethod; 28 | import org.openrewrite.java.tree.*; 29 | import org.openrewrite.marker.Markers; 30 | 31 | import java.util.ArrayList; 32 | import java.util.Collections; 33 | import java.util.List; 34 | import java.util.Objects; 35 | import java.util.regex.Matcher; 36 | import java.util.regex.Pattern; 37 | 38 | import static org.openrewrite.Tree.randomId; 39 | 40 | public class JulParameterizedArguments extends Recipe { 41 | private static final MethodMatcher METHOD_MATCHER_PARAM = new MethodMatcher("java.util.logging.Logger log(java.util.logging.Level, java.lang.String, java.lang.Object)"); 42 | private static final MethodMatcher METHOD_MATCHER_ARRAY = new MethodMatcher("java.util.logging.Logger log(java.util.logging.Level, java.lang.String, java.lang.Object[])"); 43 | 44 | @Override 45 | public String getDisplayName() { 46 | return "Replace parameterized JUL level call with corresponding SLF4J method calls"; 47 | } 48 | 49 | @Override 50 | public String getDescription() { 51 | return "Replace calls to parameterized `Logger.log(Level,String,…)` call with the corresponding slf4j method calls transforming the formatter and parameter lists."; 52 | } 53 | 54 | @Override 55 | public TreeVisitor getVisitor() { 56 | return Preconditions.check(Preconditions.or(new UsesMethod<>(METHOD_MATCHER_PARAM), new UsesMethod<>(METHOD_MATCHER_ARRAY)), new JulParameterizedToSlf4jVisitor()); 57 | } 58 | 59 | private static class JulParameterizedToSlf4jVisitor extends JavaIsoVisitor { 60 | 61 | public static boolean isStringLiteral(Expression expression) { 62 | return expression instanceof J.Literal && TypeUtils.isString(((J.Literal) expression).getType()); 63 | } 64 | 65 | private static @Nullable String getMethodIdentifier(Expression levelArgument) { 66 | String levelSimpleName = levelArgument instanceof J.FieldAccess ? 67 | (((J.FieldAccess) levelArgument).getName().getSimpleName()) : 68 | (((J.Identifier) levelArgument).getSimpleName()); 69 | switch (levelSimpleName) { 70 | case "ALL": 71 | case "FINEST": 72 | case "FINER": 73 | return "trace"; 74 | case "FINE": 75 | return "debug"; 76 | case "CONFIG": 77 | case "INFO": 78 | return "info"; 79 | case "WARNING": 80 | return "warn"; 81 | case "SEVERE": 82 | return "error"; 83 | } 84 | return null; 85 | } 86 | 87 | private static J.Literal buildStringLiteral(String string) { 88 | return new J.Literal(randomId(), Space.EMPTY, Markers.EMPTY, string, String.format("\"%s\"", string), null, JavaType.Primitive.String); 89 | } 90 | 91 | @Override 92 | public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { 93 | if (METHOD_MATCHER_ARRAY.matches(method) || METHOD_MATCHER_PARAM.matches(method)) { 94 | List originalArguments = method.getArguments(); 95 | 96 | Expression levelArgument = originalArguments.get(0); 97 | Expression messageArgument = originalArguments.get(1); 98 | 99 | if (!(levelArgument instanceof J.FieldAccess || levelArgument instanceof J.Identifier) || 100 | !isStringLiteral(messageArgument)) { 101 | return method; 102 | } 103 | String newName = getMethodIdentifier(levelArgument); 104 | if(newName == null) { 105 | return method; 106 | } 107 | maybeRemoveImport("java.util.logging.Level"); 108 | 109 | String originalFormatString = Objects.requireNonNull((String) ((J.Literal) messageArgument).getValue()); 110 | List originalIndices = originalLoggedArgumentIndices(originalFormatString); 111 | List originalParameters = originalParameters(originalArguments.get(2)); 112 | 113 | List targetArguments = new ArrayList<>(2); 114 | targetArguments.add(buildStringLiteral(originalFormatString.replaceAll("\\{\\d*}", "{}"))); 115 | originalIndices.forEach(i -> targetArguments.add(originalParameters.get(i))); 116 | return JavaTemplate.builder(createTemplateString(newName, targetArguments)) 117 | .contextSensitive() 118 | .javaParser(JavaParser.fromJavaVersion() 119 | .classpathFromResources(ctx, "slf4j-api-2.1.+")) 120 | .build() 121 | .apply(getCursor(), method.getCoordinates().replaceMethod(), targetArguments.toArray()); 122 | } 123 | return super.visitMethodInvocation(method, ctx); 124 | } 125 | 126 | private List originalLoggedArgumentIndices(String strFormat) { 127 | // A string format like "Hello {0} {1} {1}" should be transformed to 0, 1, 1 128 | Matcher matcher = Pattern.compile("\\{(\\d+)}").matcher(strFormat); 129 | List loggedArgumentIndices = new ArrayList<>(2); 130 | while (matcher.find()) { 131 | loggedArgumentIndices.add(Integer.valueOf(matcher.group(1))); 132 | } 133 | return loggedArgumentIndices; 134 | } 135 | 136 | private static List originalParameters(Expression logParameters) { 137 | if (logParameters instanceof J.NewArray) { 138 | final List initializer = ((J.NewArray) logParameters).getInitializer(); 139 | if (initializer == null || initializer.isEmpty()) { 140 | return Collections.emptyList(); 141 | } 142 | return initializer; 143 | } 144 | return Collections.singletonList(logParameters); 145 | } 146 | 147 | private static String createTemplateString(String newName, List targetArguments) { 148 | List targetArgumentsStrings = new ArrayList<>(); 149 | targetArguments.forEach(targetArgument -> targetArgumentsStrings.add("#{any()}")); 150 | return newName + '(' + String.join(",", targetArgumentsStrings) + ')'; 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/slf4j/JulToSlf4jSimpleCallsWithThrowable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.slf4j; 17 | 18 | import com.google.errorprone.refaster.annotation.AfterTemplate; 19 | import com.google.errorprone.refaster.annotation.BeforeTemplate; 20 | import org.openrewrite.java.template.RecipeDescriptor; 21 | 22 | import java.util.logging.Level; 23 | import java.util.logging.Logger; 24 | 25 | @RecipeDescriptor( 26 | name = "Replace JUL `log(Level, String, Throwable)` with corresponding SLF4J method calls", 27 | description = "Replace calls to `Logger.log(Level, String, Throwable)` with the corresponding SLF4J method calls." 28 | ) 29 | public class JulToSlf4jSimpleCallsWithThrowable { 30 | @RecipeDescriptor( 31 | name = "Replace JUL `logger.log(Level.FINEST, String message, Throwable e)` with SLF4J's `Logger.trace(message, e)`", 32 | description = "Replace calls to `java.util.logging.Logger.log(Level.FINEST, String message, Throwable e)` with `org.slf4j.Logger.trace(message, e)`." 33 | ) 34 | public static class JulToSlf4jSupplierFinest { 35 | @BeforeTemplate 36 | void before(Logger logger, String message, Throwable e) { 37 | logger.log(Level.FINEST, message, e); 38 | } 39 | 40 | @AfterTemplate 41 | void after(org.slf4j.Logger logger, String message, Throwable e) { 42 | logger.trace(message, e); 43 | } 44 | } 45 | 46 | @RecipeDescriptor( 47 | name = "Replace JUL `logger.log(Level.FINER, String message, Throwable e)` with SLF4J's `Logger.trace(message, e)`", 48 | description = "Replace calls to `java.util.logging.Logger.log(Level.FINER, String message, Throwable e)` with `org.slf4j.Logger.trace(message, e)`." 49 | ) 50 | public static class JulToSlf4jSupplierFiner { 51 | @BeforeTemplate 52 | void before(Logger logger, String message, Throwable e) { 53 | logger.log(Level.FINER, message, e); 54 | } 55 | 56 | @AfterTemplate 57 | void after(org.slf4j.Logger logger, String message, Throwable e) { 58 | logger.trace(message, e); 59 | } 60 | } 61 | 62 | @RecipeDescriptor( 63 | name = "Replace JUL `logger.log(Level.FINE, String message, Throwable e)` with SLF4J's `Logger.debug(message, e)`", 64 | description = "Replace calls to `java.util.logging.Logger.log(Level.FINE, String message, Throwable e)` with `org.slf4j.Logger.debug(message, e)`." 65 | ) 66 | public static class JulToSlf4jSupplierFine { 67 | @BeforeTemplate 68 | void before(Logger logger, String message, Throwable e) { 69 | logger.log(Level.FINE, message, e); 70 | } 71 | 72 | @AfterTemplate 73 | void after(org.slf4j.Logger logger, String message, Throwable e) { 74 | logger.debug(message, e); 75 | } 76 | } 77 | 78 | @RecipeDescriptor( 79 | name = "Replace JUL `logger.log(Level.CONFIG, String message, Throwable e)` with SLF4J's `Logger.info(message, e)`", 80 | description = "Replace calls to `java.util.logging.Logger.log(Level.CONFIG, String message, Throwable e)` with `org.slf4j.Logger.info(message, e)`." 81 | ) 82 | public static class JulToSlf4jSupplierConfig { 83 | @BeforeTemplate 84 | void before(Logger logger, String message, Throwable e) { 85 | logger.log(Level.CONFIG, message, e); 86 | } 87 | 88 | @AfterTemplate 89 | void after(org.slf4j.Logger logger, String message, Throwable e) { 90 | logger.info(message, e); 91 | } 92 | } 93 | 94 | @RecipeDescriptor( 95 | name = "Replace JUL `logger.log(Level.INFO, String message, Throwable e)` with SLF4J's `Logger.info(message, e)`", 96 | description = "Replace calls to `java.util.logging.Logger.log(Level.INFO, String message, Throwable e)` with `org.slf4j.Logger.info(message, e)`." 97 | ) 98 | public static class JulToSlf4jSupplierInfo { 99 | @BeforeTemplate 100 | void before(Logger logger, String message, Throwable e) { 101 | logger.log(Level.INFO, message, e); 102 | } 103 | 104 | @AfterTemplate 105 | void after(org.slf4j.Logger logger, String message, Throwable e) { 106 | logger.info(message, e); 107 | } 108 | } 109 | 110 | @RecipeDescriptor( 111 | name = "Replace JUL `logger.log(Level.WARNING, String message, Throwable e)` with SLF4J's `Logger.warn(message, e)`", 112 | description = "Replace calls to `java.util.logging.Logger.log(Level.WARNING, String message, Throwable e)` with `org.slf4j.Logger.warn(message, e)`." 113 | ) 114 | public static class JulToSlf4jSupplierWarning { 115 | @BeforeTemplate 116 | void before(Logger logger, String message, Throwable e) { 117 | logger.log(Level.WARNING, message, e); 118 | } 119 | 120 | @AfterTemplate 121 | void after(org.slf4j.Logger logger, String message, Throwable e) { 122 | logger.warn(message, e); 123 | } 124 | } 125 | 126 | @RecipeDescriptor( 127 | name = "Replace JUL `logger.log(Level.SEVERE, String message, Throwable e)` with SLF4J's `Logger.error(message, e)`", 128 | description = "Replace calls to `java.util.logging.Logger.log(Level.SEVERE, String message, Throwable e)` with `org.slf4j.Logger.error(message, e)`." 129 | ) 130 | public static class JulToSlf4jSupplierSevere { 131 | @BeforeTemplate 132 | void before(Logger logger, String message, Throwable e) { 133 | logger.log(Level.SEVERE, message, e); 134 | } 135 | 136 | @AfterTemplate 137 | void after(org.slf4j.Logger logger, String message, Throwable e) { 138 | logger.error(message, e); 139 | } 140 | } 141 | 142 | @RecipeDescriptor( 143 | name = "Replace JUL `logger.log(Level.ALL, String message, Throwable e)` with SLF4J's `Logger.trace(message, e)`", 144 | description = "Replace calls to `java.util.logging.Logger.log(Level.ALL, String message, Throwable e)` with `org.slf4j.Logger.trace(message, e)`." 145 | ) 146 | public static class JulToSlf4jSupplierAll { 147 | @BeforeTemplate 148 | void before(Logger logger, String message, Throwable e) { 149 | logger.log(Level.ALL, message, e); 150 | } 151 | 152 | @AfterTemplate 153 | void after(org.slf4j.Logger logger, String message, Throwable e) { 154 | logger.trace(message, e); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/slf4j/LoggersNamedForEnclosingClass.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.slf4j; 17 | 18 | import org.openrewrite.ExecutionContext; 19 | import org.openrewrite.Preconditions; 20 | import org.openrewrite.Recipe; 21 | import org.openrewrite.TreeVisitor; 22 | import org.openrewrite.java.JavaIsoVisitor; 23 | import org.openrewrite.java.JavaTemplate; 24 | import org.openrewrite.java.JavadocVisitor; 25 | import org.openrewrite.java.MethodMatcher; 26 | import org.openrewrite.java.search.UsesType; 27 | import org.openrewrite.java.tree.Expression; 28 | import org.openrewrite.java.tree.J; 29 | import org.openrewrite.java.tree.JavaType; 30 | import org.openrewrite.java.tree.Javadoc; 31 | 32 | import java.time.Duration; 33 | import java.util.Arrays; 34 | import java.util.HashSet; 35 | import java.util.Set; 36 | 37 | public class LoggersNamedForEnclosingClass extends Recipe { 38 | 39 | private static final MethodMatcher LOGGERFACTORY_GETLOGGER = new MethodMatcher( 40 | "org.slf4j.LoggerFactory getLogger(Class)"); 41 | 42 | @Override 43 | public String getDisplayName() { 44 | return "Loggers should be named for their enclosing classes"; 45 | } 46 | 47 | @Override 48 | public String getDescription() { 49 | return "Ensure `LoggerFactory#getLogger(Class)` is called with the enclosing class as argument."; 50 | } 51 | 52 | @Override 53 | public Duration getEstimatedEffortPerOccurrence() { 54 | return Duration.ofMinutes(1); 55 | } 56 | 57 | @Override 58 | public Set getTags() { 59 | return new HashSet<>(Arrays.asList("RSPEC-S3416", "logging", "slf4j")); 60 | } 61 | 62 | @Override 63 | public TreeVisitor getVisitor() { 64 | return Preconditions.check(new UsesType<>("org.slf4j.LoggerFactory", null), new JavaIsoVisitor() { 65 | @Override 66 | protected JavadocVisitor getJavadocVisitor() { 67 | return new JavadocVisitor(this) { 68 | @Override 69 | public Javadoc visitDocComment(Javadoc.DocComment javadoc, ExecutionContext ctx) { 70 | return javadoc; 71 | } 72 | }; 73 | } 74 | 75 | @Override 76 | public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { 77 | J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); 78 | if (!LOGGERFACTORY_GETLOGGER.matches(mi)) { 79 | return mi; 80 | } 81 | 82 | J.ClassDeclaration firstEnclosingClass = getCursor().firstEnclosing(J.ClassDeclaration.class); 83 | if (firstEnclosingClass == null) { 84 | return mi; 85 | } 86 | 87 | J.Block block = getCursor().firstEnclosing(J.Block.class); 88 | if (block != null && block.isStatic()) { 89 | return mi; 90 | } 91 | 92 | String enclosingClazzName = firstEnclosingClass.getSimpleName() + ".class"; 93 | Expression firstArgument = mi.getArguments().get(0); 94 | if (firstArgument instanceof J.FieldAccess) { 95 | String argumentClazzName = ((J.FieldAccess) firstArgument).toString(); 96 | if (argumentClazzName.endsWith(".class") && !enclosingClazzName.equals(argumentClazzName)) { 97 | if (firstArgument.getType() instanceof JavaType.Parameterized) { 98 | maybeRemoveImport(((JavaType.Parameterized) firstArgument.getType()).getTypeParameters().get(0).toString()); 99 | } 100 | return replaceMethodArgument(mi, enclosingClazzName); 101 | } 102 | } else if (firstArgument instanceof J.MethodInvocation && 103 | "getClass".equals(((J.MethodInvocation) firstArgument).getName().toString())) { 104 | if (firstEnclosingClass.hasModifier(J.Modifier.Type.Final)) { 105 | return replaceMethodArgument(mi, enclosingClazzName); 106 | } 107 | } 108 | 109 | return mi; 110 | } 111 | 112 | private J.MethodInvocation replaceMethodArgument(J.MethodInvocation mi, String enclosingClazzName) { 113 | return JavaTemplate.builder("#{}").contextSensitive().build() 114 | .apply(getCursor(), mi.getCoordinates().replaceArguments(), enclosingClazzName); 115 | } 116 | }); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/slf4j/Slf4jLogShouldBeConstant.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.slf4j; 17 | 18 | import org.openrewrite.ExecutionContext; 19 | import org.openrewrite.Preconditions; 20 | import org.openrewrite.Recipe; 21 | import org.openrewrite.TreeVisitor; 22 | import org.openrewrite.internal.ListUtils; 23 | import org.openrewrite.internal.StringUtils; 24 | import org.openrewrite.java.JavaTemplate; 25 | import org.openrewrite.java.JavaVisitor; 26 | import org.openrewrite.java.MethodMatcher; 27 | import org.openrewrite.java.search.UsesMethod; 28 | import org.openrewrite.java.tree.Expression; 29 | import org.openrewrite.java.tree.J; 30 | import org.openrewrite.java.tree.JavaType; 31 | import org.openrewrite.java.tree.TypeUtils; 32 | 33 | import java.util.*; 34 | import java.util.regex.Pattern; 35 | 36 | public class Slf4jLogShouldBeConstant extends Recipe { 37 | 38 | private static final String SLF4J_FORMAT_SPECIFIER = "{}"; 39 | private static final Pattern SLF4J_FORMAT_SPECIFIER_PATTERN = Pattern.compile("\\{}"); 40 | private static final Pattern FORMAT_SPECIFIER_PATTERN = Pattern.compile("%[\\d.]*[dfscbBhHn%]"); 41 | 42 | // A regular expression that matches index specifiers like '%2$s', '%1$s', etc. 43 | private static final Pattern INDEXED_FORMAT_SPECIFIER_PATTERN = Pattern.compile("%(\\d+\\$)[a-zA-Z]"); 44 | private static final MethodMatcher SLF4J_LOG = new MethodMatcher("org.slf4j.Logger *(..)"); 45 | private static final MethodMatcher STRING_FORMAT = new MethodMatcher("java.lang.String format(..)"); 46 | private static final MethodMatcher STRING_VALUE_OF = new MethodMatcher("java.lang.String valueOf(..)"); 47 | private static final MethodMatcher OBJECT_TO_STRING = new MethodMatcher("java.lang.Object toString()"); 48 | 49 | @Override 50 | public String getDisplayName() { 51 | return "SLF4J logging statements should begin with constants"; 52 | } 53 | 54 | @Override 55 | public String getDescription() { 56 | return "Logging statements shouldn't begin with `String#format`, calls to `toString()`, etc."; 57 | } 58 | 59 | @Override 60 | public Set getTags() { 61 | return new HashSet<>(Arrays.asList("logging", "slf4j")); 62 | } 63 | 64 | @Override 65 | public TreeVisitor getVisitor() { 66 | return Preconditions.check(new UsesMethod<>(SLF4J_LOG), new JavaVisitor() { 67 | @Override 68 | public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { 69 | if (SLF4J_LOG.matches(method)) { 70 | String name = method.getSimpleName(); 71 | if ("trace".equals(name) || "debug".equals(name) || "info".equals(name) || "warn".equals(name) || "error".equals(name)) { 72 | List args = method.getArguments(); 73 | if (STRING_FORMAT.matches(args.get(0))) { 74 | J.MethodInvocation stringFormat = (J.MethodInvocation) args.get(0); 75 | 76 | if (!CompleteExceptionLogging.isStringLiteral(stringFormat.getArguments().get(0))) { 77 | return method; 78 | } 79 | 80 | String strFormat = Objects.requireNonNull(((J.Literal) stringFormat.getArguments().get(0)).getValue()).toString(); 81 | if (containsIndexFormatSpecifier(strFormat) || containsCombinedFormatSpecifiers(strFormat)) { 82 | return method; 83 | } 84 | String updatedStrFormat = replaceFormatSpecifier(strFormat, SLF4J_FORMAT_SPECIFIER); 85 | List stringFormatWithArgs = ListUtils.map(stringFormat.getArguments(), (n, arg) -> { 86 | if (n == 0) { 87 | J.Literal str = (J.Literal) arg; 88 | return str.withValue(updatedStrFormat) 89 | .withValueSource("\"" + updatedStrFormat + "\""); 90 | } 91 | return arg; 92 | }); 93 | List originalArgsWithoutMessage = args.subList(1, args.size()); 94 | return method.withArguments(ListUtils.concatAll(stringFormatWithArgs, originalArgsWithoutMessage)); 95 | } else if (STRING_VALUE_OF.matches(args.get(0))) { 96 | Expression valueOf = ((J.MethodInvocation) args.get(0)).getArguments().get(0); 97 | if (TypeUtils.isAssignableTo(JavaType.ShallowClass.build("java.lang.Throwable"), valueOf.getType())) { 98 | J.MethodInvocation m = JavaTemplate.builder("\"Exception\", #{any()}").contextSensitive().build() 99 | .apply(getCursor(), method.getCoordinates().replaceArguments(), valueOf); 100 | m = m.withSelect(method.getSelect()); 101 | return m; 102 | } 103 | } else if (OBJECT_TO_STRING.matches(args.get(0))) { 104 | Expression toString = ((J.MethodInvocation) args.get(0)).getSelect(); 105 | if (toString != null) { 106 | J.MethodInvocation m = JavaTemplate.builder("\"{}\", #{any()}").contextSensitive().build() 107 | .apply(getCursor(), method.getCoordinates().replaceArguments(), toString); 108 | m = m.withSelect(method.getSelect()); 109 | return m; 110 | } 111 | } 112 | } 113 | } 114 | return super.visitMethodInvocation(method, ctx); 115 | } 116 | }); 117 | } 118 | 119 | private boolean containsCombinedFormatSpecifiers(String str) { 120 | return FORMAT_SPECIFIER_PATTERN.matcher(str).find() && SLF4J_FORMAT_SPECIFIER_PATTERN.matcher(str).find(); 121 | } 122 | 123 | private static String replaceFormatSpecifier(String str, String replacement) { 124 | if (StringUtils.isNullOrEmpty(str)) { 125 | return str; 126 | } 127 | return FORMAT_SPECIFIER_PATTERN.matcher(str).replaceAll(replacement); 128 | } 129 | 130 | private static boolean containsIndexFormatSpecifier(String str) { 131 | if (StringUtils.isNullOrEmpty(str)) { 132 | return false; 133 | } 134 | return INDEXED_FORMAT_SPECIFIER_PATTERN.matcher(str).find(); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/org/openrewrite/java/logging/slf4j/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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 | @NullMarked 17 | @NonNullFields 18 | package org.openrewrite.java.logging.slf4j; 19 | 20 | import org.jspecify.annotations.NullMarked; 21 | import org.openrewrite.internal.lang.NonNullFields; 22 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/rewrite/category.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2024 the original author or authors. 3 | #

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 | #

8 | # https://docs.moderne.io/licensing/moderne-source-available-license 9 | #

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 | 17 | --- 18 | type: specs.openrewrite.org/v1beta/category 19 | name: Logging 20 | packageName: org.openrewrite.java.logging 21 | description: Enforce logging best practices and migrate between logging frameworks. 22 | --- 23 | type: specs.openrewrite.org/v1beta/category 24 | name: Logback 25 | packageName: org.openrewrite.java.logging.logback 26 | description: Recipes related to [`logback`](http://logback.qos.ch/documentation.html). 27 | --- 28 | type: specs.openrewrite.org/v1beta/category 29 | name: SLF4J 30 | packageName: org.openrewrite.java.logging.slf4j 31 | description: Recipes related to [Simple Logging Facade for Java (`SLF4J`)](http://www.slf4j.org/). 32 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/rewrite/classpath.tsv.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openrewrite/rewrite-logging-frameworks/1e14228375c9b918d5f82da7737d574b8eb111d7/src/main/resources/META-INF/rewrite/classpath.tsv.zip -------------------------------------------------------------------------------- /src/main/resources/META-INF/rewrite/logback.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2024 the original author or authors. 3 | #

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 | #

8 | # https://docs.moderne.io/licensing/moderne-source-available-license 9 | #

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 | 17 | --- 18 | type: specs.openrewrite.org/v1beta/recipe 19 | 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. 22 | tags: 23 | - logging 24 | - log4j 25 | - logback 26 | recipeList: 27 | ## Start by moving Log4J to the abstract of SLF4J, then having SLF4J implementation dependencies are on logback. 28 | - org.openrewrite.java.logging.slf4j.Log4jToSlf4j 29 | - org.openrewrite.java.logging.logback.Log4jAppenderToLogback 30 | - org.openrewrite.java.logging.logback.Log4jLayoutToLogback 31 | - org.openrewrite.java.dependencies.AddDependency: 32 | groupId: ch.qos.logback 33 | artifactId: logback-core 34 | version: latest.release 35 | onlyIfUsing: org.apache.logging.log4j.* 36 | - org.openrewrite.java.dependencies.AddDependency: 37 | groupId: ch.qos.logback 38 | artifactId: logback-classic 39 | version: latest.release 40 | onlyIfUsing: org.apache.logging.log4j.* 41 | - org.openrewrite.java.dependencies.AddDependency: 42 | groupId: org.slf4j 43 | artifactId: slf4j-api 44 | version: latest.release 45 | onlyIfUsing: org.apache.logging.log4j.* 46 | - org.openrewrite.java.dependencies.RemoveDependency: 47 | groupId: org.apache.logging.log4j 48 | artifactId: log4j-* 49 | -------------------------------------------------------------------------------- /src/test/java/org/openrewrite/java/logging/CatchBlockLogLevelTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.java.JavaParser; 21 | import org.openrewrite.test.RecipeSpec; 22 | import org.openrewrite.test.RewriteTest; 23 | 24 | import static org.openrewrite.java.Assertions.java; 25 | 26 | @SuppressWarnings("RedundantSlf4jDefinition") 27 | class CatchBlockLogLevelTest implements RewriteTest { 28 | 29 | @Override 30 | public void defaults(RecipeSpec spec) { 31 | spec.recipe(new CatchBlockLogLevel()); 32 | } 33 | 34 | @DocumentExample 35 | @Test 36 | void log4j1() { 37 | rewriteRun( 38 | spec -> spec.parser(JavaParser.fromJavaVersion().classpath("log4j")), 39 | //language=java 40 | java( 41 | """ 42 | import org.apache.log4j.Logger; 43 | 44 | class A { 45 | Logger log = Logger.getLogger(A.class); 46 | void test() { 47 | try { 48 | log.info("unchanged"); 49 | throw new RuntimeException(); 50 | } catch (Exception e) { 51 | log.info("Some context"); 52 | log.info("Caught exception", e); 53 | } 54 | } 55 | } 56 | """, 57 | """ 58 | import org.apache.log4j.Logger; 59 | 60 | class A { 61 | Logger log = Logger.getLogger(A.class); 62 | void test() { 63 | try { 64 | log.info("unchanged"); 65 | throw new RuntimeException(); 66 | } catch (Exception e) { 67 | log.warn("Some context"); 68 | log.error("Caught exception", e); 69 | } 70 | } 71 | } 72 | """ 73 | ) 74 | ); 75 | } 76 | 77 | @Test 78 | void log4j2() { 79 | rewriteRun( 80 | spec -> spec.parser(JavaParser.fromJavaVersion().classpath("log4j-core", "log4j-api")), 81 | //language=java 82 | java( 83 | """ 84 | import org.apache.logging.log4j.LogManager; 85 | import org.apache.logging.log4j.Logger; 86 | 87 | class A { 88 | Logger log = LogManager.getLogger(A.class); 89 | void test() { 90 | try { 91 | log.info("unchanged"); 92 | throw new RuntimeException(); 93 | } catch (Exception e) { 94 | log.info("Some context"); 95 | log.info("Caught exception", e); 96 | } 97 | } 98 | } 99 | """, 100 | """ 101 | import org.apache.logging.log4j.LogManager; 102 | import org.apache.logging.log4j.Logger; 103 | 104 | class A { 105 | Logger log = LogManager.getLogger(A.class); 106 | void test() { 107 | try { 108 | log.info("unchanged"); 109 | throw new RuntimeException(); 110 | } catch (Exception e) { 111 | log.warn("Some context"); 112 | log.error("Caught exception", e); 113 | } 114 | } 115 | } 116 | """ 117 | ) 118 | ); 119 | } 120 | 121 | @Test 122 | void slf4j() { 123 | rewriteRun( 124 | spec -> spec.parser(JavaParser.fromJavaVersion().classpath("slf4j-api")), 125 | //language=java 126 | java( 127 | """ 128 | import org.slf4j.Logger; 129 | import org.slf4j.LoggerFactory; 130 | 131 | class A { 132 | Logger log = LoggerFactory.getLogger(A.class); 133 | void test() { 134 | try { 135 | log.info("unchanged"); 136 | throw new RuntimeException(); 137 | } catch (Exception e) { 138 | log.info("Some context"); 139 | log.info("Caught exception", e); 140 | } 141 | } 142 | } 143 | """, 144 | """ 145 | import org.slf4j.Logger; 146 | import org.slf4j.LoggerFactory; 147 | 148 | class A { 149 | Logger log = LoggerFactory.getLogger(A.class); 150 | void test() { 151 | try { 152 | log.info("unchanged"); 153 | throw new RuntimeException(); 154 | } catch (Exception e) { 155 | log.warn("Some context"); 156 | log.error("Caught exception", e); 157 | } 158 | } 159 | } 160 | """ 161 | ) 162 | ); 163 | } 164 | 165 | @Test 166 | void logbackClassic() { 167 | rewriteRun( 168 | spec -> spec.parser(JavaParser.fromJavaVersion().classpath("slf4j-api", "logback-classic", "logback-core")), 169 | //language=java 170 | java( 171 | """ 172 | import ch.qos.logback.classic.Logger; 173 | import org.slf4j.LoggerFactory; 174 | 175 | class A { 176 | Logger log = (Logger) LoggerFactory.getLogger(A.class); 177 | void test() { 178 | try { 179 | log.info("unchanged"); 180 | throw new RuntimeException(); 181 | } catch (Exception e) { 182 | log.info("Some context"); 183 | log.info("Caught exception", e); 184 | } 185 | } 186 | } 187 | """, 188 | """ 189 | import ch.qos.logback.classic.Logger; 190 | import org.slf4j.LoggerFactory; 191 | 192 | class A { 193 | Logger log = (Logger) LoggerFactory.getLogger(A.class); 194 | void test() { 195 | try { 196 | log.info("unchanged"); 197 | throw new RuntimeException(); 198 | } catch (Exception e) { 199 | log.warn("Some context"); 200 | log.error("Caught exception", e); 201 | } 202 | } 203 | } 204 | """ 205 | ) 206 | ); 207 | 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/test/java/org/openrewrite/java/logging/ChangeLoggersToPrivateTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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 | } 198 | -------------------------------------------------------------------------------- /src/test/java/org/openrewrite/java/logging/SystemOutToLoggingTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.InMemoryExecutionContext; 21 | import org.openrewrite.Issue; 22 | import org.openrewrite.java.JavaParser; 23 | import org.openrewrite.test.RecipeSpec; 24 | import org.openrewrite.test.RewriteTest; 25 | import org.openrewrite.test.TypeValidation; 26 | 27 | import static org.openrewrite.java.Assertions.java; 28 | 29 | class SystemOutToLoggingTest implements RewriteTest { 30 | 31 | @Override 32 | public void defaults(RecipeSpec spec) { 33 | spec.parser(JavaParser.fromJavaVersion() 34 | .classpathFromResources(new InMemoryExecutionContext(), "slf4j-api-2.1.+", "lombok-1.18.+")); 35 | } 36 | 37 | @DocumentExample 38 | @Test 39 | void useSlf4j() { 40 | rewriteRun( 41 | spec -> spec.recipe(new SystemOutToLogging(null, "LOGGER", null, "debug")), 42 | //language=java 43 | java( 44 | """ 45 | import org.slf4j.Logger; 46 | class Test { 47 | int n; 48 | Logger logger; 49 | 50 | void test() { 51 | System.out.println("Oh " + n + " no"); 52 | } 53 | } 54 | """, 55 | """ 56 | import org.slf4j.Logger; 57 | class Test { 58 | int n; 59 | Logger logger; 60 | 61 | void test() { 62 | logger.debug("Oh {} no", n); 63 | } 64 | } 65 | """ 66 | ) 67 | ); 68 | } 69 | 70 | @Test 71 | void inRunnable() { 72 | rewriteRun( 73 | spec -> spec.recipe(new SystemOutToLogging(null, "LOGGER", null, "debug")), 74 | //language=java 75 | java( 76 | """ 77 | import org.slf4j.Logger; 78 | class Test { 79 | Logger logger; 80 | 81 | void test() { 82 | Runnable r = () -> System.out.println("single"); 83 | } 84 | } 85 | """, 86 | """ 87 | import org.slf4j.Logger; 88 | class Test { 89 | Logger logger; 90 | 91 | void test() { 92 | Runnable r = () -> logger.debug("single"); 93 | } 94 | } 95 | """ 96 | ) 97 | ); 98 | } 99 | 100 | @Test 101 | @Issue("https://github.com/openrewrite/rewrite-logging-frameworks/issues/114") 102 | void supportLombokLogAnnotations() { 103 | rewriteRun( 104 | spec -> spec.recipe(new SystemOutToLogging(null, null, null, "info")) 105 | .typeValidationOptions(TypeValidation.builder().identifiers(false).build()), 106 | //language=java 107 | java( 108 | """ 109 | import lombok.extern.slf4j.Slf4j; 110 | @Slf4j 111 | class Test { 112 | int n; 113 | 114 | void test() { 115 | System.out.println("Oh " + n + " no"); 116 | } 117 | } 118 | """, 119 | """ 120 | import lombok.extern.slf4j.Slf4j; 121 | @Slf4j 122 | class Test { 123 | int n; 124 | 125 | void test() { 126 | log.info("Oh {} no", n); 127 | } 128 | } 129 | """ 130 | ) 131 | ); 132 | } 133 | 134 | @Test 135 | @Issue("https://github.com/openrewrite/rewrite-logging-frameworks/issues/125") 136 | void doNotDeleteFile() { 137 | rewriteRun( 138 | spec -> spec 139 | .recipe(new SystemOutToLogging(true, "log", "JUL", null)), 140 | //language=java 141 | java( 142 | """ 143 | class Foo { 144 | void bar() { 145 | System.out.println("Test"); 146 | } 147 | } 148 | """, 149 | """ 150 | import java.util.logging.Level; 151 | import java.util.logging.LogManager; 152 | import java.util.logging.Logger; 153 | 154 | class Foo { 155 | private static final Logger log = LogManager.getLogManager().getLogger("Foo"); 156 | 157 | void bar() { 158 | log.log(Level.INFO, "Test"); 159 | } 160 | } 161 | """ 162 | ) 163 | ); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/test/java/org/openrewrite/java/logging/jul/LoggerLevelArgumentToMethodTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.jul; 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 LoggerLevelArgumentToMethodTest implements RewriteTest { 26 | 27 | @Override 28 | public void defaults(RecipeSpec spec) { 29 | spec.recipe(new LoggerLevelArgumentToMethodRecipes()); 30 | } 31 | 32 | @DocumentExample 33 | @Test 34 | void replaceLevelArguments() { 35 | rewriteRun( 36 | //language=java 37 | java( 38 | """ 39 | import java.util.logging.Level; 40 | import java.util.logging.Logger; 41 | 42 | class Test { 43 | void test(Logger logger, String message) { 44 | logger.log(Level.FINEST, message); 45 | logger.log(Level.FINER, message); 46 | logger.log(Level.FINE, message); 47 | logger.log(Level.INFO, message); 48 | logger.log(Level.WARNING, message); 49 | logger.log(Level.SEVERE, message); 50 | logger.log(Level.CONFIG, message); 51 | } 52 | } 53 | """, 54 | """ 55 | import java.util.logging.Logger; 56 | 57 | class Test { 58 | void test(Logger logger, String message) { 59 | logger.finest(message); 60 | logger.finer(message); 61 | logger.fine(message); 62 | logger.info(message); 63 | logger.warning(message); 64 | logger.severe(message); 65 | logger.config(message); 66 | } 67 | } 68 | """ 69 | ) 70 | ); 71 | } 72 | 73 | @Test 74 | void replaceLevelArgumentsWithSupplier() { 75 | rewriteRun( 76 | //language=java 77 | java( 78 | """ 79 | import java.util.function.Supplier; 80 | import java.util.logging.Level; 81 | import java.util.logging.Logger; 82 | 83 | class Test { 84 | void test(Logger logger, Supplier message) { 85 | logger.log(Level.FINEST, message); 86 | logger.log(Level.FINER, message); 87 | logger.log(Level.FINE, message); 88 | logger.log(Level.INFO, message); 89 | logger.log(Level.WARNING, message); 90 | logger.log(Level.SEVERE, message); 91 | logger.log(Level.CONFIG, message); 92 | } 93 | } 94 | """, 95 | """ 96 | import java.util.function.Supplier; 97 | import java.util.logging.Logger; 98 | 99 | class Test { 100 | void test(Logger logger, Supplier message) { 101 | logger.finest(message); 102 | logger.finer(message); 103 | logger.fine(message); 104 | logger.info(message); 105 | logger.warning(message); 106 | logger.severe(message); 107 | logger.config(message); 108 | } 109 | } 110 | """ 111 | ) 112 | ); 113 | } 114 | 115 | @Test 116 | void noReplacementMethod() { 117 | rewriteRun( 118 | //language=java 119 | java( 120 | """ 121 | import java.util.logging.Level; 122 | import java.util.logging.Logger; 123 | 124 | class Test { 125 | void test(Logger logger, String message) { 126 | logger.log(Level.ALL, message); 127 | logger.log(Level.OFF, message); 128 | } 129 | } 130 | """ 131 | ) 132 | ); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/test/java/org/openrewrite/java/logging/log4j/CommonsLoggingToLog4jTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.log4j; 17 | 18 | import org.junit.jupiter.api.Test; 19 | import org.openrewrite.DocumentExample; 20 | import org.openrewrite.InMemoryExecutionContext; 21 | import org.openrewrite.java.JavaParser; 22 | import org.openrewrite.test.RecipeSpec; 23 | import org.openrewrite.test.RewriteTest; 24 | import org.openrewrite.test.TypeValidation; 25 | 26 | import static org.openrewrite.java.Assertions.java; 27 | 28 | class CommonsLoggingToLog4jTest implements RewriteTest { 29 | @Override 30 | public void defaults(RecipeSpec spec) { 31 | spec.recipeFromResource("/META-INF/rewrite/log4j.yml", 32 | "org.openrewrite.java.logging.log4j.CommonsLoggingToLog4j") 33 | .parser(JavaParser.fromJavaVersion() 34 | .classpathFromResources(new InMemoryExecutionContext(), "log4j-api-2.+", "commons-logging-1.3.+", "lombok-1.18.+")); 35 | } 36 | 37 | @DocumentExample 38 | @Test 39 | void loggerFactoryToLogManager() { 40 | // language=java 41 | rewriteRun( 42 | java( 43 | """ 44 | import org.apache.commons.logging.LogFactory; 45 | import org.apache.commons.logging.Log; 46 | 47 | class Test { 48 | Log log1 = LogFactory.getLog(Test.class); 49 | Log log2 = LogFactory.getLog("Test"); 50 | Log log3 = LogFactory.getFactory().getInstance(Test.class); 51 | Log log4 = LogFactory.getFactory().getInstance("Test"); 52 | } 53 | """, 54 | """ 55 | import org.apache.logging.log4j.LogManager; 56 | import org.apache.logging.log4j.Logger; 57 | 58 | class Test { 59 | Logger log1 = LogManager.getLogger(Test.class); 60 | Logger log2 = LogManager.getLogger("Test"); 61 | Logger log3 = LogManager.getLogger(Test.class); 62 | Logger log4 = LogManager.getLogger("Test"); 63 | } 64 | """ 65 | ) 66 | ); 67 | } 68 | 69 | @Test 70 | void changeLombokLogAnnotation() { 71 | // language=java 72 | rewriteRun( 73 | spec -> spec.typeValidationOptions(TypeValidation.builder() 74 | .identifiers(false) 75 | .methodInvocations(false) 76 | .build()), 77 | java( 78 | """ 79 | import lombok.extern.apachecommons.CommonsLog; 80 | 81 | @CommonsLog 82 | class Test { 83 | void method() { 84 | log.info("uh oh"); 85 | } 86 | } 87 | """, 88 | """ 89 | import lombok.extern.log4j.Log4j2; 90 | 91 | @Log4j2 92 | class Test { 93 | void method() { 94 | log.info("uh oh"); 95 | } 96 | } 97 | """ 98 | ) 99 | ); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/test/java/org/openrewrite/java/logging/log4j/ConvertJulEnteringTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.log4j; 17 | 18 | import org.junit.jupiter.api.Test; 19 | import org.openrewrite.DocumentExample; 20 | import org.openrewrite.InMemoryExecutionContext; 21 | import org.openrewrite.java.ChangeType; 22 | import org.openrewrite.java.JavaParser; 23 | import org.openrewrite.test.RecipeSpec; 24 | import org.openrewrite.test.RewriteTest; 25 | 26 | import static org.openrewrite.java.Assertions.java; 27 | 28 | class ConvertJulEnteringTest implements RewriteTest { 29 | @Override 30 | public void defaults(RecipeSpec spec) { 31 | spec.recipes(new ConvertJulEntering(), 32 | new ChangeType("java.util.logging.Logger", "org.apache.logging.log4j.Logger", true)) 33 | .parser(JavaParser.fromJavaVersion() 34 | .classpathFromResources(new InMemoryExecutionContext(), "log4j-api-2.+")); 35 | } 36 | 37 | @Test 38 | @DocumentExample 39 | void enteringToTraceEntry() { 40 | rewriteRun( 41 | // language=java 42 | java( 43 | """ 44 | import java.util.logging.Logger; 45 | 46 | class Test { 47 | void method(Logger logger) { 48 | logger.entering("Test", "method"); 49 | logger.entering("Test", "method", "param"); 50 | logger.entering("Test", "method", new Object[]{"param1", "param2"}); 51 | } 52 | } 53 | """, 54 | """ 55 | import org.apache.logging.log4j.Logger; 56 | 57 | class Test { 58 | void method(Logger logger) { 59 | logger.traceEntry(); 60 | logger.traceEntry(null, "param"); 61 | logger.traceEntry(null, new Object[]{"param1", "param2"}); 62 | } 63 | } 64 | """ 65 | ) 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/org/openrewrite/java/logging/log4j/ConvertJulExitingTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.log4j; 17 | 18 | import org.junit.jupiter.api.Test; 19 | import org.openrewrite.DocumentExample; 20 | import org.openrewrite.InMemoryExecutionContext; 21 | import org.openrewrite.java.ChangeType; 22 | import org.openrewrite.java.JavaParser; 23 | import org.openrewrite.test.RecipeSpec; 24 | import org.openrewrite.test.RewriteTest; 25 | 26 | import static org.openrewrite.java.Assertions.java; 27 | 28 | class ConvertJulExitingTest implements RewriteTest { 29 | @Override 30 | public void defaults(RecipeSpec spec) { 31 | spec.recipes(new ConvertJulExiting(), 32 | new ChangeType("java.util.logging.Logger", "org.apache.logging.log4j.Logger", true)) 33 | .parser(JavaParser.fromJavaVersion() 34 | .classpathFromResources(new InMemoryExecutionContext(), "log4j-api-2.+")); 35 | } 36 | 37 | @Test 38 | @DocumentExample 39 | void exitingToTraceExit() { 40 | rewriteRun( 41 | // language=java 42 | java( 43 | """ 44 | import java.util.logging.Logger; 45 | 46 | class Test { 47 | void method(Logger logger) { 48 | logger.exiting("Test", "method"); 49 | logger.exiting("Test", "method", "result"); 50 | } 51 | } 52 | """, 53 | """ 54 | import org.apache.logging.log4j.Logger; 55 | 56 | class Test { 57 | void method(Logger logger) { 58 | logger.traceExit(); 59 | logger.traceExit("result"); 60 | } 61 | } 62 | """ 63 | ) 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/org/openrewrite/java/logging/log4j/JulToLog4jTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.log4j; 17 | 18 | import org.junit.jupiter.api.Test; 19 | import org.openrewrite.DocumentExample; 20 | import org.openrewrite.InMemoryExecutionContext; 21 | import org.openrewrite.java.JavaParser; 22 | import org.openrewrite.test.RecipeSpec; 23 | import org.openrewrite.test.RewriteTest; 24 | import org.openrewrite.test.TypeValidation; 25 | 26 | import static org.openrewrite.java.Assertions.java; 27 | 28 | class JulToLog4jTest implements RewriteTest { 29 | @Override 30 | public void defaults(RecipeSpec spec) { 31 | spec.recipeFromResource("/META-INF/rewrite/log4j.yml", "org.openrewrite.java.logging.log4j.JulToLog4j") 32 | .parser(JavaParser.fromJavaVersion() 33 | .classpathFromResources(new InMemoryExecutionContext(), "log4j-api-2.+", "lombok-1.18.+")); 34 | } 35 | 36 | @Test 37 | @DocumentExample 38 | void simpleLoggerCalls() { 39 | rewriteRun( 40 | // language=java 41 | java( 42 | """ 43 | import java.util.logging.Level; 44 | import java.util.logging.Logger; 45 | 46 | class Test { 47 | void method(Logger logger) { 48 | logger.config("Hello"); 49 | logger.config(() -> "Hello"); 50 | logger.fine("Hello"); 51 | logger.fine(() -> "Hello"); 52 | logger.finer("Hello"); 53 | logger.finer(() -> "Hello"); 54 | logger.finest("Hello"); 55 | logger.finest(() -> "Hello"); 56 | logger.info("Hello"); 57 | logger.info(() -> "Hello"); 58 | logger.severe("Hello"); 59 | logger.severe(() -> "Hello"); 60 | logger.warning("Hello"); 61 | logger.warning(() -> "Hello"); 62 | 63 | logger.log(Level.INFO, "Hello"); 64 | logger.log(Level.INFO, () -> "Hello"); 65 | } 66 | } 67 | """, 68 | """ 69 | import org.apache.logging.log4j.Logger; 70 | 71 | class Test { 72 | void method(Logger logger) { 73 | logger.info("Hello"); 74 | logger.info(() -> "Hello"); 75 | logger.debug("Hello"); 76 | logger.debug(() -> "Hello"); 77 | logger.trace("Hello"); 78 | logger.trace(() -> "Hello"); 79 | logger.trace("Hello"); 80 | logger.trace(() -> "Hello"); 81 | logger.info("Hello"); 82 | logger.info(() -> "Hello"); 83 | logger.error("Hello"); 84 | logger.error(() -> "Hello"); 85 | logger.warn("Hello"); 86 | logger.warn(() -> "Hello"); 87 | 88 | logger.info("Hello"); 89 | logger.info(() -> "Hello"); 90 | } 91 | } 92 | """ 93 | ) 94 | ); 95 | } 96 | 97 | @Test 98 | void loggerToLogManager() { 99 | rewriteRun( 100 | // language=java 101 | java( 102 | """ 103 | import java.util.logging.Logger; 104 | 105 | class Test { 106 | Logger log = Logger.getLogger("Test"); 107 | } 108 | """, 109 | """ 110 | import org.apache.logging.log4j.LogManager; 111 | import org.apache.logging.log4j.Logger; 112 | 113 | class Test { 114 | Logger log = LogManager.getLogger("Test"); 115 | } 116 | """ 117 | ) 118 | ); 119 | } 120 | 121 | @Test 122 | void changeLombokLogAnnotation() { 123 | rewriteRun(spec -> spec.typeValidationOptions(TypeValidation.builder() 124 | .identifiers(false) 125 | .methodInvocations(false) 126 | .build()), 127 | // language=java 128 | java( 129 | """ 130 | import lombok.extern.java.Log; 131 | 132 | @Log 133 | class Test { 134 | void method() { 135 | log.info("uh oh"); 136 | } 137 | } 138 | """, 139 | """ 140 | import lombok.extern.log4j.Log4j2; 141 | 142 | @Log4j2 143 | class Test { 144 | void method() { 145 | log.info("uh oh"); 146 | } 147 | } 148 | """ 149 | ) 150 | ); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/test/java/org/openrewrite/java/logging/log4j/LoggingExceptionConcatenationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.log4j; 17 | 18 | import org.junit.jupiter.api.Test; 19 | import org.openrewrite.DocumentExample; 20 | import org.openrewrite.java.JavaParser; 21 | import org.openrewrite.test.RecipeSpec; 22 | import org.openrewrite.test.RewriteTest; 23 | 24 | import static org.openrewrite.java.Assertions.java; 25 | 26 | class LoggingExceptionConcatenationTest implements RewriteTest { 27 | 28 | @Override 29 | public void defaults(RecipeSpec spec) { 30 | spec.recipe(new LoggingExceptionConcatenationRecipe()) 31 | .parser(JavaParser.fromJavaVersion().classpath("log4j-api")); 32 | } 33 | 34 | @DocumentExample 35 | @Test 36 | void loggingException() { 37 | rewriteRun( 38 | //language=java 39 | java( 40 | """ 41 | import org.apache.logging.log4j.Logger; 42 | 43 | class Test { 44 | void test(Logger logger, RuntimeException e) { 45 | logger.error("test" + e); 46 | } 47 | } 48 | """, 49 | """ 50 | import org.apache.logging.log4j.Logger; 51 | 52 | class Test { 53 | void test(Logger logger, RuntimeException e) { 54 | logger.error("test", e); 55 | } 56 | } 57 | """ 58 | ) 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/org/openrewrite/java/logging/log4j/PrependRandomNameTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.log4j; 17 | 18 | import org.junit.jupiter.api.Test; 19 | import org.openrewrite.DocumentExample; 20 | import org.openrewrite.InMemoryExecutionContext; 21 | import org.openrewrite.java.JavaParser; 22 | import org.openrewrite.test.RecipeSpec; 23 | import org.openrewrite.test.RewriteTest; 24 | 25 | import static org.openrewrite.java.Assertions.java; 26 | 27 | class PrependRandomNameTest implements RewriteTest { 28 | 29 | @Override 30 | public void defaults(RecipeSpec spec) { 31 | spec.recipe(new PrependRandomName(2048)) 32 | .parser(JavaParser.fromJavaVersion() 33 | .classpathFromResources(new InMemoryExecutionContext(), "log4j-1.2.+")); 34 | } 35 | 36 | @DocumentExample 37 | @Test 38 | void prependRandomName() { 39 | //language=java 40 | rewriteRun( 41 | java( 42 | """ 43 | import org.apache.log4j.Logger; 44 | 45 | class Test { 46 | Logger logger; 47 | void test() { 48 | logger.info("test"); 49 | } 50 | } 51 | """, 52 | """ 53 | import org.apache.log4j.Logger; 54 | 55 | class Test { 56 | Logger logger; 57 | void test() { 58 | logger.info(" test"); 59 | } 60 | } 61 | """ 62 | ) 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/org/openrewrite/java/logging/logback/ConfigureLoggerLevelTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.logback; 17 | 18 | import org.junit.jupiter.api.Test; 19 | import org.openrewrite.DocumentExample; 20 | import org.openrewrite.test.RewriteTest; 21 | 22 | import static org.openrewrite.xml.Assertions.xml; 23 | 24 | class ConfigureLoggerLevelTest implements RewriteTest { 25 | 26 | @DocumentExample 27 | @Test 28 | void editExistingLogger() { 29 | rewriteRun( 30 | spec -> spec.recipe(new ConfigureLoggerLevel("org.springframework", ConfigureLoggerLevel.LogLevel.off)), 31 | xml(//language=xml 32 | """ 33 | 34 | 35 | 36 | 37 | %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | """, 47 | //language=xml 48 | """ 49 | 50 | 51 | 52 | 53 | %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | """, 63 | spec -> spec.path("logback.xml")) 64 | ); 65 | } 66 | 67 | @Test 68 | void addNewLogger() { 69 | rewriteRun( 70 | spec -> spec.recipe(new ConfigureLoggerLevel("com.example.MyClass", ConfigureLoggerLevel.LogLevel.off)), 71 | xml(//language=xml 72 | """ 73 | 74 | 75 | 76 | 77 | %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | """, 87 | //language=xml 88 | """ 89 | 90 | 91 | 92 | 93 | %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | """, 104 | spec -> spec.path("logback.xml")) 105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/test/java/org/openrewrite/java/logging/logback/Log4jAppenderToLogbackTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.logback; 17 | 18 | import org.junit.jupiter.api.Test; 19 | import org.openrewrite.DocumentExample; 20 | import org.openrewrite.InMemoryExecutionContext; 21 | import org.openrewrite.java.JavaParser; 22 | import org.openrewrite.test.RecipeSpec; 23 | import org.openrewrite.test.RewriteTest; 24 | 25 | import static org.openrewrite.java.Assertions.java; 26 | 27 | class Log4jAppenderToLogbackTest implements RewriteTest { 28 | 29 | @Override 30 | public void defaults(RecipeSpec spec) { 31 | spec.recipe(new Log4jAppenderToLogback()) 32 | .parser(JavaParser.fromJavaVersion() 33 | .classpathFromResources(new InMemoryExecutionContext(), "log4j-1.2.+")); 34 | } 35 | 36 | @DocumentExample 37 | @Test 38 | void appenderMigration() { 39 | //language=java 40 | rewriteRun( 41 | java( 42 | """ 43 | import org.apache.log4j.AppenderSkeleton; 44 | import org.apache.log4j.spi.LoggingEvent; 45 | 46 | class TrivialAppender extends AppenderSkeleton { 47 | @Override 48 | protected void append(LoggingEvent event) { 49 | String s = this.layout.format(event); 50 | System.out.println(s); 51 | } 52 | 53 | @Override 54 | public void close() { 55 | // nothing to do 56 | } 57 | 58 | @Override 59 | public boolean requiresLayout() { 60 | return true; 61 | } 62 | } 63 | """, 64 | """ 65 | import ch.qos.logback.classic.spi.ILoggingEvent; 66 | import ch.qos.logback.core.AppenderBase; 67 | 68 | class TrivialAppender extends AppenderBase { 69 | @Override 70 | protected void append(ILoggingEvent event) { 71 | String s = this.layout.doLayout(event); 72 | System.out.println(s); 73 | } 74 | } 75 | """ 76 | ) 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/org/openrewrite/java/logging/logback/Log4jLayoutToLogbackTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.logback; 17 | 18 | import org.junit.jupiter.api.Test; 19 | import org.openrewrite.DocumentExample; 20 | import org.openrewrite.InMemoryExecutionContext; 21 | import org.openrewrite.java.JavaParser; 22 | import org.openrewrite.test.RecipeSpec; 23 | import org.openrewrite.test.RewriteTest; 24 | 25 | import static org.openrewrite.java.Assertions.java; 26 | 27 | class Log4jLayoutToLogbackTest implements RewriteTest { 28 | 29 | @Override 30 | public void defaults(RecipeSpec spec) { 31 | spec.recipe(new Log4jLayoutToLogback()) 32 | .parser(JavaParser.fromJavaVersion() 33 | .classpathFromResources(new InMemoryExecutionContext(), "log4j-1.2.+")); 34 | } 35 | 36 | @DocumentExample 37 | @Test 38 | void layoutMigration() { 39 | //language=java 40 | rewriteRun( 41 | java( 42 | """ 43 | import org.apache.log4j.Layout; 44 | import org.apache.log4j.spi.LoggingEvent; 45 | 46 | class TrivialLayout extends Layout { 47 | 48 | @Override 49 | public void activateOptions() { 50 | // there are no options to activate 51 | } 52 | 53 | @Override 54 | public String format(LoggingEvent event) { 55 | return event.getRenderedMessage(); 56 | } 57 | 58 | @Override 59 | public boolean ignoresThrowable() { 60 | return true; 61 | } 62 | } 63 | """, 64 | """ 65 | import ch.qos.logback.classic.spi.ILoggingEvent; 66 | import ch.qos.logback.core.LayoutBase; 67 | 68 | class TrivialLayout extends LayoutBase { 69 | 70 | @Override 71 | public String doLayout(ILoggingEvent event) { 72 | return event.getMessage(); 73 | } 74 | } 75 | """ 76 | ) 77 | ); 78 | } 79 | 80 | @Test 81 | void layoutMigrationWithLifeCycle() { 82 | //language=java 83 | rewriteRun( 84 | java( 85 | """ 86 | import org.apache.log4j.Layout; 87 | import org.apache.log4j.spi.LoggingEvent; 88 | 89 | class TrivialLayout extends Layout { 90 | @Override 91 | public void activateOptions() { 92 | System.out.println("starting..."); 93 | } 94 | 95 | @Override 96 | public String format(LoggingEvent event) { 97 | return event.getRenderedMessage(); 98 | } 99 | 100 | @Override 101 | public boolean ignoresThrowable() { 102 | return true; 103 | } 104 | } 105 | """, 106 | """ 107 | import ch.qos.logback.classic.spi.ILoggingEvent; 108 | import ch.qos.logback.core.LayoutBase; 109 | import ch.qos.logback.core.spi.LifeCycle; 110 | 111 | class TrivialLayout extends LayoutBase implements LifeCycle { 112 | @Override 113 | public void start() { 114 | System.out.println("starting..."); 115 | } 116 | 117 | @Override 118 | public String doLayout(ILoggingEvent event) { 119 | return event.getMessage(); 120 | } 121 | } 122 | """ 123 | ) 124 | ); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/test/java/org/openrewrite/java/logging/slf4j/ChangeLogLevelTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.slf4j; 17 | 18 | import org.junit.jupiter.api.Test; 19 | import org.openrewrite.DocumentExample; 20 | import org.openrewrite.InMemoryExecutionContext; 21 | import org.openrewrite.java.JavaParser; 22 | import org.openrewrite.test.RecipeSpec; 23 | import org.openrewrite.test.RewriteTest; 24 | 25 | import static org.openrewrite.java.Assertions.java; 26 | 27 | class ChangeLogLevelTest implements RewriteTest { 28 | 29 | @Override 30 | public void defaults(RecipeSpec spec) { 31 | spec.recipe(new ChangeLogLevel(ChangeLogLevel.Level.INFO, ChangeLogLevel.Level.DEBUG, "LaunchDarkly")) 32 | .parser(JavaParser.fromJavaVersion() 33 | .classpathFromResources(new InMemoryExecutionContext(), "slf4j-api-2.1.+")); 34 | } 35 | 36 | @DocumentExample 37 | @Test 38 | void basic() { 39 | rewriteRun( 40 | //language=java 41 | java( 42 | """ 43 | import org.slf4j.Logger; 44 | import org.slf4j.LoggerFactory; 45 | 46 | class Test { 47 | private static final Logger log = LoggerFactory.getLogger(Test.class); 48 | 49 | void test() { 50 | log.info("LaunchDarkly Hello"); 51 | } 52 | } 53 | """, 54 | """ 55 | import org.slf4j.Logger; 56 | import org.slf4j.LoggerFactory; 57 | 58 | class Test { 59 | private static final Logger log = LoggerFactory.getLogger(Test.class); 60 | 61 | void test() { 62 | log.debug("LaunchDarkly Hello"); 63 | } 64 | } 65 | """ 66 | ) 67 | ); 68 | } 69 | 70 | @Test 71 | void concatenatedString() { 72 | rewriteRun( 73 | //language=java 74 | java( 75 | """ 76 | import org.slf4j.Logger; 77 | import org.slf4j.LoggerFactory; 78 | 79 | class Test { 80 | private static final Logger log = LoggerFactory.getLogger(Test.class); 81 | 82 | void test() { 83 | log.info("LaunchDarkly " + 1 + "Hello"); 84 | } 85 | } 86 | """, 87 | """ 88 | import org.slf4j.Logger; 89 | import org.slf4j.LoggerFactory; 90 | 91 | class Test { 92 | private static final Logger log = LoggerFactory.getLogger(Test.class); 93 | 94 | void test() { 95 | log.debug("LaunchDarkly " + 1 + "Hello"); 96 | } 97 | } 98 | """ 99 | ) 100 | ); 101 | } 102 | 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/test/java/org/openrewrite/java/logging/slf4j/CommonsLoggingToSlf4j1Test.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.slf4j; 17 | 18 | import org.junit.jupiter.api.Test; 19 | import org.openrewrite.DocumentExample; 20 | import org.openrewrite.InMemoryExecutionContext; 21 | import org.openrewrite.config.Environment; 22 | import org.openrewrite.java.JavaParser; 23 | import org.openrewrite.test.RecipeSpec; 24 | import org.openrewrite.test.RewriteTest; 25 | import org.openrewrite.test.TypeValidation; 26 | 27 | import static org.openrewrite.java.Assertions.java; 28 | 29 | class CommonsLoggingToSlf4j1Test implements RewriteTest { 30 | 31 | @Override 32 | public void defaults(RecipeSpec spec) { 33 | spec.typeValidationOptions(TypeValidation.builder().build()) 34 | .recipe(Environment.builder() 35 | .scanRuntimeClasspath("org.openrewrite.java.logging") 36 | .build() 37 | .activateRecipes("org.openrewrite.java.logging.slf4j.CommonsLogging1ToSlf4j1")) 38 | .parser(JavaParser.fromJavaVersion() 39 | .classpathFromResources(new InMemoryExecutionContext(), "commons-logging-1.3.+", "slf4j-api-2.1.+", "lombok-1.18.+")); 40 | } 41 | 42 | @DocumentExample 43 | @Test 44 | void useLoggerFactory() { 45 | //language=java 46 | rewriteRun( 47 | java( 48 | """ 49 | import org.apache.commons.logging.LogFactory; 50 | import org.apache.commons.logging.Log; 51 | 52 | class Test { 53 | Log logger0 = LogFactory.getLog(Test.class); 54 | Log logger1 = LogFactory.getLog("foobar"); 55 | Log logger2 = LogFactory.getFactory().getInstance(Test.class); 56 | Log logger3 = LogFactory.getFactory().getInstance("foobar"); 57 | } 58 | """, 59 | """ 60 | import org.slf4j.Logger; 61 | import org.slf4j.LoggerFactory; 62 | 63 | class Test { 64 | Logger logger0 = LoggerFactory.getLogger(Test.class); 65 | Logger logger1 = LoggerFactory.getLogger("foobar"); 66 | Logger logger2 = LoggerFactory.getLogger(Test.class); 67 | Logger logger3 = LoggerFactory.getLogger("foobar"); 68 | } 69 | """ 70 | ) 71 | ); 72 | } 73 | 74 | @Test 75 | void staticFinalLoggerIsStaticFinal() { 76 | //language=java 77 | rewriteRun( 78 | java( 79 | """ 80 | import org.apache.commons.logging.LogFactory; 81 | import org.apache.commons.logging.Log; 82 | 83 | class Test { 84 | private static final Log logger0 = LogFactory.getLog(Test.class); 85 | private static final Log logger1 = LogFactory.getLog("foobar"); 86 | private static final Log logger2 = LogFactory.getFactory().getInstance(Test.class); 87 | private static final Log logger3 = LogFactory.getFactory().getInstance("foobar"); 88 | } 89 | """, 90 | """ 91 | import org.slf4j.Logger; 92 | import org.slf4j.LoggerFactory; 93 | 94 | class Test { 95 | private static final Logger logger0 = LoggerFactory.getLogger(Test.class); 96 | private static final Logger logger1 = LoggerFactory.getLogger("foobar"); 97 | private static final Logger logger2 = LoggerFactory.getLogger(Test.class); 98 | private static final Logger logger3 = LoggerFactory.getLogger("foobar"); 99 | } 100 | """ 101 | ) 102 | ); 103 | } 104 | 105 | @Test 106 | void logLevelFatalToError() { 107 | //language=java 108 | rewriteRun( 109 | java( 110 | """ 111 | import org.apache.commons.logging.Log; 112 | 113 | class Test { 114 | static void method(Log logger) { 115 | if (logger.isFatalEnabled()) { 116 | logger.fatal("uh oh"); 117 | } 118 | } 119 | } 120 | """, 121 | """ 122 | import org.slf4j.Logger; 123 | 124 | class Test { 125 | static void method(Logger logger) { 126 | if (logger.isErrorEnabled()) { 127 | logger.error("uh oh"); 128 | } 129 | } 130 | } 131 | """ 132 | ) 133 | ); 134 | } 135 | 136 | @Test 137 | void changeLombokLogAnnotation() { 138 | //language=java 139 | rewriteRun( 140 | spec -> spec.typeValidationOptions(TypeValidation.builder().identifiers(false).methodInvocations(false).build()), 141 | java( 142 | """ 143 | import lombok.extern.apachecommons.CommonsLog; 144 | 145 | @CommonsLog 146 | class Test { 147 | void method() { 148 | log.info("uh oh"); 149 | } 150 | } 151 | """, 152 | """ 153 | import lombok.extern.slf4j.Slf4j; 154 | 155 | @Slf4j 156 | class Test { 157 | void method() { 158 | log.info("uh oh"); 159 | } 160 | } 161 | """ 162 | ) 163 | ); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/test/java/org/openrewrite/java/logging/slf4j/Log4j1ToSlf4j1Test.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.slf4j; 17 | 18 | import org.junit.jupiter.api.Test; 19 | import org.openrewrite.DocumentExample; 20 | import org.openrewrite.InMemoryExecutionContext; 21 | import org.openrewrite.Issue; 22 | import org.openrewrite.config.Environment; 23 | import org.openrewrite.java.JavaParser; 24 | import org.openrewrite.test.RecipeSpec; 25 | import org.openrewrite.test.RewriteTest; 26 | 27 | import static org.openrewrite.java.Assertions.java; 28 | 29 | class Log4j1ToSlf4j1Test implements RewriteTest { 30 | 31 | @Override 32 | public void defaults(RecipeSpec spec) { 33 | spec.recipe(Environment.builder() 34 | .scanRuntimeClasspath("org.openrewrite.java.logging") 35 | .build() 36 | .activateRecipes("org.openrewrite.java.logging.slf4j.Log4j1ToSlf4j1")) 37 | .parser(JavaParser.fromJavaVersion() 38 | .classpathFromResources(new InMemoryExecutionContext(), "log4j-1.2.+")); 39 | } 40 | 41 | @DocumentExample 42 | @Test 43 | void useLoggerFactory() { 44 | //language=java 45 | rewriteRun( 46 | java( 47 | """ 48 | import org.apache.log4j.Logger; 49 | 50 | class Test { 51 | Logger logger0 = Logger.getLogger(Test.class); 52 | } 53 | """, 54 | """ 55 | import org.slf4j.Logger; 56 | import org.slf4j.LoggerFactory; 57 | 58 | class Test { 59 | Logger logger0 = LoggerFactory.getLogger(Test.class); 60 | } 61 | """ 62 | ) 63 | ); 64 | } 65 | 66 | @Test 67 | void staticFinalLoggerIsStaticFinal() { 68 | //language=java 69 | rewriteRun( 70 | java( 71 | """ 72 | import org.apache.log4j.Logger; 73 | 74 | class A { 75 | private static final Logger logger = Logger.getLogger(A.class); 76 | } 77 | """, 78 | """ 79 | import org.slf4j.Logger; 80 | import org.slf4j.LoggerFactory; 81 | 82 | class A { 83 | private static final Logger logger = LoggerFactory.getLogger(A.class); 84 | } 85 | """ 86 | ), 87 | java( 88 | """ 89 | import org.apache.log4j.Logger; 90 | import org.apache.log4j.LogManager; 91 | 92 | class B { 93 | private static final Logger logger = LogManager.getLogger(B.class); 94 | } 95 | """, 96 | """ 97 | import org.slf4j.Logger; 98 | import org.slf4j.LoggerFactory; 99 | 100 | class B { 101 | private static final Logger logger = LoggerFactory.getLogger(B.class); 102 | } 103 | """ 104 | ), 105 | java( 106 | """ 107 | import org.apache.log4j.Logger; 108 | import org.apache.log4j.LogManager; 109 | 110 | class C { 111 | private static final Logger logger = LogManager.getLogger("C"); 112 | } 113 | """, 114 | """ 115 | import org.slf4j.Logger; 116 | import org.slf4j.LoggerFactory; 117 | 118 | class C { 119 | private static final Logger logger = LoggerFactory.getLogger("C"); 120 | } 121 | """ 122 | ) 123 | ); 124 | } 125 | 126 | @Test 127 | void logLevelFatalToError() { 128 | //language=java 129 | rewriteRun( 130 | java( 131 | """ 132 | import org.apache.log4j.Logger; 133 | 134 | class Test { 135 | static void method(Logger logger) { 136 | logger.fatal("uh oh"); 137 | } 138 | } 139 | """, 140 | """ 141 | import org.slf4j.Logger; 142 | 143 | class Test { 144 | static void method(Logger logger) { 145 | logger.error("uh oh"); 146 | } 147 | } 148 | """ 149 | ) 150 | ); 151 | } 152 | 153 | @Test 154 | @Issue("https://github.com/openrewrite/rewrite-logging-frameworks/issues/47") 155 | void migrateMDC() { 156 | //language=java 157 | rewriteRun( 158 | java( 159 | """ 160 | import org.apache.log4j.MDC; 161 | 162 | class Test { 163 | static void method() { 164 | MDC.clear(); 165 | } 166 | } 167 | """, 168 | """ 169 | import org.slf4j.MDC; 170 | 171 | class Test { 172 | static void method() { 173 | MDC.clear(); 174 | } 175 | } 176 | """ 177 | ) 178 | ); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/test/java/org/openrewrite/java/logging/slf4j/Log4j2ToSlf4j1Test.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.slf4j; 17 | 18 | import org.junit.jupiter.api.Test; 19 | import org.openrewrite.DocumentExample; 20 | import org.openrewrite.InMemoryExecutionContext; 21 | import org.openrewrite.config.Environment; 22 | import org.openrewrite.java.JavaParser; 23 | import org.openrewrite.test.RecipeSpec; 24 | import org.openrewrite.test.RewriteTest; 25 | import org.openrewrite.test.TypeValidation; 26 | 27 | import static org.openrewrite.java.Assertions.java; 28 | 29 | class Log4j2ToSlf4j1Test implements RewriteTest { 30 | @Override 31 | public void defaults(RecipeSpec spec) { 32 | spec.recipe(Environment.builder() 33 | .scanRuntimeClasspath("org.openrewrite.java.logging") 34 | .build() 35 | .activateRecipes("org.openrewrite.java.logging.slf4j.Log4j2ToSlf4j1")) 36 | .parser(JavaParser.fromJavaVersion() 37 | .classpathFromResources(new InMemoryExecutionContext(), "log4j-api-2.+", "log4j-core-2.+", "lombok-1.18.+")); 38 | } 39 | 40 | @DocumentExample 41 | @Test 42 | void logLevelFatalToError() { 43 | //language=java 44 | rewriteRun( 45 | java( 46 | """ 47 | import org.apache.logging.log4j.Logger; 48 | 49 | class Test { 50 | static void method(Logger logger) { 51 | logger.fatal("uh oh"); 52 | } 53 | } 54 | """, 55 | """ 56 | import org.slf4j.Logger; 57 | 58 | class Test { 59 | static void method(Logger logger) { 60 | logger.error("uh oh"); 61 | } 62 | } 63 | """ 64 | ) 65 | ); 66 | } 67 | 68 | @Test 69 | void loggerUsage() { 70 | //language=java 71 | rewriteRun( 72 | java( 73 | """ 74 | import org.apache.logging.log4j.Logger; 75 | import org.apache.logging.log4j.LogManager; 76 | 77 | class Test { 78 | private static final Logger LOGGER = LogManager.getLogger(Test.class); 79 | private static final Logger ROOT_LOGGER = LogManager.getRootLogger(); 80 | 81 | public static void main(String[] args) { 82 | if (LOGGER.isDebugEnabled()) { 83 | LOGGER.debug("logger message"); 84 | } 85 | ROOT_LOGGER.info("root logger message"); 86 | } 87 | } 88 | """, 89 | """ 90 | import org.slf4j.Logger; 91 | import org.slf4j.LoggerFactory; 92 | 93 | class Test { 94 | private static final Logger LOGGER = LoggerFactory.getLogger(Test.class); 95 | private static final Logger ROOT_LOGGER = LoggerFactory.getRootLogger(); 96 | 97 | public static void main(String[] args) { 98 | if (LOGGER.isDebugEnabled()) { 99 | LOGGER.debug("logger message"); 100 | } 101 | ROOT_LOGGER.info("root logger message"); 102 | } 103 | } 104 | """ 105 | ) 106 | ); 107 | } 108 | 109 | @Test 110 | void changeLombokLogAnnotation() { 111 | //language=java 112 | rewriteRun( 113 | spec -> spec.typeValidationOptions(TypeValidation.builder().identifiers(false).methodInvocations(false).build()), 114 | java( 115 | """ 116 | import lombok.extern.log4j.Log4j; 117 | 118 | @Log4j 119 | class Test { 120 | void method() { 121 | log.info("uh oh"); 122 | } 123 | } 124 | """, 125 | """ 126 | import lombok.extern.slf4j.Slf4j; 127 | 128 | @Slf4j 129 | class Test { 130 | void method() { 131 | log.info("uh oh"); 132 | } 133 | } 134 | """ 135 | ) 136 | ); 137 | } 138 | 139 | @Test 140 | void unparameterizedLogging() { 141 | //language=java 142 | rewriteRun( 143 | java( 144 | """ 145 | package foo; 146 | public class LogConstants { 147 | public static final String CONSTANT = "constant"; 148 | } 149 | """ 150 | ), 151 | java( 152 | """ 153 | import foo.LogConstants; 154 | import org.apache.logging.log4j.Logger; 155 | 156 | class Test { 157 | Logger logger; 158 | void method(String arg) { 159 | logger.info(LogConstants.CONSTANT + arg); 160 | } 161 | } 162 | """, 163 | """ 164 | import foo.LogConstants; 165 | import org.slf4j.Logger; 166 | 167 | class Test { 168 | Logger logger; 169 | void method(String arg) { 170 | logger.info("{}{}", LogConstants.CONSTANT, arg); 171 | } 172 | } 173 | """ 174 | ) 175 | ); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/test/java/org/openrewrite/java/logging/slf4j/Slf4jBestPracticesTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 the original author or authors. 3 | *

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 | *

8 | * https://docs.moderne.io/licensing/moderne-source-available-license 9 | *

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.slf4j; 17 | 18 | import org.junit.jupiter.api.Test; 19 | import org.openrewrite.DocumentExample; 20 | import org.openrewrite.InMemoryExecutionContext; 21 | import org.openrewrite.config.Environment; 22 | import org.openrewrite.java.JavaParser; 23 | import org.openrewrite.test.RecipeSpec; 24 | import org.openrewrite.test.RewriteTest; 25 | 26 | import static org.openrewrite.java.Assertions.java; 27 | 28 | class Slf4jBestPracticesTest implements RewriteTest { 29 | 30 | @Override 31 | public void defaults(RecipeSpec spec) { 32 | spec.recipe(Environment.builder() 33 | .scanRuntimeClasspath("org.openrewrite.java.logging.slf4j") 34 | .build() 35 | .activateRecipes("org.openrewrite.java.logging.slf4j.Slf4jBestPractices")) 36 | .parser(JavaParser.fromJavaVersion() 37 | .classpathFromResources(new InMemoryExecutionContext(), "slf4j-api-2.1.+")); 38 | } 39 | 40 | @DocumentExample 41 | @Test 42 | void applyBestPractices() { 43 | //language=java 44 | rewriteRun( 45 | java( 46 | """ 47 | import org.slf4j.Logger; 48 | import org.slf4j.LoggerFactory; 49 | class Test { 50 | Logger logger = LoggerFactory.getLogger(String.class); 51 | void test() { 52 | Object obj1 = new Object(); 53 | Object obj2 = new Object(); 54 | logger.info("Hello " + obj1 + ", " + obj2); 55 | Exception e = new Exception(); 56 | logger.warn(String.valueOf(e)); 57 | logger.error(e.getMessage()); 58 | logger.error(e.getLocalizedMessage()); 59 | } 60 | } 61 | """, 62 | """ 63 | import org.slf4j.Logger; 64 | import org.slf4j.LoggerFactory; 65 | class Test { 66 | private Logger logger = LoggerFactory.getLogger(Test.class); 67 | void test() { 68 | Object obj1 = new Object(); 69 | Object obj2 = new Object(); 70 | logger.info("Hello {}, {}", obj1, obj2); 71 | Exception e = new Exception(); 72 | logger.warn("Exception", e); 73 | logger.error("", e); 74 | logger.error("", e); 75 | } 76 | } 77 | """ 78 | ) 79 | ); 80 | } 81 | 82 | @SuppressWarnings({"UnnecessaryCallToStringValueOf", "UnnecessaryToStringCall"}) 83 | @Test 84 | void exceptionIsAppendedAtEndOfLogMessage() { 85 | //language=java 86 | rewriteRun( 87 | java( 88 | """ 89 | import org.slf4j.Logger; 90 | import org.slf4j.LoggerFactory; 91 | class Test { 92 | Logger logger = LoggerFactory.getLogger(Test.class); 93 | void test() { 94 | try { 95 | throw new IllegalStateException("oops"); 96 | } catch (Exception e) { 97 | logger.error("aaa: " + e); 98 | logger.error("bbb: " + String.valueOf(e)); 99 | logger.error("ccc: " + e.toString()); 100 | } 101 | } 102 | } 103 | """, 104 | """ 105 | import org.slf4j.Logger; 106 | import org.slf4j.LoggerFactory; 107 | class Test { 108 | private Logger logger = LoggerFactory.getLogger(Test.class); 109 | void test() { 110 | try { 111 | throw new IllegalStateException("oops"); 112 | } catch (Exception e) { 113 | logger.error("aaa: {}", e); 114 | logger.error("bbb: {}", String.valueOf(e)); 115 | logger.error("ccc: {}", e.toString()); 116 | } 117 | } 118 | } 119 | """ 120 | ) 121 | ); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | ^pkg:maven/log4j/log4j@.*$ 10 | CVE-2019-17571 11 | CVE-2020-9493 12 | CVE-2021-4104 13 | CVE-2022-23302 14 | CVE-2022-23305 15 | CVE-2022-23307 16 | CVE-2023-26464 17 | 18 | 19 | 23 | CVE-2019-11402 24 | CVE-2019-11403 25 | CVE-2019-15052 26 | CVE-2019-15052 27 | CVE-2020-11979 28 | CVE-2021-29427 29 | CVE-2021-29428 30 | CVE-2021-32751 31 | CVE-2021-41589 32 | CVE-2022-25364 33 | CVE-2023-35947 34 | CVE-2023-45161 35 | CVE-2023-45163 36 | CVE-2023-49238 37 | CVE-2023-5964 38 | 39 | 40 | --------------------------------------------------------------------------------