Java Log: dynamic level adjustment

As a developer, positioning of our daily work, while the log is a very important basis for our positioning problem. When the positioning of a conventional manner, often following steps:

  1. The logging level set low, for example, the DEBUG;
  2. Restart the application;
  3. Reproduce the problem, observe the log;

Actually you can dynamically modify the log level, without having to restart the application, effective immediately. This collection of three kinds of articles to dynamically modify the log level, respectively

  1. Spring Boot 2 dynamically change the log level
  2. Ali Arthas online diagnostic tools to adjust the log level record
  3. US Mission to dynamically adjust the logging level - gadgets to solve big problems

Spirng Boot dynamically change the log level

From the beginning of Spring Boot 1.5, Spring Boot Actuator assemblies already provide the ability to dynamically change the log level.

Examples

1. Introducing a spring-boot-starter-actuatordependent, as follows:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
复制代码

2. Write test code, as follows:

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

@RestController
public class DemoController {

    private static Logger logger = LoggerFactory.getLogger(DemoController.class);

    @GetMapping("/helloworld")
    public String helloworld(){
        logger.debug("welcome to learn spring boot");
        return "welcome to learn spring boot";
    }

}
复制代码

3. Profiles

management:
  endpoints:
    web:
      exposure:
        include: 'loggers'
复制代码

Spring Boot 2.x default only exposure / health and / info endpoints and control need to use the log / loggers endpoint, and therefore needs to be set to be exposed.

test

/loggersEndpoint provides view and modify the log level of ability.

  1. View the current log level access each application package / class HTTP: // localhost: 8080 / Actuator / Loggers , you can see results similar to the following:

  2. Check the specified package / class log details visit HTTP: // localhost: 8080 / Actuator / Loggers / com.blockmao.springboot.demo.DemoController , you can see results similar to the following:

  3. Change the log level default log level is INFO, so DemoControllerthe debug log does not print. Let's try this class logging level DEBUG, the following

    In this case, visit http: // localhost: 8080 / helloworld will see a log similar to the following:

    And, this time to access the HTTP: // localhost: 8080 / Actuator / Loggers / com.itmuch.logging.TestController , results similar to the following can be seen:

principle

Actuator has agreed /actuator/xxxcode defines the endpoint in xxxEndpointthe. Find the class org.springframework.boot.actuate.logging.LoggersEndpoint, as follows:

@Endpoint(id = "loggers")
public class LoggersEndpoint {
	private final LoggingSystem loggingSystem;

	@WriteOperation
	public void configureLogLevel(@Selector String name,
			@Nullable LogLevel configuredLevel) {
		Assert.notNull(name, "Name must not be empty");
		this.loggingSystem.setLogLevel(name, configuredLevel);
	}
	// ...其他省略
}
复制代码

EndpointWhere WriteOperation, , @Selectorare the beginning of Spring new comment Boot 2.0 provides.

@Endpoint(id = "loggers")Spring Boot Actuator used to describe the end, this will lead to a /actuator/loggerspath that is similar to the Spring MVC @RequestMapping("loggers").

@WriteOperation indicates that this is a write operation, which is similar to the Spring MVC @PostMapping. Spring Boot Actuator further provides other operations, as follows:

Operation HTTP method
@ReadOperation GET
@WriteOperation POST
@DeleteOperation DELETE

@Selector a subset of screening @Endpoint notes the return value, which is similar to the Spring MVC @PathVariable.

Thus, the above code is well understood - configureLogLevelMethod: After sending a POST request, name of the package is that we pass the class name or names, configuredLevel is that we pass the message body.

org.springframework.boot.logging.LoggingSystem#setLogLevelIs an abstract method, embodied completed by subclasses. LoggingSystemClass structure as shown below:

There are so many LoggingSystem implementation class, Spring Boot know how LoggingSystem use it and under what circumstances? You can be org.springframework.boot.logging.LoggingSystemfound similar to the following codes:

public abstract class LoggingSystem {
	private static final Map<String, String> SYSTEMS;

