GitHub 19K Star Java engineer into god’s path, not to learn about it!

GitHub 19K Star Java engineer into god’s road, really not to learn about it!

GitHub 19K Star Java engineer into god’s road, really really not to learn about it!

I wrote an article in the past titled “Logging is still drying up my service even though there is automatic cleaning!” , introduced a big rush failure, because the log volume surge, leading to the server almost hang.

After that problem, I developed a simple log degradation tool that dynamically pushes the log level through configuration and dynamically changes the log output level online. And the modification of this configuration is configured on our plan platform to promote timing or emergency plan processing during the period.

So, this article will briefly introduce the idea and code implementation.

The level of logging

A brief introduction to logging levels before the body begins. Different logging frameworks support different logging levels, the most common of which are Log4j and Logback.

Log4j supports eight log levels in descending order of priority: OFF, FATAL, ERROR, WARN, INFO, DEBUG, TRACE, and ALL.

The Logback supports seven log levels, with the highest priority in descending order: OFF, ERROR, WARN, INFO, DEBUG, TRACE, and ALL.

You can see the usual ERROR, WARN, INFO, and DEBUG, both of which are supported.

By setting the log output level, we mean the lowest level of the log output. That is, if we set the log level to INFO, the logs including INFO and higher priority can be output.

Both Log4j and Logback control the level of logging output through logging configuration files. I won’t go into details here.

The logging framework

Above we mentioned Log4j and Logback, two of the more commonly used logging frameworks.

However, many times we print logging in code without using the logging framework directly, relying instead on a logging facade such as SLF4J, Commons-logging, and so on.

The most common method is to get a Logger from slF4J’s LoggerFactory getLogger and print the log.

private static final Logger LOGGER = LoggerFactory.getLogger(LoggerService.class);

public void test(){
    LOGGER.info("hollis log test");
}
Copy the code

When we create a Logger object using the LoggerFactory.getLogger method, we pass it a loggerName that uniquely identifies a Logger. Use the full path name of the LoggerService class as its loggerName.

LoggerName is part of the configuration information for each Logger, along with other information such as the log output level.

As for why not directly use log4j and Logback to print logs, I have analyzed in “Why Alibaba prohibits engineers from directly using the API of logging system (Log4j and Logback)”.

Arthas Changes the log level

Before we get into the code implementation, let’s introduce a tool that can also help us change the log level dynamically.

Arthas (arthas.aliyun.com/doc/).

Arthas provides a Logger command to view and update Logger information, including log levels.

View logger information with the specified name

[arthas@2062]$ logger -n org.springframework.web name org.springframework.web class ch.qos.logback.classic.Logger classLoader sun.misc.Launcher$AppClassLoader@2a139a55 classLoaderHash 2a139a55 level null effectiveLevel INFO additivity True codeSource file: / Users/hengyunabc/m2 / repository/ch/qos/logback/logback - classic / 1.2.3 / logback - classic - 1.2.3. JarCopy the code

Update the logger level

[arthas@2062]$ logger --name ROOT --level debug
update logger level success.
Copy the code

Simple, you can change the machine log level with a single command.

Arthas is currently not particularly cluster-friendly. Although Arthas supports remote management/connection of multiple agents through the Arthas Tunnel Server/Client, it is not very easy to use and requires high command requirements.

Also, we have a tool in our system that allows us to dynamically adjust log levels in a pre-planned way during a big push, which is not as easy to do with Arthas.

Code implementation

The tool I wrote is very simple, is to provide dynamic log level change entry, convenient for users to change the level of dynamic.

In order to use it easily, I packaged it in a Spring Boot Starter, and it is directly connected to the internal configuration center of the company, which can easily change the log level through the configuration center.

First, let’s look at the core of the function, which is to dynamically change the log level. The code is as follows:

import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.LoggerConfiguration; import org.springframework.boot.logging.LoggingSystem; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import static org.springframework.boot.logging.LoggingSystem.ROOT_LOGGER_NAME; / log level set service class * * * * * @ author Hollis * / public class LoggerLevelSettingService {@autowired private LoggingSystem loggingSystem; private static final Logger LOGGER = LoggerFactory.getLogger(LoggerLevelSettingService.class); public void setRootLoggerLevel(String level) { LoggerConfiguration loggerConfiguration = loggingSystem.getLoggerConfiguration(ROOT_LOGGER_NAME); if (loggerConfiguration == null) { if (LOGGER.isErrorEnabled()) { LOGGER.error("no loggerConfiguration with loggerName "  + level); } return; } if (! supportLevels().contains(level)) { if (LOGGER.isErrorEnabled()) { LOGGER.error("current Level is not support : " + level); } return; } if (! loggerConfiguration.getEffectiveLevel().equals(LogLevel.valueOf(level))) { if (LOGGER.isInfoEnabled()) { LOGGER.info("setRootLoggerLevel success,old level is '" + loggerConfiguration.getEffectiveLevel() + "' , new level is '" + level + "'"); } loggingSystem.setLogLevel(ROOT_LOGGER_NAME, LogLevel.valueOf(level)); } } private List<String> supportLevels() { return loggingSystem.getSupportedLogLevels().stream().map(Enum::name).collect(Collectors.toList()); }}Copy the code

