/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.jstorm.utils;
import backtype.storm.utils.WorkerClassLoader;
import com.alibaba.jstorm.client.ConfigExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
/**
* Contains methods to access and manipulate logback/log4j framework dynamically at run-time.
* Here 'dynamically' means without referencing the logback/log4j JAR, but using it if found in the classpath.
*
* @author Jark (wuchong.wc@alibaba-inc.com)
*/
public class LogUtils {
private static final Logger LOG = LoggerFactory.getLogger(LogUtils.class);
public static final String LOGBACK_CLASSIC = "ch.qos.logback.classic";
public static final String LOGBACK_CLASSIC_LOGGER = "ch.qos.logback.classic.Logger";
public static final String LOGBACK_CLASSIC_LEVEL = "ch.qos.logback.classic.Level";
public static final String LOG4J_CLASSIC = "org.apache.log4j";
public static final String LOG4J_CLASSIC_LOGGER = "org.apache.log4j.Logger";
public static final String LOG4J_CLASSIC_LEVEL = "org.apache.log4j.Level";
// this timestamp is used to check whether we need to change log level
public static Long lastChangeTS = 0L;
private LogUtils() {
// Prevent instance creation
}
public static void update(Map conf) {
Map<String, String> logLevelConfig;
try {
logLevelConfig = ConfigExtension.getChangeLogLevelConfig(conf);
} catch (ClassCastException e) {
LOG.error("the log level config is not the type of Map<String, String> !");
return;
}
Long confTs = ConfigExtension.getChangeLogLevelTimeStamp(conf);
if (logLevelConfig != null && confTs != null && confTs > lastChangeTS) {
updateLogLevel(logLevelConfig);
if (WorkerClassLoader.isEnable()) {
WorkerClassLoader.switchThreadContext();
updateLogLevel(logLevelConfig);
WorkerClassLoader.restoreThreadContext();
}
lastChangeTS = confTs;
}
}
private static void updateLogLevel(Map<String, String> logLevelConfig) {
for (Map.Entry<String, String> entry : logLevelConfig.entrySet()) {
String loggerName = entry.getKey();
String level = entry.getValue();
boolean logback = setLogBackLevel(loggerName, level);
boolean log4j = setLog4jLevel(loggerName, level);
if (!logback && !log4j) {
LOG.warn("Couldn't set logback level to {} for the logger '{}'", level, loggerName);
}
}
}
/**
* Dynamically sets the logback log level for the given class to the specified level.
*
* @param loggerName Name of the logger to set its log level. If blank, root logger will be used.
* @param logLevel One of the supported log levels: TRACE, DEBUG, INFO, WARN, ERROR, FATAL,
* OFF. {@code null} value is considered as 'OFF'.
*/
public static boolean setLogBackLevel(String loggerName, String logLevel) {
String logLevelUpper = (logLevel == null) ? "OFF" : logLevel.toUpperCase();
try {
Package logbackPackage = Package.getPackage(LOGBACK_CLASSIC);
if (logbackPackage == null) {
LOG.warn("Logback is not in the classpath!");
return false;
}
// Use ROOT logger if given logger name is blank.
if ((loggerName == null) || loggerName.trim().isEmpty()) {
loggerName = (String) getFieldVaulue(LOGBACK_CLASSIC_LOGGER, "ROOT_LOGGER_NAME");
}
// Obtain logger by the name
Logger loggerObtained = LoggerFactory.getLogger(loggerName);
if (loggerObtained == null) {
// I don't know if this case occurs
LOG.warn("No logger for the name: {}", loggerName);
return false;
}
Object logLevelObj = getFieldVaulue(LOGBACK_CLASSIC_LEVEL, logLevelUpper);
if (logLevelObj == null) {
LOG.warn("No such log level: {}", logLevelUpper);
return false;
}
Class<?>[] paramTypes = {logLevelObj.getClass()};
Object[] params = {logLevelObj};
Class<?> clz = Class.forName(LOGBACK_CLASSIC_LOGGER);
Method method = clz.getMethod("setLevel", paramTypes);
method.invoke(loggerObtained, params);
LOG.info("LogBack level set to {} for the logger '{}'", logLevelUpper, loggerName);
return true;
} catch (NoClassDefFoundError e) {
LOG.warn("Couldn't set logback level to {} for the logger '{}'", logLevelUpper, loggerName, e);
return false;
} catch (Exception e) {
LOG.warn("Couldn't set logback level to {} for the logger '{}'", logLevelUpper, loggerName, e);
return false;
}
}
/**
* Dynamically sets the log4j log level for the given class to the specified level.
*
* @param loggerName Name of the logger to set its log level. If blank, root logger will be used.
* @param logLevel One of the supported log levels: TRACE, DEBUG, INFO, WARN, ERROR, FATAL,
* OFF. {@code null} value is considered as 'OFF'.
*/
public static boolean setLog4jLevel(String loggerName, String logLevel) {
String logLevelUpper = (logLevel == null) ? "OFF" : logLevel.toUpperCase();
try {
Package log4jPackage = Package.getPackage(LOG4J_CLASSIC);
if (log4jPackage == null) {
LOG.warn("Log4j is not in the classpath!");
return false;
}
Class<?> clz = Class.forName(LOG4J_CLASSIC_LOGGER);
// Obtain logger by the name
Object loggerObtained;
if ((loggerName == null) || loggerName.trim().isEmpty() || loggerName.trim().equals("ROOT")) {
// Use ROOT logger if given logger name is blank.
Method method = clz.getMethod("getRootLogger");
loggerObtained = method.invoke(null);
loggerName = "ROOT";
} else {
Method method = clz.getMethod("getLogger", String.class);
loggerObtained = method.invoke(null, loggerName);
}
if (loggerObtained == null) {
// I don't know if this case occurs
LOG.warn("No logger for the name: {}", loggerName);
return false;
}
Object logLevelObj = getFieldVaulue(LOG4J_CLASSIC_LEVEL, logLevelUpper);
if (logLevelObj == null) {
LOG.warn("No such log level: {}", logLevelUpper);
return false;
}
Class<?>[] paramTypes = {logLevelObj.getClass()};
Object[] params = {logLevelObj};
Method method = clz.getMethod("setLevel", paramTypes);
method.invoke(loggerObtained, params);
LOG.info("Log4j level set to {} for the logger '{}'", logLevelUpper, loggerName);
return true;
} catch (NoClassDefFoundError e) {
LOG.warn("Couldn't set log4j level to {} for the logger '{}'", logLevelUpper, loggerName, e);
return false;
} catch (Exception e) {
LOG.warn("Couldn't set log4j level to {} for the logger '{}'", logLevelUpper, loggerName);
return false;
}
}
private static Object getFieldVaulue(String fullClassName, String fieldName) {
try {
Class<?> clazz = Class.forName(fullClassName);
Field field = clazz.getField(fieldName);
return field.get(null);
} catch (ClassNotFoundException | IllegalAccessException | IllegalArgumentException | NoSuchFieldException |
SecurityException ignored) {
return null;
}
}
}