	static {
		Map<String, String> systems = new LinkedHashMap<>();
		systems.put("ch.qos.logback.core.Appender",
				"org.springframework.boot.logging.logback.LogbackLoggingSystem");
		systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory",
				"org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
		systems.put("java.util.logging.LogManager",
				"org.springframework.boot.logging.java.JavaLoggingSystem");
		SYSTEMS = Collections.unmodifiableMap(systems);
	}

	/**
	 * Detect and return the logging system in use. Supports Logback and Java Logging.
	 * @param classLoader the classloader
	 * @return the logging system
	 */
	public static LoggingSystem get(ClassLoader classLoader) {
		String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
		if (StringUtils.hasLength(loggingSystem)) {
			if (NONE.equals(loggingSystem)) {
				return new NoOpLoggingSystem();
			}
			return get(classLoader, loggingSystem);
		}
		return SYSTEMS.entrySet().stream()
				.filter((entry) -> ClassUtils.isPresent(entry.getKey(), classLoader))
				.map((entry) -> get(classLoader, entry.getValue())).findFirst()
				.orElseThrow(() -> new IllegalStateException(
						"No suitable logging system located"));
	}
  // 省略不相关内容...
}
复制代码

By the code difficult to find, in fact, it is to build a map named SYSTEMS as dictionaries various logs system; then get method, see the map application is loaded in the class; if loaded it through reflection, initialization LoggingSystem. For example: Spring Boot find the current application is loaded ch.qos.logback.core.Appender, and went to instantiate org.springframework.boot.logging.logback.LogbackLoggingSystem.

Arthas ognl command to dynamically change the log level

Use ognlcommand to dynamically change the log level, the following steps:

  1. Find classLoaderHash current class

  2. Gets logger with OGNL

    Logs can be found using the Logback framework.

  3. Separately disposed logger level DemoController

  4. Global Settings logger level

If logging framework log4j, use the above ognlcommands will be given. As for why? Read the Java log: SLF4J Comments

