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é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égory Mantelet (ARI) - * @version 2.1 (09/2017) + * @author Gré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é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égory Mantelet (ARI) - * @version 4.2 (09/2017) + * + * @author Gré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égory Mantelet (ARI) - * @version 4.2 (09/2017) - * @since 4.2 + * + * @author Gré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é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><TIMESTAMP> <LEVEL> HTTP REQUEST_RECEIVED <REQUEST_ID> <MESSAGE> <HTTP_METHOD> in <CONTENT_TYPE> at <URL> from <IP_ADDR> using <USER_AGENT> with parameters (<PARAM1>=<VAL1>&...)</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><TIMESTAMP> <LEVEL> HTTP RESPONSE_SENT <REQUEST_ID> <MESSAGE> HTTP-<STATUS_CODE> to the user <USER> as <CONTENT_TYPE></pre> * <p>,where <USER> may be either "(id:<USER_ID>;pseudo:<USER_PSEUDO>)" 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é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é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