This is the second day of my participation in the November Gwen Challenge. Check out the details: the last Gwen Challenge 2021

preface

When my colleagues went online today, they found that some machines printed logs, while others did not type any logs at all. There used to be no problem.

So the guess is that this development indirectly introduces the new log JAR package, and the log conflicts result in not printing.

The system uses SLF4J framework to print log4j2 logs. There are multiple SLF4J bridge packages in the system. The conflicting JARS are then removed, and all machines print the log as normal when it goes online


A, cause

There are many types of log printing (e.g., LOG4j2, JCL, LogBack, and so on) in an online system because there is a lot of middleware involved.

To consolidate all logs and print them uniformly, the SLF4J framework is the most commonly used.

SLF4J framework as a facade framework, there is no logging concrete implementation. Instead, they associate conversion with other specific logging implementations and configure a logging implementation in the system to print.

This can easily lead to jar package introduction conflicts, resulting in multiple logging implementations. When the LOGGING implementation chosen by the SLF4J framework is different from the one we configured, no logs will be printed.

The SLF4J framework prints a prompt when it detects multiple logging implementations. However, since it is standard error output, it will be printed in the console (Tomcat’s Catalina.out).



Second, why only part of the machine print

Because every SLF4J bridge package org. SLF4J. Impl. StaticLoggerBinder

SLF4J randomly selects one to use. Logs can be printed if the selected values are the same as those configured by the system; otherwise, logs cannot be printed.


3. Quickly sense multiple SLF4J bridge packets

As shown in the above findPossibleStaticLoggerBinderPathSet method, when there are multiple log bridge package will return a Set collection and prompt a message.

Because this message is not strong, it is not easy to perceive. Based on this, we can use reflection to get the actual number of bridge packets in the system and make custom hints.

1. Implement Spring’s BeanFactoryPostProcessor and hand it over to Spring to manage. Ensure that log conflict verification is automatically performed after the system is started

2, use reflection to obtain LoggerFactory instance and findPossibleStaticLoggerBinderPathSet method returns the result

3. According to the number of bridge packets, judge whether it is abnormal and customize the alarm

4. Discharge packets according to alarm information

<bean class="LogJarConflictCheck" />

/** * log jar package conflict check */
public class LogJarConflictCheck implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        try {
            Class<LoggerFactory> loggerFactoryClazz = LoggerFactory.class;
            Constructor<LoggerFactory> constructor = loggerFactoryClazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            LoggerFactory instance = constructor.newInstance();
            Method method = loggerFactoryClazz.getDeclaredMethod("findPossibleStaticLoggerBinderPathSet");
            // Force entry
            method.setAccessible(true);
            Set<URL> staticLoggerBinderPathSet = (Set<URL>)method.invoke(instance);
            if (CollectionUtils.isEmpty(staticLoggerBinderPathSet)) {
                handleLogJarConflict(staticLoggerBinderPathSet, "Class path is Empty.");
            }
            if (staticLoggerBinderPathSet.size() == 1) {
                return;
            }
            handleLogJarConflict(staticLoggerBinderPathSet, "Class path contains multiple SLF4J bindings. Pay attention to packing");
        } catch(Throwable t) { t.getStackTrace(); }}/** * log jar package conflict alarm *@param* staticLoggerBinderPathSet jar package path@paramTip */
    private void handleLogJarConflict (Set<URL> staticLoggerBinderPathSet, String tip) {
        String ip = getLocalHostIp();
        StringBuilder detail = new StringBuilder();
        detail.append("IP for").append(ip).append("; The prompt is").append(tip);
        if (CollectionUtils.isNotEmpty(staticLoggerBinderPathSet)) {
            String path = JsonUtils.toJson(staticLoggerBinderPathSet);
            detail.append("; The repeated package paths are").append(path);
        }
        String logDetail = detail.toString();
        
        //TODO uses custom alarm notification logDetail information
    }

    private String getLocalHostIp(a) {
        String ip;
        try {
            InetAddress addr = InetAddress.getLocalHost();
            ip = addr.getHostAddress();
        } catch (Exception var2) {
            ip = "";
        }
        returnip; }}Copy the code


Four, one configuration, lifetime reliability

The above method also helps us to quickly detect the log JAR package conflict, still need to manually discharge the package.

Is there a solution that will help us solve this problem completely?

The answer is to declare the jar packages we need to import and the jar packages we need to exclude to the top layer of Maven, and declare the jar packages we need to exclude to provided

This solution utilizes Maven’s packet sweep strategy:

1. Rely on the shortest path first principle;

2, when the dependency path is the same, declare the order first principle

When we declare all jar packages as direct dependencies, they will be used first. Packages that we need to remove will not enter the package as long as they are declared provided. In this way, the required packages are subject to our declaration, and the packages that need to be removed are not affected by indirect dependencies

<properties>
  <slf4j.version>1.77.</slf4j.version>
    <logback.version>1.23.</logback.version>
    <log4j.version>1.217.</log4j.version>
    <log4j2.version>2.3</log4j2.version>
    <jcl.version>1.2</jcl.version> </properties> <dependencies> <! Slf4J </groupId> <artifactId> slf4J-api </artifactId> <version>${slf4j.version}</version> </dependency> <! Log4j </groupId> <artifactId>log4j-core</artifactId> <version>${log4j2.version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>${log4j2.version}</version> </dependency> <! Set the jar for log4j, logback, and JCL to provided. > <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>${logback.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>${jcl.version}</version> <scope>provided</scope> </dependency> <! -- To prevent cyclic conversion, Log4j </groupId> <artifactId>log4j-to-slf4j</artifactId> <version>${log4j2.version}</version> <scope>provided</scope> </dependency> <! Log4j, JCL, JUL to SLf4J bridge package SLF4J--> <dependency> <groupId>org. SLF4J </groupId> <artifactId>log4j-over-slf4j</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jul-to-slf4j</artifactId> <version>${slf4j.version}</version> </dependency> <! <dependency> <groupId>org.apache.logging. Log4j </groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>${log4j2.version}</version> </dependency> <! Slf4j to log4j, JCL, JUL to SLf4J bridge packet, Slf4j </groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> <version>${slf4j.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jcl</artifactId> <version>${slf4j.version}</version> <scope>provided</scope> </dependency> </dependencies>Copy the code


conclusion

The solution of the third step: detect whether there is log JAR package conflict in the system at startup, and manually discharge the package after the conflict

Step 4 solution: Declare all the required log JAR configurations at once without worrying about conflicts


——The End——

If it is useful to you, or you want to continue to pay attention to it, you can search for “Mailu Wuya” in the wechat public account.