The above code changes the application ROOT log output level based on the level passed in by the user.

It USES a key services: org. Springframework. Boot. Logging. LoggingSystem

This service is SpringBoot’s abstraction of the logging system and is a top-level abstract class. He has many concrete implementations:

From the above figure, we can see that SpringBoot currently supports four types of logging. Log4j(Log4JLoggingSystem), Log4j2(Log4J2LoggingSystem), and Logback(LogbackLoggingSystem).

LoggingSystem is an abstract class that contains these methods:

  • BeforeInitialize method: Things that need to be handled before the logging system is initialized. Abstract approach, different logging architectures do different processing
  • The initialize method: initializes the log system. By default, no processing is performed. Subclasses are required for initialization
  • CleanUp method: Cleans up the log system. By default, no processing is performed, and subclasses are required to clear the work
  • GetShutdownHandler () : Returns a Runnable for handling operations that need to be performed after the logging system is shut down when the JVM exits
  • SetLogLevel method: Abstract method used to set the corresponding logger level

SpringBoot at startup, can complete LoggingSystem initialization, this part of the code is implemented in LoggingApplicationListener:

/ * * * to perform LoggingSystem initialization of the prefix * / private void onApplicationStartingEvent (ApplicationStartingEvent event) { // LogBack: LogbackLoggingSystem // log4j2: LogbackLoggingSystem Log4J2LoggingSystem / / javalog:  JavaLoggingSystem this.loggingSystem = LoggingSystem .get(event.getSpringApplication().getClassLoader()); / / execution beforeInitialize method finishing initialization operation enclosing loggingSystem. BeforeInitialize (); }Copy the code

With LoggingSystem, you can change log levels dynamically. He helped us mask the underlying specific logging framework.

In addition to changing ROOT level logs, you can also change user – defined log levels.

Define a LoggerConfig to encapsulate logging configuration:

/** * the config of logger * * @author Hollis */ public class LoggerConfig { /** * the name of the logger */ private String loggerName; /** * the log level * * @see LogLevel */ private String level; public String getLoggerName() { return loggerName; } public void setLoggerName(String loggerName) { this.loggerName = loggerName; } public String getLevel() { return level; } public void setLevel(String level) { this.level = level; }}Copy the code

Then provide a method to dynamically change the log level, the code implementation is as follows:

public void setLoggerLevel(List<LoggerConfig> configList) { Optional.ofNullable(configList).orElse(Collections.emptyList()).forEach( config -> { LoggerConfiguration loggerConfiguration = loggingSystem.getLoggerConfiguration(config.getLoggerName()); if (loggerConfiguration == null) { if (LOGGER.isErrorEnabled()) { LOGGER.error("no loggerConfiguration with loggerName "  + config.getLoggerName()); } return; } if (! supportLevels().contains(config.getLevel())) { if (LOGGER.isErrorEnabled()) { LOGGER.error("current Level is not support  : " + config.getLevel()); } return; } if (LOGGER.isInfoEnabled()) { LOGGER.info("setLoggerLevel success for logger '" + config.getLoggerName() + "' ,old level is '" + loggerConfiguration.getEffectiveLevel() + "' , new level is '" + config.getLevel() + "'"); } loggingSystem.setLogLevel(config.getLoggerName(), LogLevel.valueOf(config.getLevel())); }); }Copy the code

Change the loggerLevel of the specified loggerName based on the LoggerConfig passed in by the user. As to where LoggerLevel comes from, it can be passed in through configuration, such as parsing JSON configurations or YML files.

For example, we can use the following configuration in the configuration center to control the log level and push it:

[{'loggerName':'com.hollis.degradation.core.logger.LoggerLevelSettingService','level':'WARN'}]
Copy the code

The above configuration, will make the loggerName for com. Hollis. Degradation. The core. The logger. LoggerLevelSettingService log level dynamic change to WARN, in addition, if the configuration information is as follows:

[{'loggerName':'com.hollis.degradation.core.logger','level':'WARN'}]
Copy the code

So, the will to com. Hollis. Degradation. The core. The logger this package path below all the classes for the log output LoggerName level all dynamic modification to WARN.

Of course, this configuration also supports configuring multiple Logger levels if the following configuration is used:

[
  {'loggerName':'com.hollis.degradation.core.logger','level':'WARN'}
  ,{'loggerName':'com.hollis.degradation.core.logger.LoggerLevelSettingService','level':'INFO'}
]
Copy the code

There are multiple logs added to the code, and they are defined as

private static final Logger LOGGER1 = LoggerFactory.getLogger(LoggerLevelSettingService.class);
private static final Logger LOGGER2 = LoggerFactory.getLogger(TestService.class);
private static final Logger LOGGER3 = LoggerFactory.getLogger(DebugService.class);
Copy the code

Then, when the configuration takes effect, the output level of the above LOGGER1 will be INFO, and the levels of LOGGER2 and LOGGER3 will be WARN.

In addition, the above log level changes may affect the log output of our own tool, so we provide a method to directly change the log level of our own logging service:

public void setDegradationLoggerLevel(String level) { LoggerConfiguration loggerConfiguration = loggingSystem.getLoggerConfiguration( this.getClass().getName()); if (loggerConfiguration == null) { if (LOGGER.isWarnEnabled()) { LOGGER.warn("no loggerConfiguration with loggerName " +  level); } return; } if (! supportLevels().contains(level)) { if (LOGGER.isErrorEnabled()) { LOGGER.error("current Level is not support : " + level); } return; } if (! loggerConfiguration.getEffectiveLevel().equals(LogLevel.valueOf(level))) { loggingSystem.setLogLevel(this.getClass().getName(), LogLevel.valueOf(level)); }}Copy the code

Have more than the LoggerLevelSettingService after class, basic capability of the dynamic change log, the next step is to configure their center dynamically modify the log level.

Here because different configuration center usage is different, I just take our own configuration center simple example:

/ downgrade switch registry * * * * * @ author Hollis * / public class DegradationSwitchInitializer implements the Listener, @value ("${project.name}") private String appName; @Autowired private LoggerLevelSettingService loggerLevelSettingService; @override public void valueChange(String appName, String nameSpace, String name, String value) { if (name.equals(rootLogLevel.name())) { loggerLevelSettingService.setRootLoggerLevel(value); } if (name.equals(logLevelConfig.name())) { List<LoggerConfig> loggerConfigs = JSON.parseArray(value, LoggerConfig.class); loggerLevelSettingService.setLoggerLevel(loggerConfigs); } / / will downgrade tools of log output level is set to the INFO, can ensure the log normal output loggerLevelSettingService. SetDegradationLoggerLevel (" INFO "); } @ Override public void afterPropertiesSet () {/ / the service configuration to ConfigCenterManager configuration center. The addListener (this); ConfigCenterManager.init(appName, DegradationConfig.class); }}Copy the code

