ongoing work on the slf4j-jdk-platform-logging module

Signed-off-by: Ceki Gulcu <ceki@qos.ch>
This commit is contained in:
Ceki Gulcu 2021-08-11 17:09:18 +02:00
parent e0448ad699
commit a42ad1f4f7
10 changed files with 258 additions and 245 deletions

View File

@ -22,7 +22,7 @@
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
module org.slf4j.jdk {
module org.slf4j.jdk.platform.logging {
requires org.slf4j;
provides java.lang.System.LoggerFinder with org.slf4j.jdk.SLF4JSystemLoggerFinder;
}
provides java.lang.System.LoggerFinder with org.slf4j.jdk.platform.logging.SLF4JSystemLoggerFinder;
}

View File

@ -1,135 +0,0 @@
package org.slf4j.jdk;
import static java.util.Objects.requireNonNull;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import org.slf4j.Logger;
/**
* Adapts {@link Logger} to {@link System.Logger}.
*/
class SLF4JSystemLogger implements System.Logger {
private final Logger slf4jLogger;
public SLF4JSystemLogger(Logger logger) {
this.slf4jLogger = requireNonNull(logger);
}
@Override
public String getName() {
return slf4jLogger.getName();
}
@Override
public boolean isLoggable(Level jplLevel) {
switch (jplLevel) {
case ALL:
return true;
case TRACE:
return slf4jLogger.isTraceEnabled();
case DEBUG:
return slf4jLogger.isDebugEnabled();
case INFO:
return slf4jLogger.isInfoEnabled();
case WARNING:
return slf4jLogger.isWarnEnabled();
case ERROR:
return slf4jLogger.isErrorEnabled();
case OFF:
return false;
default:
reportUnknownLevel(jplLevel);
return true;
}
}
@Override
public void log(Level jplLevel, ResourceBundle bundle, String msg, Throwable thrown) {
String message = getResourceStringOrMessage(bundle, msg);
switch (jplLevel) {
case ALL:
// fall-through intended because a message is visible on all log levels
// if it is logged on the lowest level
case TRACE:
slf4jLogger.trace(message, thrown);
break;
case DEBUG:
slf4jLogger.debug(message, thrown);
break;
case INFO:
slf4jLogger.info(message, thrown);
break;
case WARNING:
slf4jLogger.warn(message, thrown);
break;
case ERROR:
slf4jLogger.error(message, thrown);
break;
case OFF:
// don't do anything for a message on level `OFF`
break;
default:
reportUnknownLevel(jplLevel);
}
}
@Override
public void log(Level jplLevel, ResourceBundle bundle, String format, Object... params) {
String message = getResourceStringOrMessage(bundle, format);
switch (jplLevel) {
case ALL:
// fall-through intended because a message is visible on all log levels
// if it is logged on the lowest level
case TRACE:
slf4jLogger.trace(message, params);
break;
case DEBUG:
slf4jLogger.debug(message, params);
break;
case INFO:
slf4jLogger.info(message, params);
break;
case WARNING:
slf4jLogger.warn(message, params);
break;
case ERROR:
slf4jLogger.error(message, params);
break;
case OFF:
// don't do anything for a message on level `OFF`
break;
default:
reportUnknownLevel(jplLevel);
}
}
private void reportUnknownLevel(Level jplLevel) {
String message = "Unknown log level [" + jplLevel + "]";
IllegalArgumentException iae = new IllegalArgumentException(message);
org.slf4j.helpers.Util.report("Unsupported log level", iae);
}
private static String getResourceStringOrMessage(ResourceBundle bundle, String msg) {
if (bundle == null || msg == null)
return msg;
// ResourceBundle::getString throws:
//
// * NullPointerException for null keys
// * ClassCastException if the message is no string
// * MissingResourceException if there is no message for the key
//
// Handle all of these cases here to avoid log-related exceptions from crashing the JVM.
try {
return bundle.getString(msg);
} catch (MissingResourceException ex) {
return msg;
} catch (ClassCastException ex) {
return bundle.getObject(msg).toString();
}
}
}

View File

