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 withLevelChangePropagator
is an acceptable alternative to using GeoToolsLogbackLoggerFactory
.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.
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>31-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>
Configure logback with
logback.xml
added tosrc/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.During startup logback will search for
logback.xml
on the CLASSPATH (orlogback-test.xml
for testing).To use a different file
-Dlogback.configurationFile=logback-custom.xml
.Application
LogbackJIntegration.java
startup example forsrc/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); } }
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).