当前位置:网站首页>Solve the problem that slf4j logs are not printed

Solve the problem that slf4j logs are not printed

2022-06-23 23:37:00 Ask North

The log does not print , It's a headache , It is also a problem we often encounter .

Abnormal logs are found during daily site status patrol , Navigate to the log location , When I look at the user-defined output log of the online document, I find that , The logs you added were not output . After checking, we found that , The log output in this class uses lombok Of @Slf4j The function of annotation , Browse through other classes that use this annotation , The user-defined logs are not printed online . While using LoggerFactory.getLogger(Class<?> clazz) Acquired Logger Object print log , It can print normally online .

Local boot , The use of lombok Of @Slf4j Annotated classes , Logs can also be printed , The test environment can also . It's strange !

Anyone with some experience will guess ,jar Package conflict .

How to verify and solve it ?

maven Dependency tree log

First of all, you can use maven The order of mvn dependency:tree

as follows , take maven Dependency tree output to file , Easy to view and retrieve

mvn dependency:tree > log.txt

You can search log Other keywords , Look at what you've done log Outside the bag , Is there any other version or other logging implementation indirectly introduced through other third-party packages jar( Such as , You used log4j, You rely on a third-party package logback etc. ). In this way, most conflicting dependencies can be found .

Such as , I introduced it zkClient My bag , It depends on log The version is different from mine
 Insert picture description here

I can eliminate it in the following way jar Inside log package . There are other things excluded as well , There is no point enumerating .

<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.4</version>
    <exclusions>
        <exclusion>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
        </exclusion>
    </exclusions>
</dependency>

Slf4j The principle of binding log implementation

This way is actually not so convenient to find , After reading this elder brother's post , Understand the principle of conflict . Probably quote the original text

The system uses SLF4J Frame printing log4j2 Log . View the... Introduced in the system jar Bao found that there were many SLF4J The bridge package for . So eliminate the conflict jar package , Then, when online, all machines print logs normally .

We all know ,SLF4J It's a log facade , The picture below is SLF4J frame 、 Diagram of various log implementations and corresponding bridging packages , From that article .

 Insert picture description here
SLF4J Frame as facade frame , There is no concrete implementation of the log . Instead, it is associated with other specific log implementations , A log is configured in the system to print .

So it's easy to cause jar Package introduction conflict , This results in multiple log implementations . When SLF4J When the log implementation selected by the framework is inconsistent with our configuration , Will not print out the log .

SLF4J When the framework finds multiple log implementations , It will print prompt information . But because it is standard error output , On the console (Tomcat Of catalina.out) Print in 【 When there is no log print in the business log file , You can see catalina.out Is there a hint 】
 Insert picture description here
 Insert picture description here
 Insert picture description here
Because of every SLF4J All bridging packages have org.slf4j.impl.StaticLoggerBinder, and SLF4J Will randomly select one to use . When the selection is the same as the system configuration, you can print the log , Otherwise, it will not print . So there will be some machines printing logs , Some machines may not print logs .
 Insert picture description here

Quickly sense whether there are multiple bridging packets

Just passed maven Depending on how the tree looks with the naked eye , Not very convenient , I understand Slf4j The principle of binding log implementation , We can call it findPossibleStaticLoggerBinderPathSet Method returns Set Collection to get the current number of bridging packets , Then search the specific package name in the log output from the dependency tree .

Specific way :

  1. Realization spring Of BeanFactoryPostProcessor, And leave it to spring management . Ensure that after the system is started , Automatic log conflict verification .
  2. Use reflection to get LoggerFactory Examples and findPossibleStaticLoggerBinderPathSet method .
  3. Judge whether it is abnormal according to the number of bridging packets , Customize the alarm .
  4. According to the alarm information , Search the dependency tree log for , It is indirectly introduced from that dependency , Then arrange the package .
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.URL;
import java.util.Set;