Above, we implemented to listen for changes in the configuration center and dynamically change the log level.

Now that the basic functions are complete, you can consider how to make other applications quickly accessible by defining a Starter. The main code is as follows:

Define a Configuration class:

/** * @author Hollis */ @Configuration @ConditionalOnProperty(prefix = "hollis.degradation", name = "enable", havingValue = "true") public class HollisDegradationAutoConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnProperty(name = "project.name") public LoggerLevelSettingService loggerLevelSettingService() { return new LoggerLevelSettingService(); } @Bean @ConditionalOnMissingBean @ConditionalOnBean(value = LoggerLevelSettingService.class) public DegradationSwitchInitializer degradationSwitchInitializer() { return new DegradationSwitchInitializer(); }}Copy the code

Define two beans in this class, and the bean definition assumes that the following two configuration items are configured in the application:

hollis.degradation.enable = true
project.name = test
Copy the code

Set a spring.factories file with the following definitions:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.hollis.degradation.starter.autoconfiguration.HollisDe gradationAutoConfigurationCopy the code

Above, we just need to introduce our starter and configure two configuration items in the application that needs to introduce downgrade tools.

The log output level of a single machine or cluster can be dynamically modified in the configuration center. The log output level can be configured on the emergency plan platform during the promotion period, and emergency plans can be implemented quickly.

conclusion

Above, the basic realization of a lot of basic functions, the realization of the main factors to consider the following:

  • 1. Universal. To be able to support different logging frameworks at the same time, the logging framework used by the client does not affect our functionality, and the client does not need to care about its own logging framework differences.
  • 2. Configurable. You can push configuration information through the external configuration center for quick adjustment.
  • 3. Easy to use. Encapsulate it into the SpringBoot Starter to facilitate client access.
  • 4. Non-invasive. The use of the framework should not interfere with the normal operation of the application.

Of course, this tool is just a few hours of polishing, there are still a lot of things that can be optimized, such as multiple configuration formats, support to view log configuration through the EndPoint, etc., which are not implemented yet.

This article only provides an idea, hope that everyone can learn to use tools to solve the problems encountered in daily work, learn to build wheels.

About the author: Hollis, a person with a unique pursuit of Coding, is a technical expert of Alibaba, co-author of “Three Courses for Programmers”, and author of a series of articles “Java Engineers becoming Gods”.

If you have any comments, suggestions, or want to communicate with the author, you can follow the public account [Hollis] and directly leave a message to me in the background.