US Mission to dynamically adjust the logging level gadgets

  1. Initialization: determine the log framework used to get all instances Logger memory profile, and their references to the Map cache container.

    String type = StaticLoggerBinder.getSingleton().getLoggerFactoryClassStr();
    if (LogConstant.LOG4J_LOGGER_FACTORY.equals(type)) {
        logFrameworkType = LogFrameworkType.LOG4J;
        Enumeration enumeration = org.apache.log4j.LogManager.getCurrentLoggers();
        while (enumeration.hasMoreElements()) {
            org.apache.log4j.Logger logger = (org.apache.log4j.Logger) enumeration.nextElement();
            if (logger.getLevel() != null) {
                loggerMap.put(logger.getName(), logger);
            }
        }
        org.apache.log4j.Logger rootLogger = org.apache.log4j.LogManager.getRootLogger();
        loggerMap.put(rootLogger.getName(), rootLogger);
    } else if (LogConstant.LOGBACK_LOGGER_FACTORY.equals(type)) {
        logFrameworkType = LogFrameworkType.LOGBACK;
        ch.qos.logback.classic.LoggerContext loggerContext = (ch.qos.logback.classic.LoggerContext) LoggerFactory.getILoggerFactory();
        for (ch.qos.logback.classic.Logger logger : loggerContext.getLoggerList()) {
            if (logger.getLevel() != null) {
                loggerMap.put(logger.getName(), logger);
            }
        }
        ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
        loggerMap.put(rootLogger.getName(), rootLogger);
    } else if (LogConstant.LOG4J2_LOGGER_FACTORY.equals(type)) {
        logFrameworkType = LogFrameworkType.LOG4J2;
        org.apache.logging.log4j.core.LoggerContext loggerContext = (org.apache.logging.log4j.core.LoggerContext) org.apache.logging.log4j.LogManager.getContext(false);
        Map<String, org.apache.logging.log4j.core.config.LoggerConfig> map = loggerContext.getConfiguration().getLoggers();
        for (org.apache.logging.log4j.core.config.LoggerConfig loggerConfig : map.values()) {
            String key = loggerConfig.getName();
            if (StringUtils.isBlank(key)) {
                key = "root";
            }
            loggerMap.put(key, loggerConfig);
        }
    } else {
        logFrameworkType = LogFrameworkType.UNKNOWN;
        LOG.error("Log框架无法识别: type={}", type);
    }
    复制代码
  2. Get Logger List: Map removed from local container.

    private String getLoggerList() {
        JSONObject result = new JSONObject();
        result.put("logFramework", logFrameworkType);
        JSONArray loggerList = new JSONArray();
        for (ConcurrentMap.Entry<String, Object> entry : loggerMap.entrySet()) {
            JSONObject loggerJSON = new JSONObject();
            loggerJSON.put("loggerName", entry.getKey());
            if (logFrameworkType == LogFrameworkType.LOG4J) {
                org.apache.log4j.Logger targetLogger = (org.apache.log4j.Logger) entry.getValue();
                loggerJSON.put("logLevel", targetLogger.getLevel().toString());
            } else if (logFrameworkType == LogFrameworkType.LOGBACK) {
                ch.qos.logback.classic.Logger targetLogger = (ch.qos.logback.classic.Logger) entry.getValue();
                loggerJSON.put("logLevel", targetLogger.getLevel().toString());
            } else if (logFrameworkType == LogFrameworkType.LOG4J2) {
                org.apache.logging.log4j.core.config.LoggerConfig targetLogger = (org.apache.logging.log4j.core.config.LoggerConfig) entry.getValue();
                loggerJSON.put("logLevel", targetLogger.getLevel().toString());
            } else {
                loggerJSON.put("logLevel", "Logger的类型未知,无法处理!");
            }
            loggerList.add(loggerJSON);
        }
        result.put("loggerList", loggerList);
        LOG.info("getLoggerList: result={}", result.toString());
        return result.toString();
    }
    复制代码
  3. Modify Logger level

    private String setLogLevel(JSONArray data) {
        LOG.info("setLogLevel: data={}", data);
        List<LoggerBean> loggerList = parseJsonData(data);
        if (CollectionUtils.isEmpty(loggerList)) {
            return "";
        }
        for (LoggerBean loggerbean : loggerList) {
            Object logger = loggerMap.get(loggerbean.getName());
            if (logger == null) {
                throw new RuntimeException("需要修改日志级别的Logger不存在");
            }
            if (logFrameworkType == LogFrameworkType.LOG4J) {
                org.apache.log4j.Logger targetLogger = (org.apache.log4j.Logger) logger;
                org.apache.log4j.Level targetLevel = org.apache.log4j.Level.toLevel(loggerbean.getLevel());
                targetLogger.setLevel(targetLevel);
            } else if (logFrameworkType == LogFrameworkType.LOGBACK) {
                ch.qos.logback.classic.Logger targetLogger = (ch.qos.logback.classic.Logger) logger;
                ch.qos.logback.classic.Level targetLevel = ch.qos.logback.classic.Level.toLevel(loggerbean.getLevel());
                targetLogger.setLevel(targetLevel);
            } else if (logFrameworkType == LogFrameworkType.LOG4J2) {
                org.apache.logging.log4j.core.config.LoggerConfig loggerConfig = (org.apache.logging.log4j.core.config.LoggerConfig) logger;
                org.apache.logging.log4j.Level targetLevel = org.apache.logging.log4j.Level.toLevel(loggerbean.getLevel());
                loggerConfig.setLevel(targetLevel);
                org.apache.logging.log4j.core.LoggerContext ctx = (org.apache.logging.log4j.core.LoggerContext) org.apache.logging.log4j.LogManager.getContext(false);
                ctx.updateLoggers(); // This causes all Loggers to refetch information from their LoggerConfig.
            } else {
                throw new RuntimeException("Logger的类型未知,无法处理!");
            }
        }
        return "success";
    }
    复制代码

Guess you like

Origin juejin.im/post/5d9cb3045188254423573c82