Spring Boot 2 Dynamic Modification Log Level

Keywords: Spring Java curl log4j

This paper is based on: Spring Boot 2.1.3, which supports all versions of Spring Boot 2.x theoretically.

As a programmed ape, positioning is our daily work, and log is a very important basis for positioning. When locating problems in traditional ways, the following steps are often taken:

  • Set the log level low, such as DEBUG;
  • Restart the application;
  • Reproduce the problem and observe the log.

If you can dynamically modify the log level (refresh it immediately without restarting the application), it's absolutely like a cat's wings. In fact, since Spring Boot 1.5, the Spring Boot Actuator component has provided the ability to dynamically modify the log level.

TIPS

  • In fact, the lower version only needs simple expansion to achieve dynamic modification of the log level.
  • Children's shoes strange to Spring Boot Actuator Spring Boot Actuator Understand basic usage.

Don't talk too much nonsense. Bring the code on.

Code

  • Add dependency

    <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>
    

    Here spring-boot-starter-web is not required, but the following test code is used.

  • Writing code

    package com.itmuch.logging;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    
    /**
     * @author itmuch.com
     */
    @RestController
    public class TestController {
        private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class);
    
        @GetMapping("/test")
        public String simple() {
            LOGGER.debug("This is a debug Journal...");
            return "test";
        }
    }
    
  • Write configuration:

    management:
      endpoints:
        web:
          exposure:
            include: 'loggers'
    

    Because Spring Boot 2.x only exposes / health and / info endpoints by default, and log control requires / loggers endpoints, it needs to be set up to expose them.

The code has been written.

test

/ loggers endpoints provide the ability to view and modify log levels.

Test 1: View the log levels of the packages / classes currently being applied

Visit http://localhost:8080/actuator/loggers to see results similar to the following:

{
	"levels": ["OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE"],
	"loggers": {
		"ROOT": {
			"configuredLevel": "INFO",
			"effectiveLevel": "INFO"
		},		
		"com.itmuch.logging.TestController": {
			"configuredLevel": null,
			"effectiveLevel": "INFO"
		}
	}
	// ... omission
}

Test 2: View the specified package/class log details

Visit http://localhost:8080/actuator/loggers/com.itmuch.logging.TestController to see similar results:

{"configuredLevel":null,"effectiveLevel":"INFO"}

It is not difficult to find out from the test that to see which package / class log, you only need to construct / actuator/loggers / package name class name full path to access.

Test 3: Modify the log level

In the TestController class, the author wrote and set up a log LOGGER.debug("This is a debug log..."); and by test 1, the default log level is INFO, so it will not print. Let's try to set this type of log level to DEBUG.

curl -X POST http://localhost:8080/actuator/loggers/com.itmuch.logging.TestController \
-H "Content-Type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8" \
--data '{"configuredLevel":"debug"}'

As above, just send a POST request and set the request body to {"configuredLevel":"debug"}.

At this point, when you visit localhost:8080/test, you will see a log similar to the following:

2019-03-28 16:24:04.513 DEBUG 19635 - [nio-8080-exec-7] com.itmuch.logging.TestController: This is a debug log.

Also, visit http://localhost:8080/actuator/loggers/com.itmuch.logging.TestController at this time, and you can see the following results:

{"configuredLevel":"DEBUG","effectiveLevel":"DEBUG"}

Describes that the log level has been successfully dynamically modified.

Principle analysis

TIPS

This section focuses on how to implement dynamic modification.

Actuator has a convention that the definition code for the / actuator/xxx endpoint is in xxxEndpoint. So, find the class org. spring framework. boot. actuate. logging. Loggers Endpoint, and you can see code similar to the following:

@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);
	}
	// ... Other omissions
}

Endpoint, WriteOperation, @Selector are all new annotations that Spring Boot 2.0 started to provide.

@ Endpoint (id = loggers) is used to describe the endpoint of Spring Boot Actuator, which results in a / actuator/loggers path similar to Spring MVC's @RequestMapping("loggers").

@ WriteOperation indicates that this is a write operation, similar to Spring MVC's @PostMapping. Spring Boot Actuator also provides other operations, as shown in the following table:

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

@ Selector is used to filter a subset of the @Endpoint annotation return values, similar to Spring MVC's @PathVariable.

Thus, the above code is well understood - configureLogLevel method contains a line of code: this.loggingSystem.setLogLevel(name, configuredLevel); after sending a POST request, name is our package name or class name, and configuredLevel is our message body.

How to implement dynamic modification? You might as well check in and find the code as follows:

// org.springframework.boot.logging.LoggingSystem#setLogLevel
public void setLogLevel(String loggerName, LogLevel level) {
    throw new UnsupportedOperationException("Unable to set log level");
}

Hey hey, it's okay. There must be implementation classes. This method is implemented in the following implementation classes:

# Logging System for java.util.logging
org.springframework.boot.logging.java.JavaLoggingSystem
# Logging System for Log4j 2
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem
# Logging System for logback
org.springframework.boot.logging.logback.LogbackLoggingSystem
# Logging System for Doing Nothing
org.springframework.boot.logging.LoggingSystem.NoOpLoggingSystem

In Spring Boot 2.x, Logback is used by default, so it is entered into Logback Logging System. The code is as follows:

@Override
	public void setLogLevel(String loggerName, LogLevel level) {
		ch.qos.logback.classic.Logger logger = getLogger(loggerName);
		if (logger != null) {
			logger.setLevel(LEVELS.convertSystemToNative(level));
		}
	}

So far, the truth has come to light. In fact, there is no black technology at all. Spring Boot essentially uses the API of Logback, ch.qos.logback.classic.Logger.setLevel to implement log level modifications.

You may be curious.

You may wonder how Spring Boot knows what LoggingSystem is used under what circumstances with so many implementation classes in LoggingSystem. The following code can be found at org. spring framework. boot. logging. LoggingSystem:

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"));
	}
  // Eliminate irrelevant content.
}

It is not difficult to find out from the code, in fact, is to construct a map named SYSTEMS as a dictionary of various log systems; then in the get method, see if the application loads the classes in the map; if loaded, through reflection, initialize the response Logging System. For example: Spring Boot found that the current application loaded ch.qos.logback.core.Appender and instantiated the org.spring framework.boot.logging.logback.Logback LoggingSystem.

Interface

This article uses curl to manually send POST requests to manually modify the log level. This method is not suitable for production, because it is troublesome and error prone. In the production environment, it is recommended to customize the interface according to the RESTful API provided by Actuator, or use Spring Boot Admin to visually modify the log level, as shown in the following figure:

To modify which package/class log level, click directly.

Matching code

GitHub: https://github.com/eacdy/spring-boot-study/tree/master/spring-boot-logging-change-logging-level

Gitee: https://gitee.com/itmuch/spring-boot-study/tree/master/spring-boot-logging-change-logging-level

First text

http://www.itmuch.com/spring-boot/change-logger-level/

Pay attention to me

Posted by dstockto on Mon, 06 May 2019 15:05:39 -0700