/** *  journal jar Package conflict check  */
@Component
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");
            //  Forced entry 
            method.setAccessible(true);
            Set<URL> staticLoggerBinderPathSet = (Set<URL>)method.invoke(instance);
            if (CollectionUtils.isEmpty(staticLoggerBinderPathSet)) {
    
                handleLogJarConflict(staticLoggerBinderPathSet, "Class path is Empty. Add corresponding log jar package ");
            }
            if (staticLoggerBinderPathSet.size() == 1) {
    
                return;
            }
            handleLogJarConflict(staticLoggerBinderPathSet, "Class path contains multiple SLF4J bindings.  Pay attention to packing ");
        } catch (Throwable t) {
    
            t.getStackTrace();
        }
    }
    /** *  journal jar Packet conflict alarm  * @param staticLoggerBinderPathSet jar Package path  * @param tip  Hint  */
    private void handleLogJarConflict (Set<URL> staticLoggerBinderPathSet, String tip) {
    
        String ip = getLocalHostIp();
        StringBuilder detail = new StringBuilder();
        detail.append("ip by ").append(ip).append(";  The prompt is ").append(tip);
        if (CollectionUtils.isNotEmpty(staticLoggerBinderPathSet)) {
    
            String path = JsonUtils.toJson(staticLoggerBinderPathSet);
            detail.append(";  The duplicate package paths are  ").append(path);
        }
        String logDetail = detail.toString();
		// You can customize the alarm 
        System.out.println("====>"+logDetail);
    }

    private String getLocalHostIp() {
    
        String ip;
        try {
    
            InetAddress addr = InetAddress.getLocalHost();
            ip = addr.getHostAddress();
        } catch (Exception var2) {
    
            ip = "";
        }
        return ip;
    }
}

Once and for all through configuration

The above methods only help us quickly perceive the log jar Packet collision , Manual packing is still required .

Is there a solution , Can help us solve this problem completely ?

The answer is yes. .

About what we need to introduce jar Bags and things that need to be sorted out jar Package declaration to maven At the top of , Declare the packages to be excluded as provided that will do

The solution is to use maven Packet scanning strategy :

  1. Rely on the shortest path first principle ;

  2. The dependent paths are the same , Affirm the principle of priority

When we put all jar After the package is declared as a direct dependency , Will be used preferentially . The package we need to exclude is just declared as provided, You won't break into the bag . Thus, the required packages are subject to our Declaration , Packages that need to be discarded will not be affected by indirect dependencies

<properties>
  <slf4j.version>1.7.7</slf4j.version>
    <logback.version>1.2.3</logback.version>
    <log4j.version>1.2.17</log4j.version>
    <log4j2.version>2.3</log4j2.version>
    <jcl.version>1.2</jcl.version>
</properties>
<dependencies>
    <!-- System use log4j2 As a system log  slf4J As a facade  -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${slf4j.version}</version>
    </dependency>
    <!-- Use log4j2 As an actual log implementation -->
    <dependency>
        <groupId>org.apache.logging.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>
    <!-- take log4j、logback、JCL Of jar Package set to provided, Not in the bag -->
    <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 , Get rid of it log4j2 turn slf4j The bridge package for -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-to-slf4j</artifactId>
        <version>${log4j2.version}</version>
        <scope>provided</scope>
    </dependency>
    <!-- Statement log4j、JCL、JUL turn slf4j The bridge package for , The corresponding log in the code can be converted to 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>
    <!-- Statement slf4j turn SLF4J The bridge package for -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <version>${log4j2.version}</version>
    </dependency>
    <!-- Get rid of it slf4j turn log4j、JCL、JUL turn slf4j Bridge package of bridge package , Prevent log implementation jar Packet collision -->
    <dependency>
        <groupId>org.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>

I didn't do it this way , The reason is that there are too many changes , Too risky . I prefer the above way , Each time a third-party dependency is introduced , Manual detection once , Or use the above method for automatic detection , Then manually eliminate .

Welcome to pay attention to :BiggerBoy
 Insert picture description here


Reference resources :https://blog.csdn.net/zy1817204670/article/details/121154660

原网站

版权声明
本文为[Ask North]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/174/202206232023169511.html