@ -0,0 +1,172 @@
/**
* Copyright (c) 2004-2021 QOS.ch
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package org.slf4j.jdk.platform.logging;
import static java.util.Objects.requireNonNull;
import java.text.MessageFormat;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import org.slf4j.Logger;
import org.slf4j.spi.LoggingEventBuilder;
/**
* Adapts {@link Logger} to {@link System.Logger}.
*/
class SLF4JPlarformLogger implements System.Logger {
private final Logger slf4jLogger;
public SLF4JPlarformLogger(Logger logger) {
this.slf4jLogger = requireNonNull(logger);
}
@Override
public String getName() {
return slf4jLogger.getName();
}
// The fact that non loggable levels (in java.lang.System.Logger.Level)
// such as ALL and OFF leak into the public interface is quite a pity.
@Override
public boolean isLoggable(Level jplLevel) {
if (jplLevel == Level.ALL)
return true;
if (jplLevel == Level.OFF)
return true;
org.slf4j.event.Level slf4jLevel = jplLevelToSLF4JLevel(jplLevel);
return slf4jLogger.isEnabledForLevel(slf4jLevel);
}
/**
* Transform a {@link Level} to {@link org.slf4j.event.Level}.
*
* This method assumes that Level.ALL or Level.OFF never reach this method.
*
* @param jplLevel
* @return
*/
private org.slf4j.event.Level jplLevelToSLF4JLevel(Level jplLevel) {
switch (jplLevel) {
case TRACE:
return org.slf4j.event.Level.TRACE;
case DEBUG:
return org.slf4j.event.Level.DEBUG;
case INFO:
return org.slf4j.event.Level.INFO;
case WARNING:
return org.slf4j.event.Level.WARN;
case ERROR:
return org.slf4j.event.Level.ERROR;
default:
reportUnknownLevel(jplLevel);
return null;
}
}
@Override
public void log(Level jplLevel, ResourceBundle bundle, String msg, Throwable thrown) {
log(jplLevel, bundle, msg, thrown, (Object[]) null);
}
@Override
public void log(Level jplLevel, ResourceBundle bundle, String format, Object... params) {
log(jplLevel, bundle, format, null, params);
}
/**
* Single point of processing taking all possible paramets.
*
* @param jplLevel
* @param bundle
* @param msg
* @param thrown
* @param params
*/
private void log(Level jplLevel, ResourceBundle bundle, String msg, Throwable thrown, Object... params) {
if (jplLevel == Level.OFF)
return;
if (jplLevel == Level.ALL) {
performLog(org.slf4j.event.Level.TRACE, bundle, msg, thrown, params);
return;
}
org.slf4j.event.Level slf4jLevel = jplLevelToSLF4JLevel(jplLevel);
boolean isEnabled = slf4jLogger.isEnabledForLevel(slf4jLevel);
if (isEnabled) {
performLog(slf4jLevel, bundle, msg, thrown, params);
}
}
private void performLog(org.slf4j.event.Level slf4jLevel, ResourceBundle bundle, String msg, Throwable thrown, Object... params) {
String message = getResourceStringOrMessage(bundle, msg);
LoggingEventBuilder leb = slf4jLogger.makeLoggingEventBuilder(slf4jLevel);
if (thrown != null) {
leb = leb.setCause(thrown);
}
if (params != null && params.length > 0) {
// add the arguments to the logging event for possible processing by the backend
for (Object p : params) {
leb = leb.addArgument(p);
}
// The JDK uses a different formatting convention. We must invoke it now.
message = String.format(message, params);
}
leb.log(message);
}
private void reportUnknownLevel(Level jplLevel) {
String message = "Unknown log level [" + jplLevel + "]";
IllegalArgumentException iae = new IllegalArgumentException(message);
org.slf4j.helpers.Util.report("Unsupported log level", iae);
}
private static String getResourceStringOrMessage(ResourceBundle bundle, String msg) {
if (bundle == null || msg == null)
return msg;
// ResourceBundle::getString throws:
//
// * NullPointerException for null keys
// * ClassCastException if the message is no string
// * MissingResourceException if there is no message for the key
//
// Handle all of these cases here to avoid log-related exceptions from crashing the JVM.
try {
return bundle.getString(msg);
} catch (MissingResourceException ex) {
return msg;
} catch (ClassCastException ex) {
return bundle.getObject(msg).toString();
}
}
}

View File

@ -0,0 +1,59 @@
/**
* Copyright (c) 2004-2021 QOS.ch
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package org.slf4j.jdk.platform.logging;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Manages instances of {@link SLF4JPlarformLogger}.
*
* @since 1.3.0
* @author Ceki
*
*/
public class SLF4JPlarformLoggerFactory {
ConcurrentMap<String, SLF4JPlarformLogger> loggerMap = new ConcurrentHashMap<>();
/**
* Return an appropriate {@link SLF4JPlarformLogger} instance by name.
*/
public SLF4JPlarformLogger getLogger(String loggerName) {
SLF4JPlarformLogger spla = loggerMap.get(loggerName);
if (spla != null) {
return spla;
} else {
Logger slf4jLogger = LoggerFactory.getLogger(loggerName);
SLF4JPlarformLogger newInstance = new SLF4JPlarformLogger(slf4jLogger);
SLF4JPlarformLogger oldInstance = loggerMap.putIfAbsent(loggerName, newInstance);
return oldInstance == null ? newInstance : oldInstance;
}
}
}

View File

