| package org.apache.cassandra.utils.logging; |
| |
| import java.lang.management.ManagementFactory; |
| import java.security.AccessControlException; |
| import java.util.Iterator; |
| import java.util.Map; |
| |
| import javax.management.JMX; |
| import javax.management.ObjectName; |
| |
| import org.apache.cassandra.security.ThreadAwareSecurityManager; |
| import org.apache.commons.lang3.StringUtils; |
| import org.slf4j.LoggerFactory; |
| |
| import com.google.common.collect.Maps; |
| |
| import ch.qos.logback.classic.Level; |
| import ch.qos.logback.classic.Logger; |
| import ch.qos.logback.classic.LoggerContext; |
| import ch.qos.logback.classic.jmx.JMXConfiguratorMBean; |
| import ch.qos.logback.classic.spi.ILoggingEvent; |
| import ch.qos.logback.classic.spi.TurboFilterList; |
| import ch.qos.logback.classic.turbo.ReconfigureOnChangeFilter; |
| import ch.qos.logback.classic.turbo.TurboFilter; |
| import ch.qos.logback.core.Appender; |
| import ch.qos.logback.core.hook.DelayingShutdownHook; |
| |
| /** |
| * Encapsulates all logback-specific implementations in a central place. |
| * Generally, the Cassandra code-base should be logging-backend agnostic and only use slf4j-api. |
| * This class MUST NOT be used directly, but only via {@link LoggingSupportFactory} which dynamically loads and |
| * instantiates an appropriate implementation according to the used slf4j binding. |
| */ |
| public class LogbackLoggingSupport implements LoggingSupport |
| { |
| |
| private static final org.slf4j.Logger logger = LoggerFactory.getLogger(LogbackLoggingSupport.class); |
| |
| @Override |
| public void onStartup() |
| { |
| // The default logback configuration in conf/logback.xml allows reloading the |
| // configuration when the configuration file has changed (every 60 seconds by default). |
| // This requires logback to use file I/O APIs. But file I/O is not allowed from UDFs. |
| // I.e. if logback decides to check for a modification of the config file while |
| // executing a sandbox thread, the UDF execution and therefore the whole request |
| // execution will fail with an AccessControlException. |
| // To work around this, a custom ReconfigureOnChangeFilter is installed, that simply |
| // prevents this configuration file check and possible reload of the configuration, |
| // while executing sandboxed UDF code. |
| Logger logbackLogger = (Logger) LoggerFactory.getLogger(ThreadAwareSecurityManager.class); |
| LoggerContext ctx = logbackLogger.getLoggerContext(); |
| |
| TurboFilterList turboFilterList = ctx.getTurboFilterList(); |
| for (int i = 0; i < turboFilterList.size(); i++) |
| { |
| TurboFilter turboFilter = turboFilterList.get(i); |
| if (turboFilter instanceof ReconfigureOnChangeFilter) |
| { |
| ReconfigureOnChangeFilter reconfigureOnChangeFilter = (ReconfigureOnChangeFilter) turboFilter; |
| turboFilterList.set(i, new SMAwareReconfigureOnChangeFilter(reconfigureOnChangeFilter)); |
| break; |
| } |
| } |
| } |
| |
| @Override |
| public void onShutdown() |
| { |
| DelayingShutdownHook logbackHook = new DelayingShutdownHook(); |
| logbackHook.setContext((LoggerContext) LoggerFactory.getILoggerFactory()); |
| logbackHook.run(); |
| } |
| |
| @Override |
| public void setLoggingLevel(String classQualifier, String rawLevel) throws Exception |
| { |
| Logger logBackLogger = (Logger) LoggerFactory.getLogger(classQualifier); |
| |
| // if both classQualifier and rawLevel are empty, reload from configuration |
| if (StringUtils.isBlank(classQualifier) && StringUtils.isBlank(rawLevel)) |
| { |
| JMXConfiguratorMBean jmxConfiguratorMBean = JMX.newMBeanProxy(ManagementFactory.getPlatformMBeanServer(), |
| new ObjectName("ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator"), |
| JMXConfiguratorMBean.class); |
| jmxConfiguratorMBean.reloadDefaultConfiguration(); |
| return; |
| } |
| // classQualifier is set, but blank level given |
| else if (StringUtils.isNotBlank(classQualifier) && StringUtils.isBlank(rawLevel)) |
| { |
| if (logBackLogger.getLevel() != null || hasAppenders(logBackLogger)) |
| logBackLogger.setLevel(null); |
| return; |
| } |
| |
| Level level = Level.toLevel(rawLevel); |
| logBackLogger.setLevel(level); |
| logger.info("set log level to {} for classes under '{}' (if the level doesn't look like '{}' then the logger couldn't parse '{}')", level, classQualifier, rawLevel, rawLevel); |
| } |
| |
| @Override |
| public Map<String, String> getLoggingLevels() |
| { |
| Map<String, String> logLevelMaps = Maps.newLinkedHashMap(); |
| LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); |
| for (Logger logBackLogger : lc.getLoggerList()) |
| { |
| if (logBackLogger.getLevel() != null || hasAppenders(logBackLogger)) |
| logLevelMaps.put(logBackLogger.getName(), logBackLogger.getLevel().toString()); |
| } |
| return logLevelMaps; |
| } |
| |
| private boolean hasAppenders(Logger logBackLogger) |
| { |
| Iterator<Appender<ILoggingEvent>> it = logBackLogger.iteratorForAppenders(); |
| return it.hasNext(); |
| } |
| |
| /** |
| * The purpose of this class is to prevent logback from checking for config file change, |
| * if the current thread is executing a sandboxed thread to avoid {@link AccessControlException}s. |
| */ |
| private static class SMAwareReconfigureOnChangeFilter extends ReconfigureOnChangeFilter |
| { |
| SMAwareReconfigureOnChangeFilter(ReconfigureOnChangeFilter reconfigureOnChangeFilter) |
| { |
| setRefreshPeriod(reconfigureOnChangeFilter.getRefreshPeriod()); |
| setName(reconfigureOnChangeFilter.getName()); |
| setContext(reconfigureOnChangeFilter.getContext()); |
| if (reconfigureOnChangeFilter.isStarted()) |
| { |
| reconfigureOnChangeFilter.stop(); |
| start(); |
| } |
| } |
| |
| protected boolean changeDetected(long now) |
| { |
| if (ThreadAwareSecurityManager.isSecuredThread()) |
| return false; |
| return super.changeDetected(now); |
| } |
| } |
| } |