From 5f18ac69ace1e3bc8093bf8ecdc1ef76a1278567 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gr=C3=A9gory=20Mantelet?=
 <gregory.mantelet@astro.unistra.fr>
Date: Tue, 7 Aug 2018 18:01:13 +0200
Subject: [PATCH] [UWS,TAP] Add a new logger which forwards log entries to
 SLF4J. Then, UWS or TAP implementors are free to use whatever logging system
 they want (e.g. log4j, logback, ...).

---
 build.gradle                                  |   3 +
 buildTAP.xml                                  |   7 +-
 buildUWS.xml                                  |   6 +-
 .../config/ConfigurableServiceConnection.java |  78 ++--
 src/tap/config/TAPConfiguration.java          |  51 ++-
 src/tap/config/tap_configuration_file.html    |  58 ++-
 src/tap/config/tap_full.properties            |  44 +-
 src/tap/log/Slf4jTAPLog.java                  | 174 ++++++++
 src/uws/config/ConfigurableUWSServlet.java    | 153 +++----
 src/uws/config/UWSConfiguration.java          | 120 +++---
 src/uws/config/uws_configuration_file.html    |  62 ++-
 src/uws/config/uws_full.properties            |  47 ++-
 src/uws/config/uws_min.properties             |   6 +-
 src/uws/service/log/DefaultUWSLog.java        | 110 ++---
 src/uws/service/log/Slf4jUWSLog.java          | 387 ++++++++++++++++++
 src/uws/service/log/UWSLog.java               |  20 +-
 16 files changed, 1052 insertions(+), 274 deletions(-)
 create mode 100644 src/tap/log/Slf4jTAPLog.java
 create mode 100644 src/uws/service/log/Slf4jUWSLog.java