@ -22,17 +22,18 @@
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package org.slf4j.jdk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
package org.slf4j.jdk.platform.logging;
/**
* Uses SLF4J's {@link LoggerFactory#getLogger(String)} to get a logger
* Uses {@link SLF4JPlarformLoggerFactory#getLogger(String)} to get a logger
* that is adapted for {@link System.Logger}.
*
* @since 2.0.0
*/
public class SLF4JSystemLoggerFinder extends System.LoggerFinder {
final SLF4JPlarformLoggerFactory platformLoggerFactory = new SLF4JPlarformLoggerFactory();
@Override
public System.Logger getLogger(String name, Module module) {
// JEP 264[1], which introduced the Platform Logging API,
@ -51,8 +52,8 @@ public class SLF4JSystemLoggerFinder extends System.LoggerFinder {
// is updated to forward a module, we should do that here.
//
// [1] https://openjdk.java.net/jeps/264
Logger slf4JLogger = LoggerFactory.getLogger(name);
return new SLF4JSystemLogger(slf4JLogger);
SLF4JPlarformLogger adapter = platformLoggerFactory.getLogger(name);
return adapter;
}
}

View File

@ -1,6 +1,9 @@
Implementation-Title: slf4j-ext
Implementation-Title: slf4j-jdk-platform-logging
Bundle-ManifestVersion: 2
Bundle-SymbolicName: slf4j.ext
Bundle-SymbolicName: slf4j.jdk.platform.logging
Bundle-Name: slf4j-jdk-platform-logging
Bundle-Vendor: SLF4J.ORG
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Require-Bundle: slf4j.api
Bundle-RequiredExecutionEnvironment: JavaSE-9
Export-Package: slf4j.jdk.platform.logging;version=${parsedVersion.osgiVersion}
Import-Package: org.slf4j;version=${parsedVersion.osgiVersion}

View File

@ -1 +1 @@
org.slf4j.jdk.SLF4JSystemLoggerFinder
package org.slf4j.jdk.platform.logging.SLF4JSystemLoggerFinder

View File

@ -1,93 +0,0 @@
package org.slf4j.jdk;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.System.LoggerFinder;
import java.util.Optional;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.ServiceLoader.Provider;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@Ignore
public class SLF4JSystemLoggerTest {
private static final PrintStream ERROR_OUT = System.err;
private static final ByteArrayOutputStream OUTPUT = new ByteArrayOutputStream();
private static final PrintStream ERROR_OUT_REPLACEMENT = new PrintStream(OUTPUT);
@Before
public void setUp() {
System.setErr(ERROR_OUT_REPLACEMENT);
}
@After
public void tearDown() {
System.setErr(ERROR_OUT);
}
@Test
public void loggerFinderLoadedAsService() {
// this method asserts that a `LoggerFinder` of the correct type was loaded
getSLF4JSystemLoggerFinder();
}
@Test
public void loggerFinderReturnsLogger() {
System.Logger logger = getSLF4JSystemLogger();
assertNotNull("LoggerFinder must return logger", logger);
}
@Test
public void loggerLogsMessage() {
System.Logger logger = getSLF4JSystemLogger();
String message = "Test system logging!";
logger.log(System.Logger.Level.INFO, message);
String output = getOutput();
assertTrue("Captured output should contain logged message.", output.contains(message));
}
private static System.LoggerFinder getSLF4JSystemLoggerFinder() {
// this fails when test is executed from the module path
// because the module declaration does not declare
// `uses System.LoggerFinder`
ServiceLoader<System.LoggerFinder> loader = ServiceLoader.load(System.LoggerFinder.class);
Optional<Provider<LoggerFinder>> optional = loader.stream()
.filter( provider -> SLF4JSystemLoggerFinder.class.isAssignableFrom(provider.type()))
.findAny();
if(optional.isPresent()) {
Provider<LoggerFinder> p = optional.get();
return p.get();
} else {
throw new ServiceConfigurationError("Could not find SLF4JSystemLoggerFinder");
}
//
// return .stream()
// .filter(finderProvider -> SLF4JSystemLoggerFinder.class.isAssignableFrom(finderProvider.type()))
// .findAny()
// .map(ServiceLoader.Provider::get)
// .orElseThrow();
}
private static System.Logger getSLF4JSystemLogger() {
LoggerFinder loggerFinder = getSLF4JSystemLoggerFinder();
return loggerFinder.getLogger("TestLogger", SLF4JSystemLoggerTest.class.getModule());
}
private static String getOutput() {
ERROR_OUT_REPLACEMENT.flush();
return OUTPUT.toString();
}
}

View File

@ -22,7 +22,7 @@
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package org.slf4j.jdk;
package org.slf4j.jdk.platform.logging;
import java.lang.System.Logger;
@ -31,7 +31,7 @@ import java.lang.System.LoggerFinder;
import org.junit.Test;
public class PlatformLoggingTest {
public class SLF4JPlatformLoggingTest {
@Test
@ -39,5 +39,6 @@ public class PlatformLoggingTest {
LoggerFinder finder = System.LoggerFinder.getLoggerFinder();
Logger systemLogger = finder.getLogger("x", null);
systemLogger.log(Level.INFO, "hello");
systemLogger.log(Level.INFO, "hello %s", "world");
}
}

View File

@ -900,7 +900,12 @@ org.slf4j.impl.StaticLoggerBinder.SINGLETON from class org.slf4j.LoggerFactory
<td>log4j-over-slf4j.jar</td>
<td>log4j</td>
</tr>
<tr>
<td>slf4j-jdk-platform-logging.jar</td>
<td>org.slf4j.jdk.platform.logging</td>
</tr>
</table>
</dd>