Logback integration

The logging output can also be redirected to Logback (via SL4J API):

<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>${slf4j.version}</version>
</dependency>

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

GeoTools.setLoggerFactory(LogbackLoggerFactory.getInstance());

If your application only depends logback being provided at runtime:

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

Note

If logback is not found on the CLASSPATH LogbackLoggerFactory 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.

Java Level

LogBack Level

Marker

OFF

OFF

FATAL

ERROR

FATAL

SEVERE

ERROR

WARNING

WARN

INFO

INFO

CONFIG

INFO

CONFIG

FINE

DEBUG

FINER

TRACE

FINEST

TRACE

FINEST

ALL

ALL

GeoTools logback.xml example:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %-6level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <turboFilter class="ch.qos.logback.classic.turbo.MarkerFilter">
        <Name>CONFIG_FILTER</Name>
        <Marker>CONFIG</Marker>
        <OnMatch>DENY</OnMatch>
    </turboFilter>

    <logger name="org.geotools.tutorial.logging" level="ALL"/>
    <logger name="org.geotools" level="DEBUG"/>
    <root level="error">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

The above example shows how to “DENY” messages that have the CONFIG marker applied; this provides a way to filter the Java CONFIG level messages that are published to SLF4J as INFO messages, with a CONFIG marker.

Reference:

Use with SLF4J Enviornment

Logback natively uses the SLF4J API, allowing LogbackLoggerFactory to be used with application configured for SLF4J:

  • SLF4J is designed for external configuration, and the Logger.setLelvel( Level ) method will silently do nothing (which is allowed by the Logger javadoc api contract).

  • When using logback-classic the Logger.setLelvel( Level ) works as expected.

Logback Guidance

  • Include <shutdownHook/> in your configuration, or register a shutdown hook yourself:

    Runtime.getRuntime().addShutdownHook(new Thread(() -> stopLogging()));
    
    /**
     * Allow logback to finish
     */
    private static void stopLogging(){
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        loggerContext.stop();
    }
    

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

  • jul-to-slf4j: used to bridge any components using java util logging to sl4j.

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jul-to-slf4j</artifactId>
        <version>${slf4j.version}</version>
    </dependency>
    

    Please read the instructions on use of LevelChangePropagator.

    Note

    Use of jul-to-slf4j combined with LevelChangePropagator is an acceptable alternative to using GeoTools LogbackLoggerFactory.

    This approach offers only minor regression in functionality, no mapping is provided for CONFIG and FINNER levels.

  • log4j-to-slf4j: Apache Log4J 2 provides its own bridge to Log4J:

    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-to-slf4j</artifactId>
        <version>${log4j.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>
    
  • slf4j-reload4j: used to bridge any components using slf4j api

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-reload4j</artifactId>
        <version>${slf4j.version}</version>
    </dependency>
    
  • 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.LogbackLoggerFactory");
    

Logback Integration

The following example is taken from our integration testing, this test only has slf4j api available so GeoTools.init()` is able to unambiguously determine ``LogbackLoggerFactory can be used.

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

    <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>logback</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <packaging>jar</packaging>
      <name>GeoTools Logback 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>
        <logback.version>1.3.12</logback.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.logback</groupId>
          <artifactId>logback-classic</artifactId>
          <version>${logback.version}</version>
        </dependency>
        <dependency>
          <groupId>ch.qos.logback</groupId>
          <artifactId>logback-core</artifactId>
          <version>${logback.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.logging.LogbackIntegration</argument>
              </arguments>
            </configuration>
          </plugin>
        </plugins>
      </build>
    </project>
    
  2. Configure logback with logback.xml added to src/main/resources:

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <shutdownHook/>
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%d{HH:mm:ss.SSS} %-6level %logger{36} %marker: %msg%n</pattern>
            </encoder>
        </appender>
        
        <logger name="org.geotools.tutorial.logging" level="ALL"/>
        <root level="error">
            <appender-ref ref="STDOUT" />
        </root>
    </configuration>
    

    Of interest above is the mapping of CONFIG and FINEST to logback markers, something not offered by jul-to-slf4j bridge.

  3. During startup logback will search for logback.xml on the CLASSPATH (or logback-test.xml for testing).

    To use a different file -Dlogback.configurationFile=logback-custom.xml.

  4. Application LogbackJIntegration.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) 2016, 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.logging;
    
    import java.io.File;
    import java.lang.reflect.Field;
    import java.net.URL;
    import java.util.logging.LogManager;
    import java.util.logging.Logger;
    import java.util.logging.Level;
    
    import ch.qos.logback.classic.BasicConfigurator;
    import ch.qos.logback.classic.LoggerContext;
    import ch.qos.logback.core.joran.spi.ConfigurationWatchList;
    import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil;
    import org.geotools.util.factory.GeoTools;
    import org.geotools.util.logging.LogbackLoggerFactory;
    import org.geotools.util.logging.Logging;
    import org.slf4j.LoggerFactory;
    
    /**
     * Example illustrating use of SLF4J API and Logback startup environment.
     */
    public class LogbackIntegration {
    
        public static final Logger LOGGER = initLogger();
    
        public static void main(String args[]) {
            LOGGER.info("Welcome to Logback Integration Example");
    
            if(!LOGGER.getClass().getName().equals("org.geotools.util.logging.LogbackLogger")){
                LOGGER.severe("LogbackLogger expected, but was:" + LOGGER.getClass().getName() );
            }
            LOGGER.config("Configuration " + Logging.ALL.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();
            if( Logging.ALL.getLoggerFactory() != LogbackLoggerFactory.getInstance() ){
                System.err.println("Expected GeoTools.init() to configure LogbackLoggerFactory, was "+Logging.ALL.getLoggerFactory());
            }
            
            return Logging.getLogger(LogbackIntegration.class);
        }
    
    }
    
  1. An exec:exec target is provided to make this easier to test:

    mvn exec:exec
    

    Is the equivalent of:

    java -Djava.awt.headless=true \\
         org.geotools.tutorial.logging.LogbackIntegration
    

    Note

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