uPortal is an open source project used by many institutions to deliver personalized views to students, faculty, and staff with links out to supporting services. This article will tell you about the Log4j issue that hit the IT industry in late 2021 and how to change Log4j to SLF4j, a more secure logging library.
History
If you paid any attention to security issues that hit the Internet in December 2021, then you know about “Log4shell”. Log4j, a common and VERY popular Java logging library, had a major security vulnerability. This only affected Log4j version 2 and later, but older versions also fell under scrutiny. They were found to have some lesser vulnerabilities, but they were still at risk.
The uPortal Community had moved away from Log4j many years ago in favor of SLF4J and Logback. New projects were requested to adopt SLF4J while older projects were encouraged to switch. There was even a page in the uPortal 4 manual on what dependencies to include to assist in the adoption of SLF4J. However, refactoring from Log4j to SLF4J was a very low priority, so the conversation did not happen in many projects. There was little reason to refactor the Log4j logging libraries out – that is, until that security vulnerability was discovered.
Overview
So what does it take to make the switch to SLF4J and Logback? For starters, dependencies need to be added or removed, and some transient dependencies must be addressed. This is accomplished in pom.xml
files for Maven projects and build.gradle
files in Gradle projects. This article will use a Maven build as an example, but the same concepts can be applied to the Gradle projects. At a high level:
- Log4j configuration files need to be replaced with SLF4J configuration files.
- Java source code needs to be updated with the new logging classes. (Luckily there is a tool for this part.)
- Project configuration files for logging entries need to be reviewed and updated - this usually entails the removal of a few entries in
web.xml
, but sometimes a Spring context file will need some edits as well.
I present here a walkthrough of steps I take to refactor some older Java portlets to replace Log4j with SLF4J.
Migrate Dependencies
For dependencies, we usually have three tasks to make this switch. First, we need to update the properties that capture version numbers. Second, we replace the logging dependencies directly. Finally, we need to address any transient dependencies. These last two steps are somewhat intertwined. Let’s get into the details.
Version Properties
Maven and Gradle can leverage properties defined at the parent project level to capture version numbers of dependencies. This helps sync specific dependency versions across submodules of complex projects. Note that versions are not always captured this way, so do not be alarmed if you do not see an entry for Log4j. Just move on. You are looking for something like the following in pom.xml
files:
<properties>
…
<log4jVersion>1.2.17</log4jVersion>
…
</properties>
You will want to remove the <log4jVersion>
and replace it with two entries, one for SLF4J and another for Logback. For example:
<properties>
…
<logbackVersion>1.2.10</logbackVersion>
<slf4jVersion>1.7.35</slf4jVersion>
…
</properties>
Note: check Maven Central for the latest versions of Logback and SLF4J.
Logging Dependencies
Next, you will want to remove the dependency for log4j. It will look like this:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4jVersion}</version>
</dependency>
It is okay if the dependency has a <type> and/or <scope>. Expect the version value to be an actual version number if you did not see the <log4jVersion>
tag in the <properties> section of the pom.xml
file.
Now it’s time to add the basic dependencies for SLF4J and Logback. We already added the version properties, so the dependencies look like:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4jVersion}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logbackVersion}</version>
<scope>runtime</scope>
</dependency>
Note we set the <scope> to ‘runtime’ for Logback. This is because Logback is not needed to compile Java code that uses SLF4J. It is only needed when the code is actually running.
SLF4J Bridge Dependencies
Here comes the only tricky part. You have to figure out what other dependencies require for their logging, so you can include adaptors for them to use SLF4J. SLF4J can be substituted for Jakarta Commons Logging (JCL), Log4j, and java.util.logging (JUL).
You can just add all three adaptors; however, you still need to discover what dependencies use which logging frameworks. Here are the adaptor entries:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4jVersion}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>${slf4jVersion}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>${slf4jVersion}</version>
</dependency>
I suggest adding XML comments above each dependency, and naming the other dependencies that use the adaptor. For example, Spring 3 uses JCL, so I’d add <!-- spring-core 3 –>
over the jcl-over-slf4j
entry.
Transient Dependencies
To get the transient dependencies, you run the task/target for the build tool for your project.
- For Maven:
mvn dependency:tree
- For Gradle:
./gradlew dependencies
If you are looking for a particular dependency (i.e. log4j), Maven and Gradle support that as well.
- For Maven:
mvn dependency:tree -Dincludes=[groupId]:[artifactId]
- For Gradle:
/gradlew dependencyInsight –dependency [groupId or artifactId]
Note that Maven wants the coordinates while Gradle will match on either groupId or artifactId. Luckily, the previous commands can be used to figure out the groupId and artifactId needed. As a fallback, search Maven for the logger dependency details.
Since you are taking the time to read this, here are the coordinates you will likely need for the dependency commands:
Log4j v1: log4j:log4j
Log4j v2: org.apache.logging.log4j:log4j-core
JCL: commons-logging:commons-logging
The output will show packages that are included and their dependencies. Scan the list and note if you see a dependency that relies on Log4j or JCL (or even JUL). For such dependencies, add an exclusion for log4j. The SLF4J adaptor will then be used instead, and the logging jar will not be added. For example(Maven POM):
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
When you run the dependency tree task/target after the change, the logging dependency excluded should not be in the tree. If it’s still present, check that the exclusion is correct and if there might be other dependencies that need the exclusion.
Replace Logging Configuration
This step is fairly straightforward. Copy an existinglogback.xml
from another project as a template and make necessary changes. I will be using thelogback.xml
file from the FeedbackPortlet project (https://github.com/uPortal-Project/FeedbackPortlet). The changes for the new copy would include:
- Line 30: change contextName to match project. This is used as the logfile name.
- Line 93: change logger name to match the main project package.
- Skim the existing log4j configuration file for the above two and potentially other changes.
As an example, see this commit in FeedbackPortlet (https://github.com/uPortal-Project/FeedbackPortlet/commit/6bd84f9e05275f54e35b0080c858ad0c29853a5d).
Then, remove the Log4j configuration file, which is usually log4j.properties
or log4j.xml
.
Update Java Source
This would be quite a chore if not for the tool that the SLF4J project has made available. You can find it in Maven Repo1 at https://repo1.maven.org/maven2/org/slf4j/slf4j-migrator/. To use the tool, first find the version of SLF4J you are planning to use, usually the latest. The migrator jar will be in the subdirectory along with the checksums. Once you have the tool downloaded and checksum confirmed, you can run the tool with ‘java -jar slf4j-migrator-<version>.jar’. This pops up a window that you can then use to select what logging classes to replace and in which source directory.
The tool only replaces a single logging package with SLF4J classes per pass, so you will want to run it three times, once for each of the migration types. Make sure you are running this on code that has been captured in a repo or backed up. You may need to make additional code changes as this tool is only designed for basic logging cases. Still, this tool should handle almost all of the needed Java changes automatically!
Clean Project Configuration
Finally, we need to scrub the existing project configuration files. This varies widely and really depends on each project. The most common change I have performed is to remove the Log4j context parameter and Log4j config listener. Below are examples that were removed from a web.xml
file:
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/log4j.properties</param-value>
</context-param>
…
<listener>
<listener-class>
org.springframework.web.util.Log4jConfigListener
</listener-class>
</listener>
Final Checks For Changing Log4j to SLF4j
Before you can call this effort done, we need to confirm two things. First, the code needs to run. If it compiles, that means the logging classes were likely replaced correctly, but we still need to run it to make sure dependencies are set correctly. Second, we need to look in the dependency directory to make sure log4j or other logging frameworks did not sneak in; otherwise, we will need to check the transient dependencies again. For web applications, this is usually WEB-INF/lib/
in Tomcat (or other web container).
After you have completed your final checks, you can be confident that you have removed the security vulnerabilities caused by Log4j. I hope you find this blog useful and helpful in refactoring your projects without too many surprises.
If you need assistance with your uPortal service whether it be upgrading, a new installation, or customization, Unicon provides all these services and more. We also offer a uPortal support service that includes unlimited help desk support along with some consulting hours, and funds sustaining engineering hours to give back to the community.