Reload4J Interoperability

Apache has announced Log4J 1 has reached end-of-life, the Reload4J project has taken over maintaining the API:

<reload4j.version>1.2.19</reload4j.version>

<dependency>
  <groupId>ch.qos.reload4j</groupId>
  <artifactId>reload4j</artifactId>
  <version>${reload4j.version}</version>
</dependency>

If your application uses Log4J directly during compile time, you can safely set up with:

GeoTools.setLoggerFactory(Log4JLoggerFactory.getInstance());

If your application only depends reload4j being provided at runtime:

Logging.ALL.setLoggerFactory("org.geotools.util.logging.Log4JLoggerFactory");

Note

If reload4j is not found on the CLASSPATH Log4JLoggerFactory results in unpredictable behavior!

It will typically throws a NoClassDefFoundError (the unchecked error, not the checked exception) at some future point. The error may not be thrown at the moment setLoggerFactory is invoked, but rather be delayed until a message is first logged, which may surprise the user.

Reference:

Reload4J Guidance

In a more complicated setup using multiple libraries you may also end up including:

  • slf4j-reload4j: used to bridge any components using slf4j api

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-reload4j</artifactId>
        <version>${slf4j.version}</version>
    </dependency>
    
  • jcl-over-sl4j: used to bridge any components using commons-logging to sl4j (which can be bridged to reload4j above).

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>${slf4j.version}</version>
    </dependency>
    
  • commons-logging: Assume Log4J 1 API

    Use commons-logging.properties:

    org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
    
  • Use of multiple logging frameworks prevents GeoTools.init() ability to determine which API to use requiring the use of:

    Logging.ALL.setLoggerFactory("org.geotools.util.logging.Log4JLoggerFactory");
    

Reload4J Integration

The following example is taken from our integration testing, this test only has the reload4j in play so GeoTools.init()` is able to unambiguously determine ``Log4JLoggerFactory can be used.

  1. Setup pom.xml with dependencies on geotools and reload4j:

    <project
      xmlns="http://maven.apache.org/POM/4.0.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>org.geotools.tutorial</groupId>
      <artifactId>reload4j</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <packaging>jar</packaging>
      <name>GeoTools Reload4J Integration Example</name>
      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.deploy.skip>true</maven.deploy.skip>
        <geotools.version>30-SNAPSHOT</geotools.version>
        <reload4j.version>1.2.19</reload4j.version>
      </properties>
      <dependencies>
        <dependency>
          <groupId>org.geotools</groupId>
          <artifactId>gt-metadata</artifactId>
          <version>${geotools.version}</version>
        </dependency>
        <dependency>
          <groupId>org.geotools.ogc</groupId>
          <artifactId>net.opengis.ows</artifactId>
          <version>${geotools.version}</version>
        </dependency>
        <dependency>
          <groupId>ch.qos.reload4j</groupId>
          <artifactId>reload4j</artifactId>
          <version>${reload4j.version}</version>
        </dependency>
      </dependencies>
      <repositories>
        <repository>
        <id>osgeo</id>
        <name>OSGeo Release Repository</name>
        <url>https://repo.osgeo.org/repository/release/</url>
        <snapshots><enabled>false</enabled></snapshots>
        <releases><enabled>true</enabled></releases>
        </repository>
        <repository>
        <id>osgeo-snapshot</id>
        <name>OSGeo Snapshot Repository</name>
        <url>https://repo.osgeo.org/repository/snapshot/</url>
        <snapshots><enabled>true</enabled></snapshots>
        <releases><enabled>false</enabled></releases>
        </repository>
      </repositories>
    
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>3.3.0</version>
          </plugin>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.11.0</version>
            <configuration>
              <source>11</source>
              <target>11</target>
            </configuration>
          </plugin>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-resources-plugin</artifactId>
            <version>3.1.0</version>
          </plugin>
          <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <version>3.1.0</version>
            <configuration>
              <executable>java</executable>
              <arguments>
                <argument>-classpath</argument>
                <classpath/>
                <argument>-Djava.awt.headless=true</argument>
                <argument>org.geotools.tutorial.reload.Reload4JIntegration</argument>
              </arguments>
            </configuration>
          </plugin>
        </plugins>
      </build>
    </project>
    
  2. Configure reload4j wtih log4j.properties added to src/main/resources:

    log4j.rootLogger=ERROR, stdout
    
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    
    # Pattern to output the caller's file name and line number.
    log4j.appender.stdout.layout.ConversionPattern=%d{dd MMM HH:mm:ss} %p [%c{2}] - %m%n
    
    log4j.category.org.geotools=ALL
    
  3. During startup log4j will search for log4j.properties on the CLASSPATH, or to search for a different file use the system property:

    -Dlog4j.configuration=log4-debug.properties
    
  4. Application Reload4Integration.java startup example for src/min/java.

    Example is taking care to call GeoTools.init() prior to logger use:

    /*
     *    GeoTools - The Open Source Java GIS Toolkit
     *    http://geotools.org
     *
     *    (C) 2022, Open Source Geospatial Foundation (OSGeo)
     *
     *    This file is hereby placed into the Public Domain. This means anyone is
     *    free to do whatever they wish with this file. Use it well and enjoy!
     */
    package org.geotools.tutorial.reload;
    
    import java.util.logging.Logger;
    import org.geotools.util.factory.GeoTools;
    import org.geotools.util.logging.Logging;
    import org.geotools.util.logging.Log4JLoggerFactory;
    
    /** Example illustrating use of Reload4J (providing Log4J 1 API) and startup environment. */
    public class Reload4JIntegration {
    
        public static final Logger LOGGER = initLogger();
    
        public static void main(String args[]) {
            LOGGER.info("Welcome to Reload4J Integration Example");
            if (!LOGGER.getClass().getName().equals("org.geotools.util.logging.Log4JLogger")) {
                LOGGER.severe("Log4JLogger expected, but was:" + LOGGER.getClass().getName());
            }
            // Log4J Properties
            checkProperty("log4j.defaultInitOverride");
            checkProperty("log4j.configuratorClass");
            checkProperty("log4j.configuratorClass");
    
            if (System.getProperties().containsKey("log4j.configuration")) {
                LOGGER.config("log4j.configurationFile=" + System.getProperty("log4j.configuration"));
            }
    
            LOGGER.config("Configuration " + Log4JLoggerFactory.getInstance().lookupConfiguration());
    
            LOGGER.finest("Everything is finest...");
            LOGGER.finer("Everything is finer...");
            LOGGER.fine("Everything is fine...");
            LOGGER.config("Everything is configured...");
            LOGGER.log(Logging.OPERATION, "Everything is operating...");
            LOGGER.info("Everything is okay.");
            LOGGER.warning("Everything is alarming!");
            LOGGER.severe("Everything is terrible!");
            LOGGER.log(Logging.FATAL, "Everything has died!");
        }
    
        private static Logger initLogger() {
            GeoTools.init();
            return Logging.getLogger(Reload4JIntegration.class);
        }
    
        private static void checkProperty(String property) {
            if (System.getProperties().containsKey(property)) {
                LOGGER.config(property + "=" + System.getProperty(property));
            }
        }
    }
    
  1. An exec:exec target is provided to make this easier to test:

    mvn exec:exec
    

    Note

    Avoid testing with exec:java which uses maven java runtime environment (already pre-configured for logging).