SLF4JPlatformLogger.java
/**
* Copyright (c) 2004-2021 QOS.ch
* All rights reserved.
* <p>
* 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:
* <p>
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* <p>
* 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.helpers.Reporter;
import org.slf4j.spi.CallerBoundaryAware;
import org.slf4j.spi.LoggingEventBuilder;
/**
* Implements {@link System.Logger} by forwarding logging events to the appropriate {@link org.slf4j.Logger} instance.
*
* @since 2.0.0
*/
class SLF4JPlatformLogger implements System.Logger {
static private final String PRESUMED_CALLER_BOUNDARY = System.Logger.class.getName();
private final Logger slf4jLogger;
public SLF4JPlatformLogger(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 called via log method, fixExtremeLevels has been called. System.Logger.Level.ALL and Level.OFF have
// been respectively mapped to Level.TRACE and Level.ERROR.
// However, if isLoggable is called directly, we must fix the extreme levels here as well.
final Level jplLevelReduced = fixExtremeLevels(jplLevel);
org.slf4j.event.Level slf4jLevel = jplLevelToSLF4JLevel(jplLevelReduced);
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 parameters.
*
* @param jplLevel
* @param bundle
* @param msg
* @param thrown
* @param params
*/
private void log(final Level jplLevel, final ResourceBundle bundle, final String msg, final Throwable thrown, final Object... params) {
final Level jplLevelReduced = fixExtremeLevels(jplLevel);
org.slf4j.event.Level slf4jLevel = jplLevelToSLF4JLevel(jplLevelReduced);
boolean isEnabled = slf4jLogger.isEnabledForLevel(slf4jLevel);
if(isEnabled) {
performLog(slf4jLevel, bundle, msg, thrown, params);
}
}
/**
* <p>Level.OFF and Level.ALL levels are not supposed to be used when calling log printing methods.
* </p>
*
* <p>We compensate for such incorrect usage by transforming Level.OFF as Level.ERROR and
* Level.ALL as Level.TRACE.
* </p>
*
* @param jplLevel
* @return
*/
private Level fixExtremeLevels(Level jplLevel) {
if(jplLevel == Level.OFF)
return Level.ERROR;
if(jplLevel == Level.ALL)
return Level.TRACE;
return jplLevel;
}
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 = MessageFormat.format(message, params);
}
if(leb instanceof CallerBoundaryAware) {
CallerBoundaryAware cba = (CallerBoundaryAware) leb;
cba.setCallerBoundary(PRESUMED_CALLER_BOUNDARY);
}
leb.log(message);
}
private void reportUnknownLevel(Level jplLevel) {
String message = "Unknown log level [" + jplLevel + "]";
IllegalArgumentException iae = new IllegalArgumentException(message);
Reporter.error("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();
}
}
}