diff --git a/build.gradle b/build.gradle
index 46c6c2c..da03fdb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -11,10 +11,13 @@ dependencies {
     compile fileTree(dir: 'lib', includes: ['stil_3.1.jar','cos-1.5beta.jar'])
     compile 'javax.servlet:javax.servlet-api:3.0.1'
     compile 'postgresql:postgresql:9.1-901.jdbc4'
+    compile 'org.slf4j:slf4j-api:1.7.25'
 
     testCompile 'junit:junit:4.12'
     testCompile 'com.h2database:h2:1.4.193'
     testCompile fileTree(dir: 'lib', include: 'astroh2-0.3.jar')
+    testCompile 'org.slf4j:slf4j-simple:1.7.25'
+    
     
     testRuntime 'simple-jndi:simple-jndi:0.11.4.1'
     testRuntime 'com.vividsolutions:jts-core:1.14.0'
diff --git a/buildTAP.xml b/buildTAP.xml
index 65414d8..8a10aaa 100644
--- a/buildTAP.xml
+++ b/buildTAP.xml
@@ -14,9 +14,13 @@
 
 	<property name="cosJarName" value="cos-1.5beta.jar" />
 	<property name="cosJar" value="${libDir}/${cosJarName}" />
+	
 	<property name="stilJarName" value="stil_3.1.jar" />
 	<property name="stilJar" value="${libDir}/${stilJarName}" />
 
+	<property name="slf4jApiJarName" value="slf4j-api-1.7.25.jar" />
+	<property name="slf4jApiJar" value="${libDir}/${slf4jApiJarName}" />
+
 	<property name="sqliteJarName" value="sqlite-jdbc-3.16.1.jar" />
 	<property name="sqliteJar" value="${libDir}/${sqliteJarName}" />
 
@@ -29,7 +33,7 @@
 	<property name="astroh2JarName" value="astroh2-0.3.jar" />
 	<property name="astroh2Jar" value="${libDir}/${astroh2JarName}" />
 		
-	<property name="tapPath" value="tap/**,adql/**,uws/**,cds/**" />
+	<property name="tapPath" value="tap/**,adql/**,uws/**,cds/**,datalink/**,puzzlibs/**" />
 	<property name="jsonPath" value="org/json/**" />
 	<property name="extLibsPath" value="com/oreilly/servlet/**,nom/tam/**,org/apache/tools/bzip2/**,uk/ac/starlink/**" />
 	<property name="licensePath" value="COPYING.LESSER" />
@@ -67,6 +71,7 @@
 	<!-- CLASSPATHS -->
 	<path id="tap.classpath">
 		<pathelement location="${cosJar}" />
+		<pathelement location="${slf4jApiJar}" />
 		<pathelement location="${jsonJar}" />
 		<pathelement location="${stilJar}" />
 		<pathelement location="${POSTGRES}" />
diff --git a/buildUWS.xml b/buildUWS.xml
index 972fac9..3371f5b 100644
--- a/buildUWS.xml
+++ b/buildUWS.xml
@@ -2,7 +2,7 @@
 <!DOCTYPE project>
 <project name="uws" basedir="." default="buildLib">
 	
-	<property name="version" value="4.3" />
+	<property name="version" value="4.4" />
 
 	<property name="srcDir" value="src" />
 	<property name="testDir" value="test" />
@@ -14,6 +14,9 @@
 
 	<property name="cosJarName" value="cos-1.5beta.jar" />
 	<property name="cosJar" value="${libDir}/${cosJarName}" />
+
+	<property name="slf4jApiJarName" value="slf4j-api-1.7.25.jar" />
+	<property name="slf4jApiJar" value="${libDir}/${slf4jApiJarName}" />
 	
 	<property name="uwsPath" value="uws/**" />
 	<property name="jsonPath" value="org/json/**" />
@@ -36,6 +39,7 @@
 	<!-- CLASSPATHS -->
 	<path id="uws.classpath">
 		<pathelement location="${cosJar}" />
+		<pathelement location="${slf4jApiJar}" />
 		<pathelement location="${SERVLET-API}" />
 	</path>
 	
diff --git a/src/tap/config/ConfigurableServiceConnection.java b/src/tap/config/ConfigurableServiceConnection.java
index bd3a994..3fe6d2f 100644
--- a/src/tap/config/ConfigurableServiceConnection.java
+++ b/src/tap/config/ConfigurableServiceConnection.java
@@ -58,6 +58,7 @@ import static tap.config.TAPConfiguration.KEY_UDFS;
 import static tap.config.TAPConfiguration.KEY_UPLOAD_ENABLED;
 import static tap.config.TAPConfiguration.KEY_UPLOAD_MAX_FILE_SIZE;
 import static tap.config.TAPConfiguration.KEY_USER_IDENTIFIER;
+import static tap.config.TAPConfiguration.SLF4J_LOGGER;
 import static tap.config.TAPConfiguration.VALUE_ALL;
 import static tap.config.TAPConfiguration.VALUE_ANY;
 import static tap.config.TAPConfiguration.VALUE_CSV;
@@ -109,6 +110,7 @@ import tap.formatter.SVFormat;
 import tap.formatter.TextFormat;
 import tap.formatter.VOTableFormat;
 import tap.log.DefaultTAPLog;
+import tap.log.Slf4jTAPLog;
 import tap.log.TAPLog;
 import tap.metadata.TAPMetadata;
 import tap.metadata.TableSetParser;
@@ -129,7 +131,7 @@ import uws.service.log.UWSLog.LogLevel;
  * </p>
  *
  * @author Gr&eacute;gory Mantelet (ARI)
- * @version 2.3 (03/2018)
+ * @version 2.3 (04/2018)
  * @since 2.0
  */
 public final class ConfigurableServiceConnection implements ServiceConnection {
@@ -171,7 +173,7 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 
 	/** Array of 2 integers: resp. default and maximum output limit.
 	 * <em>Each limit is expressed in a unit specified in the array {@link #outputLimitTypes}.</em> */
-	private int[] outputLimits = new int[]{-1,-1};
+	private int[] outputLimits = new int[]{ -1, -1 };
 	/** Array of 2 limit units: resp. unit of the default output limit and unit of the maximum output limit. */
 	private LimitUnit[] outputLimitTypes = new LimitUnit[2];
 
@@ -179,7 +181,7 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 	private boolean isUploadEnabled = false;
 	/** Array of 2 integers: resp. default and maximum upload limit.
 	 * <em>Each limit is expressed in a unit specified in the array {@link #uploadLimitTypes}.</em> */
-	private int[] uploadLimits = new int[]{-1,-1};
+	private int[] uploadLimits = new int[]{ -1, -1 };
 	/** Array of 2 limit units: resp. unit of the default upload limit and unit of the maximum upload limit. */
 	private LimitUnit[] uploadLimitTypes = new LimitUnit[2];
 	/** The maximum size of a set of uploaded files.
@@ -188,7 +190,7 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 
 	/** Array of 2 integers: resp. default and maximum fetch size.
 	 * <em>Both sizes are expressed in number of rows.</em> */
-	private int[] fetchSize = new int[]{DEFAULT_ASYNC_FETCH_SIZE,DEFAULT_SYNC_FETCH_SIZE};
+	private int[] fetchSize = new int[]{ DEFAULT_ASYNC_FETCH_SIZE, DEFAULT_SYNC_FETCH_SIZE };
 
 	/** The method to use in order to identify a TAP user. */
 	private UserIdentifier userIdentifier = null;
@@ -322,7 +324,7 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 		}
 		// CUSTOM file manager:
 		else
-			fileManager = newInstance(fileManagerType, KEY_FILE_MANAGER, UWSFileManager.class, new Class<?>[]{Properties.class}, new Object[]{tapConfig});
+			fileManager = newInstance(fileManagerType, KEY_FILE_MANAGER, UWSFileManager.class, new Class<?>[]{ Properties.class }, new Object[]{ tapConfig });
 	}
 
 	/**
@@ -355,7 +357,7 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 	}
 
 	/**
-	 * Initialize the TAP logger with the given TAP configuration file.
+	 * Initialise the TAP logger with the given TAP configuration file.
 	 *
 	 * @param tapConfig	The content of the TAP configuration file.
 	 *
@@ -367,31 +369,33 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 		String propValue = getProperty(tapConfig, KEY_LOGGER);
 		if (propValue == null || propValue.trim().equalsIgnoreCase(DEFAULT_LOGGER))
 			logger = new DefaultTAPLog(fileManager);
+		else if (propValue == null || propValue.trim().equalsIgnoreCase(SLF4J_LOGGER))
+			logger = new Slf4jTAPLog();
 		else
-			logger = newInstance(propValue, KEY_LOGGER, TAPLog.class, new Class<?>[]{UWSFileManager.class}, new Object[]{fileManager});
+			logger = newInstance(propValue, KEY_LOGGER, TAPLog.class, new Class<?>[]{ UWSFileManager.class }, new Object[]{ fileManager });
 
-		StringBuffer buf = new StringBuffer("Logger initialized");
+		// Set some options for the default logger:
+		if (propValue == null || propValue.trim().equalsIgnoreCase(DEFAULT_LOGGER)){
 
-		// Set the minimum log level:
-		propValue = getProperty(tapConfig, KEY_MIN_LOG_LEVEL);
-		if (propValue != null){
-			try{
-				((DefaultTAPLog)logger).setMinLogLevel(LogLevel.valueOf(propValue.toUpperCase()));
-			}catch(IllegalArgumentException iae){}
-		}
-		buf.append(" (minimum log level: ").append(((DefaultTAPLog)logger).getMinLogLevel());
-
-		// Set the log rotation period, if any:
-		if (fileManager instanceof LocalUWSFileManager){
-			propValue = getProperty(tapConfig, KEY_LOG_ROTATION);
-			if (propValue != null)
-				((LocalUWSFileManager)fileManager).setLogRotationFreq(propValue);
-			buf.append(", log rotation: ").append(((LocalUWSFileManager)fileManager).getLogRotationFreq());
+			// Set the minimum log level:
+			propValue = getProperty(tapConfig, KEY_MIN_LOG_LEVEL);
+			if (propValue != null){
+				try{
+					((DefaultTAPLog)logger).setMinLogLevel(LogLevel.valueOf(propValue.toUpperCase()));
+				}catch(IllegalArgumentException iae){
+				}
+			}
+
+			// Set the log rotation period, if any:
+			if (fileManager instanceof LocalUWSFileManager){
+				propValue = getProperty(tapConfig, KEY_LOG_ROTATION);
+				if (propValue != null)
+					((LocalUWSFileManager)fileManager).setLogRotationFreq(propValue);
+			}
 		}
 
-		// Log the successful initialization with set parameters:
-		buf.append(").");
-		logger.info(buf.toString());
+		// Log the successful initialisation of the logger:
+		logger.info("Logger initialized - {" + logger.getConfigString() + "}");
 	}
 
 	/**
@@ -412,10 +416,10 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 		String propValue = getProperty(tapConfig, KEY_TAP_FACTORY);
 		if (propValue == null)
 			tapFactory = new ConfigurableTAPFactory(this, tapConfig);
-		else if (hasConstructor(propValue, KEY_TAP_FACTORY, TAPFactory.class, new Class<?>[]{ServiceConnection.class,Properties.class}))
-			tapFactory = newInstance(propValue, KEY_TAP_FACTORY, TAPFactory.class, new Class<?>[]{ServiceConnection.class,Properties.class}, new Object[]{this,tapConfig});
+		else if (hasConstructor(propValue, KEY_TAP_FACTORY, TAPFactory.class, new Class<?>[]{ ServiceConnection.class, Properties.class }))
+			tapFactory = newInstance(propValue, KEY_TAP_FACTORY, TAPFactory.class, new Class<?>[]{ ServiceConnection.class, Properties.class }, new Object[]{ this, tapConfig });
 		else
-			tapFactory = newInstance(propValue, KEY_TAP_FACTORY, TAPFactory.class, new Class<?>[]{ServiceConnection.class}, new Object[]{this});
+			tapFactory = newInstance(propValue, KEY_TAP_FACTORY, TAPFactory.class, new Class<?>[]{ ServiceConnection.class }, new Object[]{ this });
 	}
 
 	/**
@@ -486,7 +490,7 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 
 				// fetch and set the ADQL<->DB mapping for all standard TAP_SCHEMA items:
 				if (conn instanceof JDBCConnection){
-					HashMap<String,String> dbMapping = new HashMap<String,String>(10);
+					HashMap<String, String> dbMapping = new HashMap<String, String>(10);
 					// fetch the mapping from the Property file:
 					for(String key : tapConfig.stringPropertyNames()){
 						if (key.trim().startsWith("TAP_SCHEMA") && tapConfig.getProperty(key) != null && tapConfig.getProperty(key).trim().length() > 0)
@@ -794,7 +798,7 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 			}
 			// custom OutputFormat
 			else if (isClassName(f))
-				outputFormats.add(TAPConfiguration.newInstance(f, KEY_OUTPUT_FORMATS, OutputFormat.class, new Class<?>[]{ServiceConnection.class}, new Object[]{this}));
+				outputFormats.add(TAPConfiguration.newInstance(f, KEY_OUTPUT_FORMATS, OutputFormat.class, new Class<?>[]{ ServiceConnection.class }, new Object[]{ this }));
 			// unknown format
 			else
 				throw new TAPException("Unknown output format: " + f);
@@ -1050,7 +1054,7 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 					// parse the coordinate system regular expression in order to check it:
 					else{
 						try{
-							STCS.buildCoordSysRegExp(new String[]{item});
+							STCS.buildCoordSysRegExp(new String[]{ item });
 							lstCoordSys.add(item);
 						}catch(ParseException pe){
 							throw new TAPException("Incorrect coordinate system regular expression (\"" + item + "\"): " + pe.getMessage(), pe);
@@ -1446,7 +1450,7 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 
 	@Override
 	public final LimitUnit[] getOutputLimitType(){
-		return new LimitUnit[]{LimitUnit.rows,LimitUnit.rows};
+		return new LimitUnit[]{ LimitUnit.rows, LimitUnit.rows };
 	}
 
 	@Override
@@ -1495,7 +1499,7 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 	 */
 	public void setUploadLimitType(final LimitUnit type){
 		if (type != null)
-			uploadLimitTypes = new LimitUnit[]{type,type};
+			uploadLimitTypes = new LimitUnit[]{ type, type };
 	}
 
 	/**
@@ -1516,7 +1520,8 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 				uploadLimits[0] = limit;
 				return true;
 			}
-		}catch(TAPException e){}
+		}catch(TAPException e){
+		}
 		return false;
 	}
 
@@ -1540,7 +1545,8 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 				uploadLimits[0] = limit;
 			// Set the new maximum output limit:
 			uploadLimits[1] = limit;
-		}catch(TAPException e){}
+		}catch(TAPException e){
+		}
 	}
 
 	@Override
diff --git a/src/tap/config/TAPConfiguration.java b/src/tap/config/TAPConfiguration.java
index 1f1b260..16b19d6 100644
--- a/src/tap/config/TAPConfiguration.java
+++ b/src/tap/config/TAPConfiguration.java
@@ -16,7 +16,8 @@ package tap.config;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
  *
- * Copyright 2015-2017 - Astronomisches Rechen Institut (ARI)
+ * Copyright 2015-2018 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.lang.reflect.Constructor;
@@ -35,8 +36,8 @@ import tap.backup.DefaultTAPBackupManager;
  * <p><i>This class implements the Design Pattern "Utility": no instance of this class can be created, it can not be extended,
  * and it must be used only thanks to its static classes and attributes.</i></p>
  *
- * @author Gr&eacute;gory Mantelet (ARI)
- * @version 2.1 (09/2017)
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.3 (07/2018)
  * @since 2.0
  */
 public final class TAPConfiguration {
@@ -83,14 +84,26 @@ public final class TAPConfiguration {
 	/** Name/Key of the property specifying the logger to use.
 	 * By default, {@link tap.log.DefaultTAPLog} is used. */
 	public final static String KEY_LOGGER = "logger";
-	/** Default value of the property {@link #KEY_LOGGER}: {@value #DEFAULT_LOGGER}. */
+	/** Default value of the property {@link #KEY_LOGGER}:
+	 * {@value #DEFAULT_LOGGER}. */
 	public final static String DEFAULT_LOGGER = "default";
-	/** Name/Key of the property specifying the minimum type of messages (i.e. DEBUG, INFO, WARNING, ERROR, FATAL)
-	 * that must be logged. By default all messages are logged...which is equivalent to set this property to "DEBUG". */
+	/** Name/Key of the property specifying the minimum type of messages
+	 * (i.e. DEBUG, INFO, WARNING, ERROR, FATAL) that must be logged by the
+	 * <strong>default logger</strong>.
+	 * <p>By default all messages are logged...which is equivalent to set this
+	 * property to "DEBUG".</p>
+	 * <p><i><b>Note:</b> If {@link #KEY_LOGGER} is set to a value different
+	 * from {@value #DEFAULT_LOGGER}, this property is ignored.</i></p> */
 	public final static String KEY_MIN_LOG_LEVEL = "min_log_level";
-	/** Name/Key of the property specifying the frequency of the log file rotation.
-	 * By default the log rotation occurs every day at midnight. */
+	/** Name/Key of the property specifying the frequency of the log file
+	 * rotation to set in the <strong>default logger</strong>.
+	 * <p>By default the log rotation occurs every day at midnight.</p>
+	 * <p><i><b>Note:</b> If {@link #KEY_LOGGER} is set to a value different
+	 * from {@value #DEFAULT_LOGGER}, this property is ignored.</i></p> */
 	public final static String KEY_LOG_ROTATION = "log_rotation";
+	/** SLF4J logger value: {@value #SLF4J_LOGGER}.
+	 * @since 2.3 */
+	public final static String SLF4J_LOGGER = "slf4j";
 
 	/* UWS BACKUP */
 	/** Name/Key of the property specifying the frequency (in milliseconds) of jobs backup.
@@ -164,7 +177,7 @@ public final class TAPConfiguration {
 	/** List of the most known JDBC drivers. For the moment this list contains 4 drivers:
 	 * oracle ("oracle.jdbc.OracleDriver"), postgresql ("org.postgresql.Driver"), mysql ("com.mysql.jdbc.Driver"),
 	 * sqlite ("org.sqlite.JDBC") and h2 ("org.h2.Driver"). */
-	public final static HashMap<String,String> VALUE_JDBC_DRIVERS = new HashMap<String,String>(4);
+	public final static HashMap<String, String> VALUE_JDBC_DRIVERS = new HashMap<String, String>(4);
 	static{
 		VALUE_JDBC_DRIVERS.put("oracle", "oracle.jdbc.OracleDriver");
 		VALUE_JDBC_DRIVERS.put("postgresql", "org.postgresql.Driver");
@@ -191,6 +204,11 @@ public final class TAPConfiguration {
 	/** Name/Key of the property specifying the local file path of the XML file containing the TAP metadata to load. */
 	public final static String KEY_METADATA_FILE = "metadata_file";
 
+	/* DATALINK KEY */
+	/** Name/Key of the property providing the API for the Datalink capability.
+	 * @since 2.3 */
+	public final static String KEY_DATALINK = "datalink";
+
 	/* HOME PAGE KEY */
 	/** Name/Key of the property specifying the TAP home page to use.
 	 * It can be a file, a URL or a class. If null, the default TAP home page of the library is used.
@@ -298,7 +316,8 @@ public final class TAPConfiguration {
 	public final static String KEY_TAP_FACTORY = "tap_factory";
 
 	/** No instance of this class should be created. */
-	private TAPConfiguration(){}
+	private TAPConfiguration(){
+	}
 
 	/**
 	 * <p>Read the asked property from the given Properties object.</p>
@@ -354,7 +373,7 @@ public final class TAPConfiguration {
 	 * @see #isClassName(String)
 	 */
 	@SuppressWarnings("unchecked")
-	public final static < C > Class<? extends C> fetchClass(final String value, final String propertyName, final Class<C> expectedType) throws TAPException{
+	public final static <C> Class<? extends C> fetchClass(final String value, final String propertyName, final Class<C> expectedType) throws TAPException{
 		if (!isClassName(value))
 			return null;
 
@@ -391,7 +410,7 @@ public final class TAPConfiguration {
 	 *
 	 * @since 2.1
 	 */
-	public final static < C > boolean hasConstructor(final String propValue, final String propName, final Class<C> expectedType, final Class<?>[] pTypes) throws TAPException{
+	public final static <C> boolean hasConstructor(final String propValue, final String propName, final Class<C> expectedType, final Class<?>[] pTypes) throws TAPException{
 		// Ensure the given name is a class name specification:
 		if (!isClassName(propValue))
 			throw new TAPException("Class name expected for the property \"" + propName + "\" instead of: \"" + propValue + "\"! The specified class must extend/implement " + expectedType.getName() + ".");
@@ -429,7 +448,7 @@ public final class TAPConfiguration {
 	 * @see #isClassName(String)
 	 * @see #fetchClass(String, String, Class)
 	 */
-	public final static < C > C newInstance(final String propValue, final String propName, final Class<C> expectedType) throws TAPException{
+	public final static <C> C newInstance(final String propValue, final String propName, final Class<C> expectedType) throws TAPException{
 		return newInstance(propValue, propName, expectedType, null, null);
 	}
 
@@ -457,7 +476,7 @@ public final class TAPConfiguration {
 	 * @see #isClassName(String)
 	 * @see #fetchClass(String, String, Class)
 	 */
-	public final static < C > C newInstance(final String propValue, final String propName, final Class<C> expectedType, final Class<?>[] pTypes, final Object[] parameters) throws TAPException{
+	public final static <C> C newInstance(final String propValue, final String propName, final Class<C> expectedType, final Class<?>[] pTypes, final Object[] parameters) throws TAPException{
 		// Ensure the given name is a class name specification:
 		if (!isClassName(propValue))
 			throw new TAPException("Class name expected for the property \"" + propName + "\" instead of: \"" + propValue + "\"! The specified class must extend/implement " + expectedType.getName() + ".");
@@ -529,7 +548,7 @@ public final class TAPConfiguration {
 
 		// If empty value, return an infinite limit:
 		if (value == null || value.length() == 0)
-			return new Object[]{-1,LimitUnit.rows};
+			return new Object[]{ -1, LimitUnit.rows };
 
 		// A. Parse the string from the end in order to extract the unit part.
 		//    The final step of the loop is the extraction of the numeric value, when the first digit is encountered.
@@ -596,7 +615,7 @@ public final class TAPConfiguration {
 			}
 		}
 
-		return new Object[]{((numValue < 0) ? -1 : numValue),unit};
+		return new Object[]{ ((numValue < 0) ? -1 : numValue), unit };
 	}
 
 }
diff --git a/src/tap/config/tap_configuration_file.html b/src/tap/config/tap_configuration_file.html
index 2345960..d9d5450 100644
--- a/src/tap/config/tap_configuration_file.html
+++ b/src/tap/config/tap_configuration_file.html
@@ -412,6 +412,40 @@
 							file 'service.log' inside the root directory of this
 							service (cf property <code>file_root_path</code>).
 						</li>
+						<li><b>slf4j</b>: wrapper for
+							<a href="https://www.slf4j.org">SLF4J</a>. All log
+							messages will be forwarded to SLF4J. It is up to the
+							implementor to add the suitable JAR files in the
+							Java class-path.
+							<p>
+								Exactly two JAR files are expected by SLF4J to
+								work as expected:
+							</p>
+							<ul>
+								<li>slf4j-api-{version}.jar (the main API)</li>
+								<li>and the slf4j-{binding}-{version}.jar</li>
+							</ul>
+							<p>
+								Depending on the chosen SLF4J binding, you may
+								also add another JAR file (e.g. Log4J, LogBack,
+								...) in the Java class-path.
+							</p>
+							<p>
+								A configuration file might also be needed.
+								There, it will be possible to configure the
+								following loggers:
+							</p>
+							<ul>
+								<li>"tap.service" (general/root purpose log),</li>
+								<li>"tap.service.UWS" (UWS actions),</li>
+								<li>"tap.service.HTTP" (HTTP requests and
+									responses),</li>
+								<li>"tap.service.JOB" (UWS's jobs actions),</li>
+								<li>"tap.service.THREAD" (job's thread actions),</li>
+								<li>"tap.service.TAP" (TAP actions),</li>
+								<li>and "tap.service.DB" (DB actions)</li>
+							</ul>
+						</li>
 						<li><b><em>{...}</em></b>: a custom logger. A class name MUST be
 							provided (between {...}). The specified class must
 							reference an implementation of tap.log.TAPLog. This
@@ -422,14 +456,17 @@
 					</ul>
 	 				<p><em>Default: <code>default</code> (i.e. tap.log.DefaultTAPLog)</em></p>
 				</td>
-				<td><ul><li>default</li><li>{aPackage.MyLogger}</li></ul></td>
+				<td><ul><li>default</li><li>slf4j</li><li>{aPackage.MyLogger}</li></ul></td>
 			</tr>
 			<tr class="optional">
 				<td class="done">min_log_level</td>
 				<td></td>
 				<td>text</td>
 				<td>
-					<p>Minimum level that a message must have in order to be logged.</p>
+					<p>
+						Minimum level that a message must have in order to be
+						logged by the default logger.
+					</p>
 					<p>5 possible values:</p>
 	 				<ul>
 	 					<li><b>DEBUG</b>: every messages are logged.</li>
@@ -438,6 +475,10 @@
 	 					<li><b>ERROR</b>: only ERROR and FATAL messages are logged.</li>
 	 					<li><b>FATAL</b>: only FATAL messages are logged.</li>
 	 				</ul>
+					<p><em><b>Note:</b>
+						This property is ignored if
+						<code>logger != default</code>.
+					</em></p>
 	 				<p><em>Default: <code>DEBUG</code> (every messages are logged)</em></p>
 				</td>
 				<td><ul><li>DEBUG</li><li>INFO</li><li>WANRING</li><li>ERROR</li><li>FATAL</li></ul></td>
@@ -447,8 +488,13 @@
 				<td></td>
 				<td>text</td>
 				<td>
-					<p>Frequency of the log file rotation. That's to say, logs will be written in a new file after this period. This avoid having too big log files.
-					Old log files are renamed so that highlighting its logging period.</p>
+					<p>
+						Frequency of the log file rotation performed by the
+						default logger. That's to say, logs will be written in a
+						new file after this period. This avoid having too big
+						log files. Old log files are renamed so that
+						highlighting its logging period.
+					</p>
 					<p>The frequency string must respect the following syntax:</p>
 	 				<ul>
 	 					<li><b>'D' hh mm</b>: daily schedule at hh:mm</li>
@@ -463,6 +509,10 @@
 	 					The frequency type is case sensitive! Then you should particularly pay attention at the case
 	 					when using the frequency types 'M' (monthly) and 'm' (every minute).
 	 				</em></p>
+					<p><em><b>Note:</b>
+						This property is ignored if
+						<code>logger != default</code>.
+					</em></p>
 	 				<p><em>Default: <code>D 0 0</code> (daily at midnight)</em></p>
 				</td>
 				<td><ul><li>D 6 30</li><li>W 2 6 30</li><li>M 2 6 30</li><li>h 10</li><li>m</li></ul></td>
diff --git a/src/tap/config/tap_full.properties b/src/tap/config/tap_full.properties
index dce6ffc..301f0cb 100644
--- a/src/tap/config/tap_full.properties
+++ b/src/tap/config/tap_full.properties
@@ -1,9 +1,9 @@
 ################################################################################
 #                        FULL TAP CONFIGURATION FILE                           #
 #                                                                              #
-# TAP Version: 2.1                                                             #
-# Date: 26 Feb. 2018                                                           #
-# Author: Gregory Mantelet (ARI)                                               #
+# TAP Version: 2.3                                                             #
+# Date: 31 July 2018                                                           #
+# Author: Gregory Mantelet (CDS;ARI)                                           #
 #                                                                              #
 ################################################################################ 
 
@@ -317,9 +317,29 @@ max_retention_period = 0
 # Logging method to use.
 # 
 # Only two possibilities are already implemented.
+#
 #     * default: default logger provided by the library. Any logged message
 #                will be appended in the file 'service.log' inside the root
 #                directory of this service (cf property 'file_root_path').
+#
+#     * slf4j: wrapper for SLF4J (https://www.slf4j.org). All log messages will
+#              be forwarded to SLF4J. It is up to the implementor to add the
+#              suitable JAR files in the Java class-path. Exactly two JAR files
+#              are expected by SLF4J to work as expected:
+#                  - slf4j-api-{version}.jar (the main API)
+#                  - and the slf4j-{binding}-{version}.jar.
+#              Depending on the chosen SLF4J binding, you may also add another
+#              JAR file (e.g. Log4J, LogBack, ...) in the Java class-path.
+#              A configuration file might also be needed. There, it will be
+#              possible to configure the  the following loggers:
+#                  - "tap.service" (general/root purpose log),
+#                  - "tap.service.UWS" (UWS actions),
+#                  - "tap.service.HTTP" (HTTP requests and responses),
+#                  - "tap.service.JOB" (UWS's jobs actions),
+#                  - "tap.service.THREAD" (job's thread actions),
+#                  - "tap.service.TAP" (TAP actions)
+#                  - and "tap.service.DB" (DB actions).
+#
 #     * {...}: a custom logger. A class name MUST be provided
 #              (between {...}). The specified class must reference
 #              an implementation of tap.log.TAPLog. This implementation
@@ -330,7 +350,8 @@ max_retention_period = 0
 logger = 
 
 # [OPTIONAL]
-# Minimum level that a message must have in order to be logged.
+# Minimum level that a message must have in order to be logged by the default
+# logger.
 #
 # 5 possible values:
 #     * DEBUG: every messages are logged.
@@ -339,13 +360,16 @@ logger =
 #     * ERROR: only ERROR and FATAL messages are logged.
 #     * FATAL: only FATAL messages are logged.
 #
+# Note: this property is ignored if `logger != default`.
+#
 # Default: DEBUG (every messages are logged)
 min_log_level = 
 
 # [OPTIONAL]
-# Frequency of the log file rotation. That's to say, logs will be written in a
-# new file after this period. This avoid having too big log files. Old log files
-# are renamed so that highlighting its logging period.
+# Frequency of the log file rotation performed by the default logger.
+# That's to say, logs will be written in a new file after this period. This
+# avoid having too big log files. Old log files are renamed so that highlighting
+# its logging period.
 # 
 # The frequency string must respect the following syntax:
 #     'D' hh mm: daily schedule at hh:mm
@@ -365,8 +389,10 @@ min_log_level =
 #          pay attention at the case when using the frequency types 'M'
 #          (monthly) and 'm' (every minute).
 # 
-# Note: this property is ignored if the file manager is not any more an
-#       extension of uws.service.file.LocalUWSFileManager.
+# Note 1: this property is ignored if the file manager is not any more an
+#         extension of uws.service.file.LocalUWSFileManager.
+#
+# Note 2: this property is ignored if `logger != default`.
 # 
 # Default: D 0 0 (daily at midnight)
 log_rotation = 
diff --git a/src/tap/log/Slf4jTAPLog.java b/src/tap/log/Slf4jTAPLog.java
new file mode 100644
index 0000000..456048d
--- /dev/null
+++ b/src/tap/log/Slf4jTAPLog.java
@@ -0,0 +1,174 @@
+package tap.log;
+
+/*
+ * This file is part of TAPLibrary.
+ *
+ * TAPLibrary is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * TAPLibrary is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright 2018 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ */
+
+import java.sql.SQLException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import tap.TAPExecutionReport;
+import tap.TAPSyncJob;
+import tap.db.DBConnection;
+import tap.parameters.TAPParameters;
+import uws.service.log.Slf4jUWSLog;
+
+/**
+ * This implementation of {@link TAPLog} forwards all log submission to
+ * <a href="https://www.slf4j.org">SLF4J</a>.
+ *
+ * <p>
+ * 	Thus, a TAP implementor can choose how to deal with logs. SLF4J already
+ * 	provides bridges at least for the following logging frameworks:
+ * </p>
+ * <ul>
+ * 	<li><a href="https://www.slf4j.org/api/org/slf4j/helpers/NOPLogger.html">
+ * 		NOPE</a> (all logs are merely discarded),</li>
+ * 	<li><a href="https://www.slf4j.org/apidocs/org/slf4j/impl/SimpleLogger.html">
+ * 		simple logging mechanism</a> (console, file, ...),</li>
+ * 	<li><a href="https://docs.oracle.com/javase/7/docs/api/java/util/logging/package-summary.html">JUL</a> (java.util.logging),</li>
+ * 	<li><a href="https://logging.apache.org/log4j">Log4J</a>,</li>
+ * 	<li><a href="http://commons.apache.org/proper/commons-logging/">JCL</a>
+ * 	    (Jakarta Commons Logging),</li>
+ * 	<li>and <a href="https://logback.qos.ch/">LogBack</a>.</li>
+ * </ul>
+ *
+ * <b>SLF4J loggers</b>
+ *
+ * <p>
+ * 	A different logger is used in function of the log entry's CONTEXT.
+ * 	Four values are supported: "UWS", "HTTP", "JOB" and "THREAD". If a different
+ * 	value is set for the CONTEXT, the general/root logger will be used.
+ * </p>
+ * <p>The ID of each logger follows this rule:</p>
+ * <pre>{@value #DEFAULT_ROOT_LOG_ID}{SUFFIX}</pre>
+ * <p>
+ * 	The ID of the general/root logger is just
+ * 	<code>{@value #DEFAULT_ROOT_LOG_ID}</code>. For all the other specific
+ * 	loggers, <code>{SUFFIX}</code> is a dot followed by one among
+ * 	{@value #UWS_LOG_ID_SUFFIX}, {@value #HTTP_LOG_ID_SUFFIX},
+ * 	{@value #JOB_LOG_ID_SUFFIX} and {@value #THREAD_LOG_ID_SUFFIX}. Thus, the
+ * 	full ID of the logger for the HTTP requests is:
+ * </p>
+ * <pre>uws.service.http</pre>
+ *
+ * <b>Log entries format</b>
+ *
+ * <p>All log entries are formatted as follow:</p>
+ * <pre>{EVENT} - {ID} - {MESSAGE} - {ADDITIONAL_DATA}</pre>
+ * <p>
+ * 	{EVENT}, {ID} and {ADDITIONAL_DATA} may not be all provided for
+ * 	some messages ; all are optional, and especially ADDITIONAL_DATA. When not
+ * 	provided, each is replaced by an empty string. In the special case of
+ * 	ADDITIONAL_DATA, if none is provided, the " - " prefixing it is not written.
+ * </p>
+ *
+ * @author Gr&eacute;gory Mantelet (CDS)
+ * @version 2.3 (07/2018)
+ * @since 2.3
+ */
+public class Slf4jTAPLog extends Slf4jUWSLog implements TAPLog {
+
+	/** Default SLF4J identifier for the general/root logger to use. */
+	protected static final String DEFAULT_ROOT_LOG_ID = "tap.service";
+
+	/** Suffix to append to the root logger ID for the TAP logger. */
+	protected static final String TAP_LOG_ID_SUFFIX = "TAP";
+
+	/** Suffix to append to the root logger ID for the DATABASE logger. */
+	protected static final String DB_LOG_ID_SUFFIX = "DB";
+
+	/** Logger for TAP jobs actions. */
+	protected final Logger tapLogger;
+
+	/** Logger for database actions. */
+	protected final Logger dbLogger;
+
+	/**
+	 * Initialise this logger.
+	 */
+	public Slf4jTAPLog(){
+		this(DEFAULT_ROOT_LOG_ID);
+	}
+
+	@Override
+	public String getConfigString(){
+		return "type: \"SLF4J\", loggers: [\"" + rootLogger.getName() + "\", \"" + uwsLogger.getName() + "\", \"" + httpLogger.getName() + "\", \"" + jobLogger.getName() + "\", \"" + threadLogger.getName() + "\", \"" + tapLogger.getName() + "\", \"" + dbLogger.getName() + "\"]";
+	}
+
+	/**
+	 * Initialise this logger with the specified SLF4J logger.
+	 */
+	protected Slf4jTAPLog(final String logId){
+		super((logId == null || logId.trim().length() == 0) ? DEFAULT_ROOT_LOG_ID : logId);
+
+		final String rootLogId = rootLogger.getName();
+		tapLogger = LoggerFactory.getLogger(rootLogId + "." + TAP_LOG_ID_SUFFIX);
+		dbLogger = LoggerFactory.getLogger(rootLogId + "." + DB_LOG_ID_SUFFIX);
+	}
+
+	@Override
+	public void logDB(final LogLevel level, final DBConnection connection, final String event, final String message, final Throwable error){
+		// log the main given error:
+		log(level, dbLogger, event, (connection != null ? connection.getID() : null), message, null, error);
+
+		/* Some SQL exceptions (like BatchUpdateException) have a next exception which provides more information.
+		 * Here, the stack trace of the next exception is also logged:
+		 */
+		if (error != null && error instanceof SQLException && ((SQLException)error).getNextException() != null){
+			Throwable nextError;
+			int indNextError = 1;
+			/* show all next exceptions
+			 * (limited to 3 so that not filling the log with a lot of stack
+			 *  traces): */
+			do{
+				nextError = ((SQLException)error).getNextException();
+				log(level, dbLogger, event, (connection != null ? connection.getID() : null), "[NEXT EXCEPTION - " + (indNextError++) + "]", null, nextError);
+			}while(error != null && indNextError <= 3);
+		}
+	}
+
+	@Override
+	public void logTAP(final LogLevel level, final Object obj, final String event, final String message, final Throwable error){
+		// Get more information (when known event and available object):
+		String jobId = null, msgAppend = null;
+		try{
+			if (event != null && obj != null){
+				if (event.equals("SYNC_INIT"))
+					msgAppend = "QUERY=" + ((TAPParameters)obj).getQuery();
+				else if (obj instanceof TAPSyncJob){
+					log(level, tapLogger, event, ((TAPSyncJob)obj).getID(), message, null, error);
+					return;
+				}else if (obj instanceof TAPExecutionReport){
+					TAPExecutionReport report = (TAPExecutionReport)obj;
+					jobId = report.jobID;
+					msgAppend = (report.synchronous ? "SYNC" : "ASYNC") + ",duration=" + report.getTotalDuration() + "ms (upload=" + report.getUploadDuration() + ",parse=" + report.getParsingDuration() + ",exec=" + report.getExecutionDuration() + ",format[" + report.parameters.getFormat() + "]=" + report.getFormattingDuration() + ")";
+				}else if (event.equalsIgnoreCase("WRITING_ERROR"))
+					jobId = obj.toString();
+			}
+		}catch(Throwable t){
+			error("Error while preparing a log message in logTAP(...)! The message will be logger but without additional information such as the job ID.", t);
+		}
+
+		// Log the message:
+		log(level, tapLogger, event, jobId, message, msgAppend, error);
+	}
+
+}
diff --git a/src/uws/config/ConfigurableUWSServlet.java b/src/uws/config/ConfigurableUWSServlet.java
index d449236..bd4d6ac 100644
--- a/src/uws/config/ConfigurableUWSServlet.java
+++ b/src/uws/config/ConfigurableUWSServlet.java
@@ -2,21 +2,22 @@ package uws.config;
 
 /*
  * This file is part of UWSLibrary.
- * 
+ *
  * UWSLibrary is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Lesser General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
- * 
+ *
  * UWSLibrary is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU Lesser General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU Lesser General Public License
  * along with UWSLibrary.  If not, see <http://www.gnu.org/licenses/>.
- * 
- * Copyright 2016-2017 - Astronomisches Rechen Institut (ARI)
+ *
+ * Copyright 2016-2018 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import static uws.config.UWSConfiguration.DEFAULT_BACKUP_BY_USER;
@@ -49,6 +50,7 @@ import static uws.config.UWSConfiguration.KEY_USER_IDENTIFIER;
 import static uws.config.UWSConfiguration.KEY_UWS_FACTORY;
 import static uws.config.UWSConfiguration.KEY_XSLT_STYLESHEET;
 import static uws.config.UWSConfiguration.REGEXP_JOB_LIST_NAME;
+import static uws.config.UWSConfiguration.SLF4J_LOGGER;
 import static uws.config.UWSConfiguration.UWS_CONF_PARAMETER;
 import static uws.config.UWSConfiguration.VALUE_LOCAL;
 import static uws.config.UWSConfiguration.VALUE_NEVER;
@@ -92,20 +94,23 @@ import uws.service.error.ServiceErrorWriter;
 import uws.service.file.LocalUWSFileManager;
 import uws.service.file.UWSFileManager;
 import uws.service.log.DefaultUWSLog;
+import uws.service.log.Slf4jUWSLog;
 import uws.service.log.UWSLog;
 import uws.service.log.UWSLog.LogLevel;
 
 /**
  * <p>HTTP servlet fully configured with a UWS configuration file.</p>
- * 
+ *
  * <p>
- * 	This configuration file may be specified in the initial parameter named {@link UWSConfiguration#UWS_CONF_PARAMETER}
- * 	of this servlet inside the WEB-INF/web.xml file. If none is specified, the file {@link UWSConfiguration#DEFAULT_UWS_CONF_FILE}
- * 	will be searched inside the directories of the classpath, and inside WEB-INF and META-INF.
+ * 	This configuration file may be specified in the initial parameter named
+ * 	{@link UWSConfiguration#UWS_CONF_PARAMETER} of this servlet inside the
+ * 	WEB-INF/web.xml file. If none is specified, the file
+ * 	{@link UWSConfiguration#DEFAULT_UWS_CONF_FILE} will be searched inside the
+ * 	directories of the classpath, and inside WEB-INF and META-INF.
  * </p>
- * 
- * @author Gr&eacute;gory Mantelet (ARI)
- * @version 4.2 (09/2017)
+ *
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.3 (07/2018)
  * @since 4.2
  */
 public class ConfigurableUWSServlet extends HttpServlet {
@@ -158,7 +163,8 @@ public class ConfigurableUWSServlet extends HttpServlet {
 		}finally{
 			try{
 				input.close();
-			}catch(IOException ioe2){}
+			}catch(IOException ioe2){
+			}
 		}
 
 		/* 4. CREATE THE FILE MANAGER */
@@ -200,7 +206,7 @@ public class ConfigurableUWSServlet extends HttpServlet {
 			// If it is a class path, replace the current home page by an instance of this class:
 			if (isClassName(propValue)){
 				try{
-					uws.replaceUWSAction(newInstance(propValue, KEY_HOME_PAGE, ShowHomePage.class, new Class<?>[]{UWSService.class}, new Object[]{uws}));
+					uws.replaceUWSAction(newInstance(propValue, KEY_HOME_PAGE, ShowHomePage.class, new Class<?>[]{ UWSService.class }, new Object[]{ uws }));
 				}catch(UWSException te){
 					throw new ServletException(te.getMessage(), te.getCause());
 				}
@@ -243,14 +249,14 @@ public class ConfigurableUWSServlet extends HttpServlet {
 
 	/**
 	 * Initialize the management of UWS service files using the given UWS configuration file.
-	 * 
+	 *
 	 * @param uwsConfig		The content of the UWS configuration file.
 	 * @param webAppRootDir	The directory of the Web Application running this UWS service.
 	 *                     	<em>This directory may be used only to search the root UWS directory
 	 *                     	if specified with a relative path in the UWS configuration file.</em>
-	 * 
+	 *
 	 * @return	The created file manager.
-	 * 
+	 *
 	 * @throws UWSException	If a property is wrong or missing, or if an error occurs while creating the file manager.
 	 */
 	private UWSFileManager createFileManager(final Properties uwsConfig, final String webAppRootDir) throws UWSException{
@@ -286,15 +292,16 @@ public class ConfigurableUWSServlet extends HttpServlet {
 		}
 		// CUSTOM file manager:
 		else
-			return newInstance(fileManagerType, KEY_FILE_MANAGER, UWSFileManager.class, new Class<?>[]{Properties.class}, new Object[]{uwsConfig});
+			return newInstance(fileManagerType, KEY_FILE_MANAGER, UWSFileManager.class, new Class<?>[]{ Properties.class }, new Object[]{ uwsConfig });
 	}
 
 	/**
-	 * Initialize the UWS logger with the given UWS configuration file.
-	 * 
+	 * Initialise the UWS logger with the given UWS configuration file.
+	 *
 	 * @param uwsConfig		The content of the UWS configuration file.
-	 * @param fileManager	The file manager to access the log file(s).
-	 * 
+	 * @param fileManager	The file manager to access the log file(s)
+	 *                   	(if needed).
+	 *
 	 * @throws UWSException	If no instance of the specified custom logger can
 	 *                     	be created.
 	 */
@@ -304,64 +311,66 @@ public class ConfigurableUWSServlet extends HttpServlet {
 		String propValue = getProperty(uwsConfig, KEY_LOGGER);
 		if (propValue == null || propValue.trim().equalsIgnoreCase(DEFAULT_LOGGER))
 			logger = new DefaultUWSLog(fileManager);
+		else if (propValue == null || propValue.trim().equalsIgnoreCase(SLF4J_LOGGER))
+			logger = new Slf4jUWSLog();
 		else
-			logger = newInstance(propValue, KEY_LOGGER, UWSLog.class, new Class<?>[]{UWSFileManager.class}, new Object[]{fileManager});
+			logger = newInstance(propValue, KEY_LOGGER, UWSLog.class, new Class<?>[]{ UWSFileManager.class }, new Object[]{ fileManager });
 
-		StringBuffer buf = new StringBuffer("Logger initialized");
+		// Set some options for the default logger:
+		if (propValue == null || propValue.trim().equalsIgnoreCase(DEFAULT_LOGGER)){
 
-		// Set the minimum log level:
-		propValue = getProperty(uwsConfig, KEY_MIN_LOG_LEVEL);
-		if (propValue != null){
-			try{
-				((DefaultUWSLog)logger).setMinLogLevel(LogLevel.valueOf(propValue.toUpperCase()));
-			}catch(IllegalArgumentException iae){}
-		}
-		buf.append(" (minimum log level: ").append(((DefaultUWSLog)logger).getMinLogLevel());
-
-		// Set the log rotation period, if any:
-		if (fileManager instanceof LocalUWSFileManager){
-			propValue = getProperty(uwsConfig, KEY_LOG_ROTATION);
-			if (propValue != null)
-				((LocalUWSFileManager)fileManager).setLogRotationFreq(propValue);
-			buf.append(", log rotation: ").append(((LocalUWSFileManager)fileManager).getLogRotationFreq());
+			// Set the minimum log level:
+			propValue = getProperty(uwsConfig, KEY_MIN_LOG_LEVEL);
+			if (propValue != null){
+				try{
+					((DefaultUWSLog)logger).setMinLogLevel(LogLevel.valueOf(propValue.toUpperCase()));
+				}catch(IllegalArgumentException iae){
+				}
+			}
+
+			// Set the log rotation period, if any:
+			if (fileManager instanceof LocalUWSFileManager){
+				propValue = getProperty(uwsConfig, KEY_LOG_ROTATION);
+				if (propValue != null)
+					((LocalUWSFileManager)fileManager).setLogRotationFreq(propValue);
+			}
 		}
 
-		// Log the successful initialization with set parameters:
-		buf.append(").");
-		logger.info(buf.toString());
+		// Log the successful initialisation of the logger:
+		logger.info("Logger initialized - {" + logger.getConfigString() + "}");
 
 		return logger;
 	}
 
 	/**
 	 * <p>Initialize the {@link UWSFactory} to use.</p>
-	 * 
+	 *
 	 * <p>
 	 * 	The built factory is either a {@link ConfigurableUWSFactory} instance (by default) or
 	 * 	an instance of the class specified in the UWS configuration file.
 	 * </p>
-	 * 
+	 *
 	 * @param uwsConfig		The content of the UWS configuration file.
-	 * 
+	 *
 	 * @throws UWSException	If an error occurs while building the specified {@link UWSFactory}.
-	 * 
+	 *
 	 * @see ConfigurableUWSFactory
 	 */
 	private UWSFactory createFactory(final Properties uwsConfig) throws UWSException{
 		String propValue = getProperty(uwsConfig, KEY_UWS_FACTORY);
 		if (propValue == null)
 			return new ConfigurableUWSFactory(uwsConfig);
-		else if (hasConstructor(propValue, KEY_UWS_FACTORY, UWSFactory.class, new Class<?>[]{Properties.class}))
-			return newInstance(propValue, KEY_UWS_FACTORY, UWSFactory.class, new Class<?>[]{Properties.class}, new Object[]{uwsConfig});
+		else if (hasConstructor(propValue, KEY_UWS_FACTORY, UWSFactory.class, new Class<?>[]{ Properties.class }))
+			return newInstance(propValue, KEY_UWS_FACTORY, UWSFactory.class, new Class<?>[]{ Properties.class }, new Object[]{ uwsConfig });
 		else
 			return newInstance(propValue, KEY_UWS_FACTORY, UWSFactory.class, new Class<?>[]{}, new Object[]{});
 	}
 
 	/**
 	 * Create a {@link UWSBackupManager} if needed, thanks to the configuration provided in the UWS configuration file.
-	 * 
+	 *
 	 * @param uwsConf	List of properties set in the UWS configuration file.
-	 * 
+	 *
 	 * @throws ServletException	If any error occurs when initializing the {@link UWSBackupManager}.
 	 */
 	private void initBackup(final Properties uwsConf) throws ServletException{
@@ -403,9 +412,9 @@ public class ConfigurableUWSServlet extends HttpServlet {
 
 	/**
 	 * Initialize the UWS user identification method.
-	 * 
+	 *
 	 * @param uwsConfig	The content of the UWS configuration file.
-	 * 
+	 *
 	 * @throws ServletException	If the corresponding UWS configuration property is wrong.
 	 */
 	private void initUserIdentifier(final Properties uwsConfig) throws ServletException{
@@ -422,9 +431,9 @@ public class ConfigurableUWSServlet extends HttpServlet {
 
 	/**
 	 * Initialize all the specified job lists.
-	 * 
+	 *
 	 * @param uwsConfig	The content of the UWS configuration file.
-	 * 
+	 *
 	 * @throws ServletException	If the corresponding UWS configuration property is wrong.
 	 */
 	private void initJobLists(final Properties uwsConf) throws ServletException{
@@ -452,7 +461,7 @@ public class ConfigurableUWSServlet extends HttpServlet {
 					// if an execution manager is provided, set it:
 					propValue = getProperty(uwsConf, jlName + "." + KEY_EXECUTION_MANAGER);
 					if (propValue != null)
-						execManager = newInstance(propValue, jlName + "." + KEY_EXECUTION_MANAGER, ExecutionManager.class, new Class<?>[]{UWSLog.class}, new Object[]{uws.getLogger()});
+						execManager = newInstance(propValue, jlName + "." + KEY_EXECUTION_MANAGER, ExecutionManager.class, new Class<?>[]{ UWSLog.class }, new Object[]{ uws.getLogger() });
 
 					/* if none is provided, the default execution manager will be used
 					 * EXCEPT if a maximum number of running jobs is specified ; in such case a QueuedExecutionManager will be used. */
@@ -495,9 +504,9 @@ public class ConfigurableUWSServlet extends HttpServlet {
 
 	/**
 	 * Add all additional custom actions listed in the configuration file.
-	 * 
+	 *
 	 * @param uwsConfig	The content of the UWS configuration file.
-	 * 
+	 *
 	 * @throws ServletException	If the corresponding UWS configuration property is wrong.
 	 */
 	private void addCustomActions(final Properties uwsConfig) throws ServletException{
@@ -519,7 +528,7 @@ public class ConfigurableUWSServlet extends HttpServlet {
 						actionClass = actionClass.substring(actionIndex + 1);
 					}
 					// create an instance of the specified action:
-					UWSAction action = newInstance(actionClass, KEY_ADD_UWS_ACTIONS, UWSAction.class, new Class<?>[]{UWSService.class}, new Object[]{uws});
+					UWSAction action = newInstance(actionClass, KEY_ADD_UWS_ACTIONS, UWSAction.class, new Class<?>[]{ UWSService.class }, new Object[]{ uws });
 					// add or replacing depending if an action with the same name already exists or not:
 					boolean added = false;
 					if (uws.getUWSAction(action.getName()) != null)
@@ -542,9 +551,9 @@ public class ConfigurableUWSServlet extends HttpServlet {
 
 	/**
 	 * Add all additional UWS serialized listed in the configuration file.
-	 * 
+	 *
 	 * @param uwsConfig	The content of the UWS configuration file.
-	 * 
+	 *
 	 * @throws ServletException	If the corresponding UWS configuration property is wrong.
 	 */
 	private void addCustomSerializers(final Properties uwsConfig) throws ServletException{
@@ -570,9 +579,9 @@ public class ConfigurableUWSServlet extends HttpServlet {
 
 	/**
 	 * Initialize the error writer of the UWS service.
-	 * 
+	 *
 	 * @param uwsConfig	The content of the UWS configuration file.
-	 * 
+	 *
 	 * @throws ServletException	If the corresponding UWS configuration property is wrong.
 	 */
 	private void initXSLTStylesheet(final Properties uwsConfig) throws ServletException{
@@ -590,9 +599,9 @@ public class ConfigurableUWSServlet extends HttpServlet {
 
 	/**
 	 * Initialize the error writer of the UWS service.
-	 * 
+	 *
 	 * @param uwsConfig	The content of the UWS configuration file.
-	 * 
+	 *
 	 * @throws ServletException	If the corresponding UWS configuration property is wrong.
 	 */
 	private void initErrorWriter(final Properties uwsConfig) throws ServletException{
@@ -609,12 +618,12 @@ public class ConfigurableUWSServlet extends HttpServlet {
 
 	/**
 	 * Search the given file name/path in the directories of the classpath, then inside WEB-INF and finally inside META-INF.
-	 * 
+	 *
 	 * @param filePath	A file name/path.
 	 * @param config	Servlet configuration (containing also the context class loader - link with the servlet classpath).
-	 * 
+	 *
 	 * @return	The input stream toward the specified file, or NULL if no file can be found.
-	 * 
+	 *
 	 * @since 2.0
 	 */
 	protected final InputStream searchFile(String filePath, final ServletConfig config){
@@ -639,20 +648,20 @@ public class ConfigurableUWSServlet extends HttpServlet {
 
 	/**
 	 * <p>Resolve the given file name/path.</p>
-	 * 
+	 *
 	 * <p>Only the URI protocol "file:" is allowed. If the protocol is different a {@link UWSException} is thrown.</p>
-	 * 
+	 *
 	 * <p>
 	 * 	If not an absolute URI, the given path may be either relative or absolute. A relative path is always considered
 	 * 	as relative from the Web Application directory (supposed to be given in 2nd parameter).
 	 * </p>
-	 * 
+	 *
 	 * @param filePath			URI/Path/Name of the file to get.
 	 * @param webAppRootPath	Web Application directory local path.
 	 * @param propertyName		Name of the property which gives the given file path.
-	 * 
+	 *
 	 * @return	The specified File instance.
-	 * 
+	 *
 	 * @throws UWSException	If the given URI is malformed or if the used URI scheme is different from "file:".
 	 */
 	protected final File getFile(final String filePath, final String webAppRootPath, final String propertyName) throws UWSException{
diff --git a/src/uws/config/UWSConfiguration.java b/src/uws/config/UWSConfiguration.java
index 5814445..42c2aca 100644
--- a/src/uws/config/UWSConfiguration.java
+++ b/src/uws/config/UWSConfiguration.java
@@ -2,21 +2,22 @@ package uws.config;
 
 /*
  * This file is part of UWSLibrary.
- * 
+ *
  * UWSLibrary is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Lesser General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
- * 
+ *
  * UWSLibrary is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU Lesser General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU Lesser General Public License
  * along with UWSLibrary.  If not, see <http://www.gnu.org/licenses/>.
- * 
- * Copyright 2016-2017 - Astronomisches Rechen Institut (ARI)
+ *
+ * Copyright 2016-2018 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.lang.reflect.Constructor;
@@ -34,13 +35,13 @@ import uws.service.request.UWSRequestParser;
 
 /**
  * <p>Utility class gathering tool functions and properties' names useful to deal with a UWS configuration file.</p>
- * 
+ *
  * <p><i>This class implements the Design Pattern "Utility": no instance of this class can be created, it can not be extended,
  * and it must be used only thanks to its static classes and attributes.</i></p>
- * 
- * @author Gr&eacute;gory Mantelet (ARI)
- * @version 4.2 (09/2017)
- * @since 4.2
+ *
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 4.3 (07/2018)
+ * @since 4.3
  */
 public final class UWSConfiguration {
 
@@ -181,14 +182,26 @@ public final class UWSConfiguration {
 	/** Name/Key of the property specifying the logger to use.
 	 * By default, {@link uws.service.log.DefaultUWSLog} is used. */
 	public final static String KEY_LOGGER = "logger";
-	/** Default value of the property {@link #KEY_LOGGER}: {@value #DEFAULT_LOGGER}. */
+	/** Default value of the property {@link #KEY_LOGGER}:
+	 * {@value #DEFAULT_LOGGER}. */
 	public final static String DEFAULT_LOGGER = "default";
-	/** Name/Key of the property specifying the minimum type of messages (i.e. DEBUG, INFO, WARNING, ERROR, FATAL)
-	 * that must be logged. By default all messages are logged...which is equivalent to set this property to "DEBUG". */
+	/** Name/Key of the property specifying the minimum type of messages
+	 * (i.e. DEBUG, INFO, WARNING, ERROR, FATAL) that must be logged by the
+	 * <strong>default logger</strong>.
+	 * <p>By default all messages are logged...which is equivalent to set this
+	 * property to "DEBUG".</p>
+	 * <p><i><b>Note:</b> If {@link #KEY_LOGGER} is set to a value different
+	 * from {@value #DEFAULT_LOGGER}, this property is ignored.</i></p> */
 	public final static String KEY_MIN_LOG_LEVEL = "min_log_level";
-	/** Name/Key of the property specifying the frequency of the log file rotation.
-	 * By default the log rotation occurs every day at midnight. */
+	/** Name/Key of the property specifying the frequency of the log file
+	 * rotation to set in the <strong>default logger</strong>.
+	 * <p>By default the log rotation occurs every day at midnight.</p>
+	 * <p><i><b>Note:</b> If {@link #KEY_LOGGER} is set to a value different
+	 * from {@value #DEFAULT_LOGGER}, this property is ignored.</i></p> */
 	public final static String KEY_LOG_ROTATION = "log_rotation";
+	/** SLF4J logger value: {@value #SLF4J_LOGGER}.
+	 * @since 4.3 */
+	public final static String SLF4J_LOGGER = "slf4j";
 
 	/* UWS BACKUP */
 
@@ -243,7 +256,8 @@ public final class UWSConfiguration {
 	public final static String KEY_UWS_FACTORY = "uws_factory";
 
 	/** No instance of this class should be created. */
-	private UWSConfiguration(){}
+	private UWSConfiguration(){
+	}
 
 	/**
 	 * <p>Read the asked property from the given Properties object.</p>
@@ -251,10 +265,10 @@ public final class UWSConfiguration {
 	 * 	<li>The returned property value is trimmed (no space at the beginning and at the end of the string).</li>
 	 * 	<li>If the value is empty (length=0), NULL is returned.</li>
 	 * </ul>
-	 * 
+	 *
 	 * @param prop	List of property
 	 * @param key	Property whose the value is requested.
-	 * 
+	 *
 	 * @return		Return property value.
 	 */
 	public final static String getProperty(final Properties prop, final String key){
@@ -272,14 +286,14 @@ public final class UWSConfiguration {
 
 	/**
 	 * Extract the job list name prefixing the given property name.
-	 * 
+	 *
 	 * <p><b>Important:</b>
 	 * 	This function aims to be used for properties prefixed by a job list name such as
 	 * 	{@link #REGEXP_JOB_THREAD}, {@link #REGEXP_PARAMETERS}, ...
 	 * </p>
-	 * 
+	 *
 	 * @param compoundPropertyName	Property name prefixed by a job list name.
-	 * 
+	 *
 	 * @return	The prefix of the given property name,
 	 *        	or <code>null</code> if the given name is <code>null</code>
 	 *             or if it is not prefixed by a valid job list name.
@@ -293,11 +307,11 @@ public final class UWSConfiguration {
 	/**
 	 * Test whether a property value is a class name.
 	 * Expected syntax: a non-empty string surrounded by brackets ('{' and '}').
-	 * 
+	 *
 	 * Note: The class name itself is not checked!
-	 * 
+	 *
 	 * @param value	Property value.
-	 * 
+	 *
 	 * @return <i>true</i> if the given value is formatted as a class name, <i>false</i> otherwise.
 	 */
 	public final static boolean isClassName(final String value){
@@ -306,20 +320,20 @@ public final class UWSConfiguration {
 
 	/**
 	 * Fetch the class object corresponding to the class name provided between brackets in the given value.
-	 * 
+	 *
 	 * @param value			Value which is supposed to contain the class name between brackets (see {@link #isClassName(String)} for more details)
 	 * @param propertyName	Name of the property associated with the parameter "value".
 	 * @param expectedType	Type of the class expected to be returned ; it is also the type which parameterizes this function: C.
-	 * 
+	 *
 	 * @return	The corresponding Class object.
-	 * 
+	 *
 	 * @throws UWSException	If the class name is incorrect
 	 *                     	or if its type is not compatible with the parameterized type C (represented by the parameter "expectedType").
-	 * 
+	 *
 	 * @see #isClassName(String)
 	 */
 	@SuppressWarnings("unchecked")
-	public final static < C > Class<? extends C> fetchClass(final String value, final String propertyName, final Class<C> expectedType) throws UWSException{
+	public final static <C> Class<? extends C> fetchClass(final String value, final String propertyName, final Class<C> expectedType) throws UWSException{
 		if (!isClassName(value))
 			return null;
 
@@ -342,19 +356,19 @@ public final class UWSConfiguration {
 
 	/**
 	 * Test whether the specified class has a constructor with the specified parameters.
-	 * 
+	 *
 	 * @param propValue		Value which is supposed to contain the class name between brackets (see {@link #isClassName(String)} for more details)
 	 * @param propName		Name of the property associated with the parameter "propValue".
 	 * @param expectedType	Type of the class expected to be returned ; it is also the type which parameterizes this function: C.
 	 * @param pTypes		List of each constructor parameter type. Each type MUST be exactly the type declared in the class constructor to select. <i>NULL or empty array if no parameter.</i>
-	 * 
+	 *
 	 * @return	<code>true</code> if the specified class has a constructor with the specified parameters,
 	 *        	<code>false</code> otherwise.
-	 * 
+	 *
 	 * @throws UWSException	If the class name is incorrect
 	 *                     	or if its type is not compatible with the parameterized type C (represented by the parameter "expectedType").
 	 */
-	public final static < C > boolean hasConstructor(final String propValue, final String propName, final Class<C> expectedType, final Class<?>[] pTypes) throws UWSException{
+	public final static <C> boolean hasConstructor(final String propValue, final String propName, final Class<C> expectedType, final Class<?>[] pTypes) throws UWSException{
 		// Ensure the given name is a class name specification:
 		if (!isClassName(propValue))
 			throw new UWSException("Class name expected for the property \"" + propName + "\" instead of: \"" + propValue + "\"! The specified class must extend/implement " + expectedType.getName() + ".");
@@ -375,23 +389,23 @@ public final class UWSConfiguration {
 
 	/**
 	 * Fetch the specified constructor of the class corresponding to the class name provided between brackets in the given value.
-	 * 
+	 *
 	 * <p><b>IMPORTANT:</b>
 	 * 	The number and types of given parameters MUST match exactly to the list of parameter types.
 	 * </p>
-	 * 
+	 *
 	 * @param propValue		Value which is supposed to contain the class name between brackets (see {@link #isClassName(String)} for more details)
 	 * @param propName		Name of the property associated with the parameter "propValue".
 	 * @param expectedType	Type of the class expected to be returned ; it is also the type which parameterizes this function: C.
 	 * @param pTypes		List of each constructor parameter type. Each type MUST be exactly the type declared in the class constructor to select. <i>NULL or empty array if no parameter.</i>
-	 * 
+	 *
 	 * @return	The corresponding constructor.
-	 * 
+	 *
 	 * @throws UWSException	If the class name is incorrect
 	 *                     	or if its type is not compatible with the parameterized type C (represented by the parameter "expectedType")
 	 *                     	or if the constructor with the specified parameters can not be found.
 	 */
-	public final static < C > Constructor<? extends C> fetchConstructor(final String propValue, final String propName, final Class<C> expectedType, final Class<?>[] pTypes) throws UWSException{
+	public final static <C> Constructor<? extends C> fetchConstructor(final String propValue, final String propName, final Class<C> expectedType, final Class<?>[] pTypes) throws UWSException{
 		// Ensure the given name is a class name specification:
 		if (!isClassName(propValue))
 			throw new UWSException("Class name expected for the property \"" + propName + "\" instead of: \"" + propValue + "\"! The specified class must extend/implement " + expectedType.getName() + ".");
@@ -424,52 +438,52 @@ public final class UWSConfiguration {
 
 	/**
 	 * <p>Create an instance of the specified class. The class name is expected to be surrounded by {} in the given value.</p>
-	 * 
+	 *
 	 * <p>The instance is created using the empty constructor of the specified class.</p>
-	 * 
+	 *
 	 * @param propValue		Value which is supposed to contain the class name between brackets (see {@link #isClassName(String)} for more details)
 	 * @param propName		Name of the property associated with the parameter "propValue".
 	 * @param expectedType	Type of the class expected to be returned ; it is also the type which parameterizes this function: C.
-	 * 
+	 *
 	 * @return	The corresponding instance.
-	 * 
+	 *
 	 * @throws UWSException	If the class name is incorrect
 	 *                     	or if its type is not compatible with the parameterized type C (represented by the parameter "expectedType")
 	 *                     	or if the specified class has no empty constructor
 	 *                     	or if an error occurred while calling this constructor.
-	 * 
+	 *
 	 * @see #isClassName(String)
 	 * @see #fetchClass(String, String, Class)
 	 */
-	public final static < C > C newInstance(final String propValue, final String propName, final Class<C> expectedType) throws UWSException{
+	public final static <C> C newInstance(final String propValue, final String propName, final Class<C> expectedType) throws UWSException{
 		return newInstance(propValue, propName, expectedType, null, null);
 	}
 
 	/**
 	 * <p>Create an instance of the specified class. The class name is expected to be surrounded by {} in the given value.</p>
-	 * 
+	 *
 	 * <p><b>IMPORTANT:</b>
 	 * 	The instance is created using the constructor whose the declaration matches exactly with the given list of parameter types.
 	 * 	The number and types of given parameters MUST match exactly to the list of parameter types.
 	 * </p>
-	 * 
+	 *
 	 * @param propValue		Value which is supposed to contain the class name between brackets (see {@link #isClassName(String)} for more details)
 	 * @param propName		Name of the property associated with the parameter "propValue".
 	 * @param expectedType	Type of the class expected to be returned ; it is also the type which parameterizes this function: C.
 	 * @param pTypes		List of each constructor parameter type. Each type MUST be exactly the type declared in the class constructor to select. <i>NULL or empty array if no parameter.</i>
 	 * @param parameters	List of all constructor parameters. The number of object MUST match exactly the number of classes provided in the parameter pTypes. <i>NULL or empty array if no parameter.</i>
-	 * 
+	 *
 	 * @return	The corresponding instance.
-	 * 
+	 *
 	 * @throws UWSException	If the class name is incorrect
 	 *                     	or if its type is not compatible with the parameterized type C (represented by the parameter "expectedType")
 	 *                     	or if the constructor with the specified parameters can not be found
 	 *                     	or if an error occurred while calling this constructor.
-	 * 
+	 *
 	 * @see #isClassName(String)
 	 * @see #fetchClass(String, String, Class)
 	 */
-	public final static < C > C newInstance(final String propValue, final String propName, final Class<C> expectedType, final Class<?>[] pTypes, final Object[] parameters) throws UWSException{
+	public final static <C> C newInstance(final String propValue, final String propName, final Class<C> expectedType, final Class<?>[] pTypes, final Object[] parameters) throws UWSException{
 		// Ensure the given name is a class name specification:
 		if (!isClassName(propValue))
 			throw new UWSException("Class name expected for the property \"" + propName + "\" instead of: \"" + propValue + "\"! The specified class must extend/implement " + expectedType.getName() + ".");
@@ -525,13 +539,13 @@ public final class UWSConfiguration {
 	 * 	If the unit is not specified, it is set by default to BYTES.
 	 * </p>
 	 * <p><i>Note: If the value is strictly less than 0 (whatever is the unit), the returned value will be -1.</i></p>
-	 * 
+	 *
 	 * @param value				Property value (must follow the limit syntax: num_val[unit] ; ex: 20kB or 2000 (for 2000 bytes)).
 	 * @param propertyName		Name of the property which specify the limit.
-	 * 
+	 *
 	 * @return	The expressed unit in bytes
 	 *        	or -1, if the given value was incorrect or negative.
-	 * 
+	 *
 	 * @throws UWSException	If the syntax is incorrect or if a not allowed unit has been used.
 	 */
 	public final static long parseLimit(String value, final String propertyName) throws UWSException{
diff --git a/src/uws/config/uws_configuration_file.html b/src/uws/config/uws_configuration_file.html
index 56f7f6c..ab571ce 100644
--- a/src/uws/config/uws_configuration_file.html
+++ b/src/uws/config/uws_configuration_file.html
@@ -431,24 +431,51 @@
 							file 'service.log' inside the root directory of this
 							service (cf property <code>file_root_path</code>).
 						</li>
-						<li><b><em>{...}</em></b>: a custom logger. A class name
-							MUST be provided (between {...}). The specified
-							class must reference an implementation of
-							uws.service.log.UWSLog. This implementation must
-							have at least one constructor with a single
-							parameter of type uws.service.file.UWSFileManager.
-						</li>
+						<li><b>slf4j</b>: wrapper for
+							<a href="https://www.slf4j.org">SLF4J</a>. All log
+							messages will be forwarded to SLF4J. It is up to the
+							implementor to add the suitable JAR files in the
+							Java class-path.
+							<p>
+								Exactly two JAR files are expected by SLF4J to
+								work as expected:
+							</p>
+							<ul>
+								<li>slf4j-api-{version}.jar (the main API)</li>
+								<li>and the slf4j-{binding}-{version}.jar</li>
+							</ul>
+							<p>
+								Depending on the chosen SLF4J binding, you may
+								also add another JAR file (e.g. Log4J, LogBack,
+								...) in the Java class-path.
+							</p>
+							<p>
+								A configuration file might also be needed.
+								There, it will be possible to configure the
+								following loggers:
+							</p>
+							<ul>
+								<li>"uws.service" (general/root purpose log),</li>
+								<li>"uws.service.UWS" (UWS actions),</li>
+								<li>"uws.service.HTTP" (HTTP requests and
+									responses),</li>
+								<li>"uws.service.JOB" (UWS's jobs actions),</li>
+								<li>"uws.service.THREAD" (job's thread actions)</li>
+							</ul>
 					</ul>
 	 				<p><em>Default: <code>default</code> (i.e. uws.service.log.DefaultUWSLog)</em></p>
 				</td>
-				<td><ul><li>default</li><li>{aPackage.MyLogger}</li></ul></td>
+				<td><ul><li>default</li><li>slf4j</li><li>{aPackage.MyLogger}</li></ul></td>
 			</tr>
 			<tr class="optional">
 				<td class="todo">min_log_level</td>
 				<td></td>
 				<td>text</td>
 				<td>
-					<p>Minimum level that a message must have in order to be logged.</p>
+					<p>
+						Minimum level that a message must have in order to be
+						logged by the default logger.
+					</p>
 					<p>5 possible values:</p>
 	 				<ul>
 	 					<li><b>DEBUG</b>: every messages are logged.</li>
@@ -457,6 +484,10 @@
 	 					<li><b>ERROR</b>: only ERROR and FATAL messages are logged.</li>
 	 					<li><b>FATAL</b>: only FATAL messages are logged.</li>
 	 				</ul>
+					<p><em><b>Note:</b>
+						This property is ignored if
+						<code>logger != default</code>.
+					</em></p>
 	 				<p><em>Default: <code>DEBUG</code> (every messages are logged)</em></p>
 				</td>
 				<td><ul><li>DEBUG</li><li>INFO</li><li>WANRING</li><li>ERROR</li><li>FATAL</li></ul></td>
@@ -466,8 +497,13 @@
 				<td></td>
 				<td>text</td>
 				<td>
-					<p>Frequency of the log file rotation. That's to say, logs will be written in a new file after this period. This avoid having too big log files.
-					Old log files are renamed so that highlighting its logging period.</p>
+					<p>
+						Frequency of the log file rotation performed by the
+						default logger. That's to say, logs will be written in a
+						new file after this period. This avoid having too big
+						log files. Old log files are renamed so that
+						highlighting its logging period.
+					</p>
 					<p>The frequency string must respect the following syntax:</p>
 	 				<ul>
 	 					<li><b>'D' hh mm</b>: daily schedule at hh:mm</li>
@@ -482,6 +518,10 @@
 	 					The frequency type is case sensitive! Then you should particularly pay attention at the case
 	 					when using the frequency types 'M' (monthly) and 'm' (every minute).
 	 				</em></p>
+					<p><em><b>Note:</b>
+						This property is ignored if
+						<code>logger != default</code>.
+					</em></p>
 	 				<p><em>Default: <code>D 0 0</code> (daily at midnight)</em></p>
 				</td>
 				<td><ul><li>D 6 30</li><li>W 2 6 30</li><li>M 2 6 30</li><li>h 10</li><li>m</li></ul></td>
diff --git a/src/uws/config/uws_full.properties b/src/uws/config/uws_full.properties
index db107e7..dcf7a81 100644
--- a/src/uws/config/uws_full.properties
+++ b/src/uws/config/uws_full.properties
@@ -1,9 +1,9 @@
 ##########################################################
 #             FULL UWS CONFIGURATION FILE                #
 #                                                        #
-# UWS Version: 4.2                                       #
-# Date: 13 June 2016                                     #
-# Author: Gregory Mantelet (ARI)                         #
+# UWS Version: 4.3                                       #
+# Date: 31 July 2018                                     #
+# Author: Gregory Mantelet (CDS;ARI)                     #
 #                                                        #
 ########################################################## 
 
@@ -330,10 +330,28 @@ group_user_dir = false
 # [OPTIONAL]
 # Logging method to use.
 # 
-# Only two possibilities are already implemented.
+# Only three possibilities are already implemented.
+#
 #     * default: default logger provided by the library. Any logged message
 #                will be appended in the file 'service.log' inside the root
 #                directory of this service (cf property 'file_root_path').
+#
+#     * slf4j: wrapper for SLF4J (https://www.slf4j.org). All log messages will
+#              be forwarded to SLF4J. It is up to the implementor to add the
+#              suitable JAR files in the Java class-path. Exactly two JAR files
+#              are expected by SLF4J to work as expected:
+#                  - slf4j-api-{version}.jar (the main API)
+#                  - and the slf4j-{binding}-{version}.jar.
+#              Depending on the chosen SLF4J binding, you may also add another
+#              JAR file (e.g. Log4J, LogBack, ...) in the Java class-path.
+#              A configuration file might also be needed. There, it will be
+#              possible to configure the  the following loggers:
+#                  - "uws.service" (general/root purpose log),
+#                  - "uws.service.UWS" (UWS actions),
+#                  - "uws.service.HTTP" (HTTP requests and responses),
+#                  - "uws.service.JOB" (UWS's jobs actions),
+#                  - "uws.service.THREAD" (job's thread actions).
+#
 #     * {...}: a custom logger. A class name must be provided
 #              (between {...}). The specified class must reference
 #              an implementation of uws.service.log.UWSLog. This implementation
@@ -344,7 +362,8 @@ group_user_dir = false
 logger = 
 
 # [OPTIONAL]
-# Minimum level that a message must have in order to be logged.
+# Minimum level that a message must have in order to be logged by the default
+# logger.
 #
 # 5 possible values:
 #     * DEBUG: every messages are logged.
@@ -353,13 +372,16 @@ logger =
 #     * ERROR: only ERROR and FATAL messages are logged.
 #     * FATAL: only FATAL messages are logged.
 #
+# Note: this property is ignored if `logger != default`.
+#
 # Default: DEBUG (every messages are logged)
 min_log_level = 
 
 # [OPTIONAL]
-# Frequency of the log file rotation. That's to say, logs will be written in a
-# new file after this period. This avoid having too big log files.
-# Old log files are renamed so that highlighting its logging period.
+# Frequency of the log file rotation performed by the default logger.
+# That's to say, logs will be written in a new file after this period. This
+# avoid having too big log files. Old log files are renamed so that highlighting
+# its logging period.
 # 
 # The frequency string must respect the following syntax:
 #     'D' hh mm: daily schedule at hh:mm
@@ -373,14 +395,13 @@ min_log_level =
 #        dd (for 'M') = integer between 1 and 31.
 # 
 # Warning: The frequency type is case sensitive! Then you should particularly
-#          pay attention at the case when using the frequency types 'M'
-#          (monthly) and 'm' (every minute).
+#            pay attention at the case when using the frequency types 'M'
+#            (monthly) and 'm' (every minute).
 # 
 # Note 1: this property is ignored if the file manager is not any more an
 #         extension of uws.service.file.LocalUWSFileManager.
-# 
-# Note 2: this property may be ignored if the specified logger is not the
-#         default one (cf property 'logger').
+#
+# Note 2: this property is ignored if `logger != default`.
 # 
 # Default: D 0 0 (daily at midnight)
 log_rotation = 
diff --git a/src/uws/config/uws_min.properties b/src/uws/config/uws_min.properties
index 81658b5..b65e28b 100644
--- a/src/uws/config/uws_min.properties
+++ b/src/uws/config/uws_min.properties
@@ -1,9 +1,9 @@
 ##########################################################
 #            MINIMUM UWS CONFIGURATION FILE              #
 #                                                        #
-# UWS Version: 4.2                                       #
-# Date: 13 June 2016                                     #
-# Author: Gregory Mantelet (ARI)                         #
+# UWS Version: 4.3                                       #
+# Date: 30 July 2018                                     #
+# Author: Gregory Mantelet (CDS;ARI)                     #
 #                                                        #
 ##########################################################
 
diff --git a/src/uws/service/log/DefaultUWSLog.java b/src/uws/service/log/DefaultUWSLog.java
index d6fe0b5..4498839 100644
--- a/src/uws/service/log/DefaultUWSLog.java
+++ b/src/uws/service/log/DefaultUWSLog.java
@@ -2,20 +2,20 @@ package uws.service.log;
 
 /*
  * This file is part of UWSLibrary.
- * 
+ *
  * UWSLibrary is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Lesser General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
- * 
+ *
  * UWSLibrary is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU Lesser General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU Lesser General Public License
  * along with UWSLibrary.  If not, see <http://www.gnu.org/licenses/>.
- * 
+ *
  * Copyright 2012-2016 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
  *                       Astronomisches Rechen Institut (ARI)
  */
@@ -37,11 +37,12 @@ import uws.UWSToolBox;
 import uws.job.UWSJob;
 import uws.job.user.JobOwner;
 import uws.service.UWS;
+import uws.service.file.LocalUWSFileManager;
 import uws.service.file.UWSFileManager;
 
 /**
  * <p>Default implementation of {@link UWSLog} interface which lets logging any message about a UWS.</p>
- * 
+ *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
  * @version 4.2 (07/2016)
  */
@@ -69,11 +70,11 @@ public class DefaultUWSLog implements UWSLog {
 	/**
 	 * <p>Builds a {@link UWSLog} which will use the file manager
 	 * of the given UWS to get the log output (see {@link UWSFileManager#getLogOutput(uws.service.log.UWSLog.LogLevel, String)}).</p>
-	 * 
+	 *
 	 * <p><i><u>note 1</u>: This constructor is particularly useful if the file manager of the given UWS may change.</i></p>
 	 * <p><i><u>note 2</u>: If no output can be found in the file manager (or if there is no file manager),
 	 * the standard error output ({@link System#err}) will be chosen automatically for all log messages.</i></p>
-	 * 
+	 *
 	 * @param uws	A UWS.
 	 */
 	public DefaultUWSLog(final UWS uws){
@@ -85,13 +86,13 @@ public class DefaultUWSLog implements UWSLog {
 	/**
 	 * <p>Builds a {@link UWSLog} which will use the given file
 	 * manager to get the log output (see {@link UWSFileManager#getLogOutput(uws.service.log.UWSLog.LogLevel, String)}).</p>
-	 * 
+	 *
 	 * <p><i><u>note 1</u>: This constructor is particularly useful if the way of managing log output may change in the given file manager.
 	 * Indeed, the output may change in function of the type of message to log ({@link uws.service.log.UWSLog.LogLevel}).</i></p>
-	 * 
+	 *
 	 * <p><i><u>note 2</u> If no output can be found in the file manager the standard error output ({@link System#err})
 	 * will be chosen automatically for all log messages.</i></p>
-	 * 
+	 *
 	 * @param fm	A UWS file manager.
 	 */
 	public DefaultUWSLog(final UWSFileManager fm){
@@ -103,9 +104,9 @@ public class DefaultUWSLog implements UWSLog {
 	/**
 	 * <p>Builds a {@link UWSLog} which will print all its
 	 * messages into the given stream.</p>
-	 * 
+	 *
 	 * <p><i><u>note</u>: the given output will be used whatever is the type of message to log ({@link uws.service.log.UWSLog.LogLevel}).</i></p>
-	 * 
+	 *
 	 * @param output	An output stream.
 	 */
 	public DefaultUWSLog(final OutputStream output){
@@ -117,9 +118,9 @@ public class DefaultUWSLog implements UWSLog {
 	/**
 	 * <p>Builds a {@link UWSLog} which will print all its
 	 * messages into the given stream.</p>
-	 * 
+	 *
 	 * <p><i><u>note</u>: the given output will be used whatever is the type of message to log ({@link uws.service.log.UWSLog.LogLevel}).</i></p>
-	 * 
+	 *
 	 * @param writer	A print writer.
 	 */
 	public DefaultUWSLog(final PrintWriter writer){
@@ -128,9 +129,14 @@ public class DefaultUWSLog implements UWSLog {
 		defaultOutput = writer;
 	}
 
+	@Override
+	public String getConfigString(){
+		return "minimum log level: \"" + minLogLevel + (fileManager instanceof LocalUWSFileManager ? "\", log rotation: \"" + ((LocalUWSFileManager)fileManager).getLogRotationFreq() : "") + "\"";
+	}
+
 	/**
 	 * <p>Get the minimum level that a message must have in order to be logged.</p>
-	 * 
+	 *
 	 * <p>The default behavior is the following:</p>
 	 * <ul>
 	 * 	<li><b>DEBUG</b>: every messages are logged.</li>
@@ -139,9 +145,9 @@ public class DefaultUWSLog implements UWSLog {
 	 * 	<li><b>ERROR</b>: only ERROR and FATAL messages are logged.</li>
 	 * 	<li><b>FATAL</b>: only FATAL messages are logged.</li>
 	 * </ul>
-	 * 
+	 *
 	 * @return	The minimum log level.
-	 * 
+	 *
 	 * @since 4.1
 	 */
 	public final LogLevel getMinLogLevel(){
@@ -150,7 +156,7 @@ public class DefaultUWSLog implements UWSLog {
 
 	/**
 	 * <p>Set the minimum level that a message must have in order to be logged.</p>
-	 * 
+	 *
 	 * <p>The default behavior is the following:</p>
 	 * <ul>
 	 * 	<li><b>DEBUG</b>: every messages are logged.</li>
@@ -159,13 +165,13 @@ public class DefaultUWSLog implements UWSLog {
 	 * 	<li><b>ERROR</b>: only ERROR and FATAL messages are logged.</li>
 	 * 	<li><b>FATAL</b>: only FATAL messages are logged.</li>
 	 * </ul>
-	 * 
+	 *
 	 * <p><i>Note:
 	 * 	If the given level is NULL, this function has no effect.
 	 * </i></p>
-	 * 
+	 *
 	 * @param newMinLevel	The new minimum log level.
-	 * 
+	 *
 	 * @since 4.1
 	 */
 	public final void setMinLogLevel(final LogLevel newMinLevel){
@@ -192,13 +198,13 @@ public class DefaultUWSLog implements UWSLog {
 
 	/**
 	 * <p>Gets an output for the given type of message to print.</p>
-	 * 
+	 *
 	 * <p>The {@link System#err} output is used if none can be found in the {@link UWS} or the {@link UWSFileManager}
 	 * given at the creation, or if the given output stream or writer is NULL.</p>
-	 * 
+	 *
 	 * @param level		Level of the message to print (DEBUG, INFO, WARNING, ERROR or FATAL).
 	 * @param context	Context of the message to print (UWS, HTTP, JOB, THREAD).
-	 * 
+	 *
 	 * @return			A writer.
 	 */
 	protected PrintWriter getOutput(final LogLevel level, final String context){
@@ -222,16 +228,16 @@ public class DefaultUWSLog implements UWSLog {
 
 	/**
 	 * <p>Normalize a log message.</p>
-	 * 
+	 *
 	 * <p>
 	 * 	Since a log entry will a tab-separated concatenation of information, additional tabulations or new-lines
 	 * 	would corrupt a log entry. This function replaces such characters by one space. Only \r are definitely deleted.
 	 * </p>
-	 * 
+	 *
 	 * @param message	Log message to normalize.
-	 * 
+	 *
 	 * @return	The normalized log message.
-	 * 
+	 *
 	 * @since 4.1
 	 */
 	protected String normalizeMessage(final String message){
@@ -243,7 +249,7 @@ public class DefaultUWSLog implements UWSLog {
 
 	/**
 	 * <p>Tells whether a message with the given error level can be logged or not.</p>
-	 * 
+	 *
 	 * <p>In function of the minimum log level of this class, the default behavior is the following:</p>
 	 * <ul>
 	 * 	<li><b>DEBUG</b>: every messages are logged.</li>
@@ -252,11 +258,11 @@ public class DefaultUWSLog implements UWSLog {
 	 * 	<li><b>ERROR</b>: only ERROR and FATAL messages are logged.</li>
 	 * 	<li><b>FATAL</b>: only FATAL messages are logged.</li>
 	 * </ul>
-	 * 
+	 *
 	 * @param msgLevel	Level of the message which has been asked to log. <i>Note: if NULL, it will be considered as DEBUG.</i>
-	 * 
+	 *
 	 * @return	<i>true</i> if the message associated with the given log level can be logged, <i>false</i> otherwise.
-	 * 
+	 *
 	 * @since 4.1
 	 */
 	protected boolean canLog(LogLevel msgLevel){
@@ -287,11 +293,11 @@ public class DefaultUWSLog implements UWSLog {
 
 	/**
 	 * <p>Logs a full message and/or error.</p>
-	 * 
+	 *
 	 * <p><i>Note:
 	 * 	If no message and error is provided, nothing will be written.
 	 * </i></p>
-	 * 
+	 *
 	 * @param level		Level of the error (DEBUG, INFO, WARNING, ERROR, FATAL).	<i>SHOULD NOT be NULL</i>
 	 * @param context	Context of the error (UWS, HTTP, THREAD, JOB). <i>MAY be NULL</i>
 	 * @param event		Context event during which this log is emitted. <i>MAY be NULL</i>
@@ -299,7 +305,7 @@ public class DefaultUWSLog implements UWSLog {
 	 * @param message	Message of the error. <i>MAY be NULL</i>
 	 * @param addColumn	Additional column to append after the message and before the stack trace.
 	 * @param error		Error at the origin of the log error/warning/fatal. <i>MAY be NULL</i>
-	 * 
+	 *
 	 * @since 4.1
 	 */
 	protected final void log(LogLevel level, final String context, final String event, final String ID, final String message, final String addColumn, final Throwable error){
@@ -347,27 +353,27 @@ public class DefaultUWSLog implements UWSLog {
 
 	/**
 	 * <p>Format and print the given exception inside the given writer.</p>
-	 * 
+	 *
 	 * <p>This function does nothing if the given error is NULL.</p>
-	 * 
+	 *
 	 * <p>The full stack trace is printed ONLY for unknown exceptions.</p>
-	 * 
+	 *
 	 * <p>The printed text has the following format for known exceptions:</p>
 	 * <pre>
 	 * Caused by a {ExceptionClassName} {ExceptionOrigin}
 	 *     {ExceptionMessage}
 	 * </pre>
-	 * 
+	 *
 	 * <p>The printed text has the following format for unknown exceptions:</p>
 	 * <pre>
 	 * Caused by a {ExceptionFullStackTrace}
 	 * </pre>
-	 * 
+	 *
 	 * @param error	The exception to print.
 	 * @param out	The output in which the exception must be written.
-	 * 
+	 *
 	 * @see #getExceptionOrigin(Throwable)
-	 * 
+	 *
 	 * @since 4.1
 	 */
 	protected void printException(final Throwable error, final PrintWriter out){
@@ -390,20 +396,20 @@ public class DefaultUWSLog implements UWSLog {
 	/**
 	 * <p>Format and return the origin of the given error.
 	 * "Origin" means here: "where the error has been thrown from?" (from which class? method? file? line?).</p>
-	 * 
+	 *
 	 * <p>This function does nothing if the given error is NULL or if the origin information is missing.</p>
-	 * 
+	 *
 	 * <p>The returned text has the following format:</p>
 	 * <pre>
 	 * at {OriginClass}.{OriginMethod}({OriginFile}:{OriginLine})
 	 * </pre>
-	 * 
+	 *
 	 * <p>{OriginFile} and {OriginLine} are written only if provided.</p>
-	 * 
+	 *
 	 * @param error	Error whose the origin should be returned.
-	 * 
+	 *
 	 * @return	A string which contains formatted information about the origin of the given error.
-	 * 
+	 *
 	 * @since 4.1
 	 */
 	protected String getExceptionOrigin(final Throwable error){
@@ -461,7 +467,7 @@ public class DefaultUWSLog implements UWSLog {
 	/**
 	 * <p>A message/error logged with this function will have the following format:</p>
 	 * <pre>&lt;TIMESTAMP&gt;	&lt;LEVEL&gt;	HTTP	REQUEST_RECEIVED	&lt;REQUEST_ID&gt;	&lt;MESSAGE&gt;	&lt;HTTP_METHOD&gt; in &lt;CONTENT_TYPE&gt; at &lt;URL&gt; from &lt;IP_ADDR&gt; using &lt;USER_AGENT&gt; with parameters (&lt;PARAM1&gt;=&lt;VAL1&gt;&...)</pre>
-	 * 
+	 *
 	 * @see uws.service.log.UWSLog#logHttp(uws.service.log.UWSLog.LogLevel, javax.servlet.http.HttpServletRequest, java.lang.String, java.lang.String, java.lang.Throwable)
 	 */
 	@Override
@@ -497,9 +503,9 @@ public class DefaultUWSLog implements UWSLog {
 
 			// Write the posted parameters:
 			str.append(" with parameters (");
-			Map<String,String> params = UWSToolBox.getParamsMap(request);
+			Map<String, String> params = UWSToolBox.getParamsMap(request);
 			int i = -1;
-			for(Entry<String,String> p : params.entrySet()){
+			for(Entry<String, String> p : params.entrySet()){
 				if (++i > 0)
 					str.append('&');
 				str.append(p.getKey()).append('=').append((p.getValue() != null) ? p.getValue() : "");
@@ -518,7 +524,7 @@ public class DefaultUWSLog implements UWSLog {
 	 * <p>A message/error logged with this function will have the following format:</p>
 	 * <pre>&lt;TIMESTAMP&gt;	&lt;LEVEL&gt;	HTTP	RESPONSE_SENT	&lt;REQUEST_ID&gt;	&lt;MESSAGE&gt;	HTTP-&lt;STATUS_CODE&gt; to the user &lt;USER&gt; as &lt;CONTENT_TYPE&gt;</pre>
 	 * <p>,where &lt;USER&gt; may be either "(id:&lt;USER_ID&gt;;pseudo:&lt;USER_PSEUDO&gt;)" or "ANONYMOUS".</p>
-	 * 
+	 *
 	 * @see uws.service.log.UWSLog#logHttp(uws.service.log.UWSLog.LogLevel, javax.servlet.http.HttpServletResponse, java.lang.String, uws.job.user.JobOwner, java.lang.String, java.lang.Throwable)
 	 */
 	@Override
diff --git a/src/uws/service/log/Slf4jUWSLog.java b/src/uws/service/log/Slf4jUWSLog.java
new file mode 100644
index 0000000..a4f8846
--- /dev/null
+++ b/src/uws/service/log/Slf4jUWSLog.java
@@ -0,0 +1,387 @@
+package uws.service.log;
+
+/*
+ * This file is part of UWSLibrary.
+ *
+ * UWSLibrary is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * UWSLibrary is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with UWSLibrary.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright 2018 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ */
+
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import uws.UWSToolBox;
+import uws.job.UWSJob;
+import uws.job.user.JobOwner;
+
+/**
+ * This implementation of {@link UWSLog} forwards all log submission to
+ * <a href="https://www.slf4j.org">SLF4J</a>.
+ *
+ * <p>
+ * 	Thus, a UWS implementor can choose how to deal with logs. SLF4J already
+ * 	provides bridges at least for the following logging frameworks:
+ * </p>
+ * <ul>
+ * 	<li><a href="https://www.slf4j.org/api/org/slf4j/helpers/NOPLogger.html">
+ * 		NOPE</a> (all logs are merely discarded),</li>
+ * 	<li><a href="https://www.slf4j.org/apidocs/org/slf4j/impl/SimpleLogger.html">
+ * 		simple logging mechanism</a> (console, file, ...),</li>
+ * 	<li><a href="https://docs.oracle.com/javase/7/docs/api/java/util/logging/package-summary.html">JUL</a> (java.util.logging),</li>
+ * 	<li><a href="https://logging.apache.org/log4j">Log4J</a>,</li>
+ * 	<li><a href="http://commons.apache.org/proper/commons-logging/">JCL</a>
+ * 	    (Jakarta Commons Logging),</li>
+ * 	<li>and <a href="https://logback.qos.ch/">LogBack</a>.</li>
+ * </ul>
+ *
+ * <b>SLF4J loggers</b>
+ *
+ * <p>
+ * 	A different logger is used in function of the log entry's CONTEXT.
+ * 	Four values are supported: "UWS", "HTTP", "JOB" and "THREAD". If a different
+ * 	value is set for the CONTEXT, the general/root logger will be used.
+ * </p>
+ * <p>The ID of each logger follows this rule:</p>
+ * <pre>{@value #DEFAULT_ROOT_LOG_ID}{SUFFIX}</pre>
+ * <p>
+ * 	The ID of the general/root logger is just
+ * 	<code>{@value #DEFAULT_ROOT_LOG_ID}</code>. For all the other specific
+ * 	loggers, <code>{SUFFIX}</code> is a dot followed by one among
+ * 	{@value #UWS_LOG_ID_SUFFIX}, {@value #HTTP_LOG_ID_SUFFIX},
+ * 	{@value #JOB_LOG_ID_SUFFIX} and {@value #THREAD_LOG_ID_SUFFIX}. Thus, the
+ * 	full ID of the logger for the HTTP requests is:
+ * </p>
+ * <pre>uws.service.http</pre>
+ *
+ * <b>Log entries format</b>
+ *
+ * <p>All log entries are formatted as follow:</p>
+ * <pre>{EVENT} - {ID} - {MESSAGE} - {ADDITIONAL_DATA}</pre>
+ * <p>
+ * 	{EVENT}, {ID} and {ADDITIONAL_DATA} may not be all provided for
+ * 	some messages ; all are optional, and especially ADDITIONAL_DATA. When not
+ * 	provided, each is replaced by an empty string. In the special case of
+ * 	ADDITIONAL_DATA, if none is provided, the " - " prefixing it is not written.
+ * </p>
+ *
+ * @author Gr&eacute;gory Mantelet (CDS)
+ * @version 4.3 (07/2018)
+ * @since 4.3
+ */
+public class Slf4jUWSLog implements UWSLog {
+
+	/** Default SLF4J identifier for the general/root logger to use. */
+	protected static final String DEFAULT_ROOT_LOG_ID = "uws.service";
+
+	/** Suffix to append to the root logger ID for the UWS logger. */
+	protected static final String UWS_LOG_ID_SUFFIX = "UWS";
+
+	/** Suffix to append to the root logger ID for the HTTP logger. */
+	protected static final String HTTP_LOG_ID_SUFFIX = "HTTP";
+
+	/** Suffix to append to the root logger ID for the JOB logger. */
+	protected static final String JOB_LOG_ID_SUFFIX = "JOB";
+
+	/** Suffix to append to the root logger ID for the THREAD logger. */
+	protected static final String THREAD_LOG_ID_SUFFIX = "THREAD";
+
+	/** General purpose logger. */
+	protected final Logger rootLogger;
+
+	/** Logger for general UWS actions. */
+	protected final Logger uwsLogger;
+
+	/** Logger for HTTP requests and responses. */
+	protected final Logger httpLogger;
+
+	/** Logger for UWS jobs actions. */
+	protected final Logger jobLogger;
+
+	/** Logger for UWS jobs' threads actions. */
+	protected final Logger threadLogger;
+
+	/**
+	 * Initialise this logger.
+	 */
+	public Slf4jUWSLog(){
+		this(DEFAULT_ROOT_LOG_ID);
+	}
+
+	/**
+	 * Initialise this logger with the specified SLF4J logger.
+	 */
+	protected Slf4jUWSLog(final String logId){
+		final String rootLogId = (logId == null || logId.trim().length() == 0) ? DEFAULT_ROOT_LOG_ID : logId;
+		rootLogger = LoggerFactory.getLogger(rootLogId);
+		uwsLogger = LoggerFactory.getLogger(rootLogId + "." + UWS_LOG_ID_SUFFIX);
+		httpLogger = LoggerFactory.getLogger(rootLogId + "." + HTTP_LOG_ID_SUFFIX);
+		jobLogger = LoggerFactory.getLogger(rootLogId + "." + JOB_LOG_ID_SUFFIX);
+		threadLogger = LoggerFactory.getLogger(rootLogId + "." + THREAD_LOG_ID_SUFFIX);
+	}
+
+	@Override
+	public String getConfigString(){
+		return "type: \"SLF4J\", loggers: [\"" + rootLogger.getName() + "\", \"" + uwsLogger.getName() + "\", \"" + httpLogger.getName() + "\", \"" + jobLogger.getName() + "\", \"" + threadLogger.getName() + "\"]";
+	}
+
+	/* *********************** */
+	/* GENERAL LOGGING METHODS */
+	/* *********************** */
+
+	@Override
+	public void log(LogLevel level, String context, String message, Throwable error){
+		Logger logger = rootLogger;
+		if (context != null){
+			if ("HTTP".equalsIgnoreCase(context))
+				logger = httpLogger;
+			else if ("UWS".equalsIgnoreCase(context))
+				logger = uwsLogger;
+			else if ("JOB".equalsIgnoreCase(context))
+				logger = jobLogger;
+			else if ("THREAD".equalsIgnoreCase(context))
+				logger = threadLogger;
+		}
+		log(level, logger, null, null, message, null, error);
+	}
+
+	/**
+	 * <p>Logs a full message and/or error.</p>
+	 *
+	 * <p><i>Note:
+	 * 	If no message and error is provided, nothing will be written.
+	 * </i></p>
+	 *
+	 * @param level		Level of the error (DEBUG, INFO, WARNING, ERROR, FATAL).	<i>SHOULD NOT be NULL</i>
+	 * @param logger	Logger to use. <i>MUST NOT be NULL</i>
+	 * @param event		Context event during which this log is emitted. <i>MAY be NULL</i>
+	 * @param ID		ID of the job or HTTP request (it may also be an ID of anything else). <i>MAY BE NULL</i>
+	 * @param message	Message of the error. <i>MAY be NULL</i>
+	 * @param addColumn	Additional column to append after the message and before the stack trace.
+	 * @param error		Error at the origin of the log error/warning/fatal. <i>MAY be NULL</i>
+	 */
+	protected final void log(LogLevel level, Logger logger, final String event, final String ID, final String message, final String addColumn, final Throwable error){
+		// If no message and no error is provided, nothing to log:
+		if ((message == null || message.length() <= 0) && error == null)
+			return;
+
+		// Set a default log level, if needed:
+		if (level == null)
+			level = (error != null) ? LogLevel.ERROR : LogLevel.INFO;
+
+		// Set the root logger if not is set:
+		if (logger == null)
+			logger = rootLogger;
+
+		// Call the appropriate function depending on the log level:
+		switch(level){
+			case DEBUG:
+				logger.debug("{} - {} - {}{}", (event == null ? "" : event), (ID == null ? "" : ID), message, (addColumn == null ? "" : " - " + addColumn), error);
+				break;
+			case INFO:
+				logger.info("{} - {} - {}{}", (event == null ? "" : event), (ID == null ? "" : ID), message, (addColumn == null ? "" : " - " + addColumn), error);
+				break;
+			case WARNING:
+				logger.warn("{} - {} - {}{}", (event == null ? "" : event), (ID == null ? "" : ID), message, (addColumn == null ? "" : " - " + addColumn), error);
+				break;
+			case ERROR:
+				logger.error("{} - {} - {}{}", (event == null ? "" : event), (ID == null ? "" : ID), message, (addColumn == null ? "" : " - " + addColumn), error);
+				break;
+			case FATAL:
+				logger.error("{} - {} - GRAVE: {}{}", (event == null ? "" : event), (ID == null ? "" : ID), message, (addColumn == null ? "" : " - " + addColumn), error);
+				break;
+		}
+	}
+
+	@Override
+	public void debug(final String msg){
+		log(LogLevel.DEBUG, rootLogger, null, null, msg, null, null);
+	}
+
+	@Override
+	public void debug(final Throwable t){
+		log(LogLevel.DEBUG, rootLogger, null, null, "Debugging stack trace:", null, t);
+	}
+
+	@Override
+	public void debug(final String msg, final Throwable t){
+		log(LogLevel.DEBUG, rootLogger, null, null, msg, null, t);
+	}
+
+	@Override
+	public void info(final String msg){
+		log(LogLevel.INFO, rootLogger, null, null, msg, null, null);
+	}
+
+	@Override
+	public void warning(final String msg){
+		log(LogLevel.WARNING, rootLogger, null, null, msg, null, null);
+	}
+
+	@Override
+	public void error(final String msg){
+		log(LogLevel.ERROR, rootLogger, null, null, msg, null, null);
+	}
+
+	@Override
+	public void error(final Throwable t){
+		log(LogLevel.ERROR, rootLogger, null, null, "Unexpected error:", null, t);
+	}
+
+	@Override
+	public void error(final String msg, final Throwable t){
+		log(LogLevel.ERROR, rootLogger, null, null, msg, null, t);
+	}
+
+	/* ************* */
+	/* HTTP ACTIVITY */
+	/* ************* */
+
+	@Override
+	public void logHttp(final LogLevel level, final HttpServletRequest request, final String requestId, final String message, final Throwable error){
+		// IF A REQUEST IS PROVIDED, write its details after the message in a new column:
+		if (request != null){
+			StringBuffer str = new StringBuffer((message != null) ? message + ": " : "");
+
+			// Write the request type, content type and the URL:
+			str.append(request.getMethod());
+			str.append(" as ");
+			if (request.getContentType() != null){
+				if (request.getContentType().indexOf(';') > 0)
+					str.append(request.getContentType().substring(0, request.getContentType().indexOf(';')));
+				else
+					str.append(request.getContentType());
+			}
+			str.append(" at ").append(request.getRequestURL());
+
+			// Write the IP address:
+			str.append(" from ").append(request.getRemoteAddr());
+
+			// Write the user agent:
+			str.append(" using ").append(request.getHeader("User-Agent") == null ? "" : request.getHeader("User-Agent"));
+
+			// Write the posted parameters:
+			str.append(" with parameters (");
+			Map<String, String> params = UWSToolBox.getParamsMap(request);
+			int i = -1;
+			for(Entry<String, String> p : params.entrySet()){
+				if (++i > 0)
+					str.append('&');
+				str.append(p.getKey()).append('=').append((p.getValue() != null) ? p.getValue() : "");
+			}
+			str.append(')');
+
+			// Send the log message to the log file:
+			if (message == null)
+				log(level, httpLogger, "REQUEST_RECEIVED", requestId, str.toString(), null, error);
+			else
+				log(level, httpLogger, "REQUEST_RECEIVED", requestId, str.toString(), null, error);
+		}
+		// OTHERWISE, just write the given message:
+		else
+			log(level, httpLogger, "REQUEST_RECEIVED", requestId, message, null, error);
+	}
+
+	@Override
+	public void logHttp(final LogLevel level, final HttpServletResponse response, final String requestId, final JobOwner user, final String message, final Throwable error){
+		if (response != null){
+			StringBuffer str = new StringBuffer();
+
+			// Write the response status code:
+			str.append("HTTP-").append(response.getStatus());
+
+			// Write the user to whom the response is sent:
+			str.append(" to the user ");
+			if (user != null){
+				str.append("(id:").append(user.getID());
+				if (user.getPseudo() != null)
+					str.append(";pseudo:").append(user.getPseudo());
+				str.append(')');
+			}else
+				str.append("ANONYMOUS");
+
+			// Write the response's MIME type:
+			if (response.getContentType() != null)
+				str.append(" as ").append(response.getContentType());
+
+			// Send the log message to the log file:
+			log(level, httpLogger, "RESPONSE_SENT", requestId, message, str.toString(), error);
+		}
+		// OTHERWISE, just write the given message:
+		else
+			log(level, httpLogger, "RESPONSE_SENT", requestId, message, null, error);
+	}
+
+	/* ************ */
+	/* UWS ACTIVITY */
+	/* ************ */
+
+	@Override
+	public void logUWS(final LogLevel level, final Object obj, final String event, final String message, final Throwable error){
+		// CASE "BACKUPED": Append to the message the backup report:
+		String report = null;
+		if (event != null && event.equalsIgnoreCase("BACKUPED") && obj != null && obj.getClass().getName().equals("[I")){
+			int[] backupReport = (int[])obj;
+			if (backupReport.length == 2)
+				report = "(" + backupReport[0] + "/" + backupReport[1] + " jobs backuped for this user)";
+			else
+				report = "(" + backupReport[0] + "/" + backupReport[1] + " jobs backuped ; " + backupReport[2] + "/" + backupReport[3] + " users backuped)";
+		}else if (event != null && event.equalsIgnoreCase("RESTORED") && obj != null && obj.getClass().getName().equals("[I")){
+			int[] restoreReport = (int[])obj;
+			report = "(" + restoreReport[0] + "/" + restoreReport[1] + " jobs restored ; " + restoreReport[2] + "/" + restoreReport[3] + " users restored)";
+		}
+
+		// Log the message
+		log(level, uwsLogger, event, null, message, report, error);
+	}
+
+	/* ************ */
+	/* JOB ACTIVITY */
+	/* ************ */
+
+	@Override
+	public void logJob(final LogLevel level, final UWSJob job, final String event, final String message, final Throwable error){
+		log(level, jobLogger, event, (job == null) ? null : job.getJobId(), message, null, error);
+	}
+
+	@Override
+	public void logThread(final LogLevel level, final Thread thread, final String event, final String message, final Throwable error){
+		if (thread != null){
+			StringBuffer str = new StringBuffer();
+
+			// Write the thread name and ID:
+			str.append(thread.getName()).append(" (thread ID: ").append(thread.getId()).append(")");
+
+			// Write the thread state:
+			str.append(" is ").append(thread.getState());
+
+			// Write its thread group name:
+			str.append(" in the group " + thread.getThreadGroup().getName());
+
+			// Write the number of active threads:
+			str.append(" where ").append(thread.getThreadGroup().activeCount()).append(" threads are active");
+
+			log(level, threadLogger, event, thread.getName(), message, str.toString(), error);
+
+		}else
+			log(level, threadLogger, event, null, message, null, error);
+	}
+
+}
diff --git a/src/uws/service/log/UWSLog.java b/src/uws/service/log/UWSLog.java
index afee118..0577409 100644
--- a/src/uws/service/log/UWSLog.java
+++ b/src/uws/service/log/UWSLog.java
@@ -16,7 +16,7 @@ package uws.service.log;
  * You should have received a copy of the GNU Lesser General Public License
  * along with UWSLibrary.  If not, see <http://www.gnu.org/licenses/>.
  *
- * Copyright 2012-2017 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ * Copyright 2012-2018 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
  *                       Astronomisches Rechen Institut (ARI)
  */
 
@@ -38,7 +38,7 @@ import uws.service.UWSUrl;
  * Let log any kind of message about a UWS service.
  *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 4.3 (09/2017)
+ * @version 4.3 (07/2018)
  */
 public interface UWSLog {
 
@@ -49,10 +49,24 @@ public interface UWSLog {
 	 * @version 4.1 (09/2014)
 	 * @since 4.1
 	 */
-	public static enum LogLevel{
+	public static enum LogLevel {
 		DEBUG, INFO, WARNING, ERROR, FATAL;
 	}
 
+	/**
+	 * Get a string representing the configuration of this logger.
+	 *
+	 * <p>
+	 * 	The result of this function aims to be logged when the logging
+	 * 	mechanism is successfully configured and ready to be used.
+	 * </p>
+	 *
+	 * @return	String representing the configuration of this logger.
+	 *
+	 * @since 4.3.
+	 */
+	public String getConfigString();
+
 	/* *********************** */
 	/* GENERAL LOGGING METHODS */
 	/* *********************** */
-- 
GitLab