类加载器隔离朴实案例(二)logback

背景:与类加载器隔离朴实案例【重点】【loadclass yetdone】(一)相同,避免主项目pom中众多log jar包冲突(比如:java日志组件的关系 slf4j logback log4j ),套路还是一样

现成的代码继承:work log

pom -war

  好多log jars与logback.xml等配置文件,jetty/tomcat webclassloader

  我的logback jars,JdbcProxyClassLoader

1 首先,项目pom去除依赖,模拟我们的类加载器与webclassloader完全隔离的场景(因为我本机是springboot的关系,主pom必须移除这些jar,否则会受到双亲委派的干扰);借助maven dependengcy:tree 查看maven依赖树(母项目指定pluginManagement)

[INFO] | +- org.springframework.boot:spring-boot-starter-logging:jar:2.1.8.RELEASE:compile
[INFO] | | +- ch.qos.logback:logback-classic:jar:1.2.3:compile
[INFO] | | | - ch.qos.logback:logback-core:jar:1.2.3:compile
[INFO] | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.11.2:compile
[INFO] | | | - org.apache.logging.log4j:log4j-api:jar:2.11.2:compile
[INFO] | | - org.slf4j:jul-to-slf4j:jar:1.7.28:compile

[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:2.1.8.RELEASE:test
[INFO] | +- org.springframework.boot:spring-boot-test:jar:2.1.8.RELEASE:test
[INFO] | +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.1.8.RELEASE:test
[INFO] | +- com.jayway.jsonpath:json-path:jar:2.4.0:test
[INFO] | | +- net.minidev:json-smart:jar:2.3:test
[INFO] | | | - net.minidev:accessors-smart:jar:1.2:test
[INFO] | | | - org.ow2.asm:asm:jar:5.0.4:test
[INFO] | | - org.slf4j:slf4j-api:jar:1.7.28:compile

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
			<exclusions>
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-logging</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.slf4j</groupId>
					<artifactId>slf4j-api</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

 使MySpringBoot不再依赖slf logback关键词

 2 在自定义类加载器中 加入slf logback相关3个包,并在nativejars放入3个jar包

    static {
        try {
            InputStream inputStream = FakeJdbcDriverClassLoader.class.getResourceAsStream("nativejars/mysql-connector-java-5.1.0-bin.jar");
            InputStream inputStreamSlf4jApi = FakeJdbcDriverClassLoader.class.getResourceAsStream("nativejars/slf4j-api-1.7.25.jar");
            InputStream inputStreamLogbackCore = FakeJdbcDriverClassLoader.class.getResourceAsStream("nativejars/logback-core-1.2.3.jar");
            InputStream inputStreamLogbackClassic = FakeJdbcDriverClassLoader.class.getResourceAsStream("nativejars/logback-classic-1.2.3.jar");
            jdbcDriverClassLoader = new FakeJdbcDriverClassLoader(new JarInputStream[]{
                    new JarInputStream(inputStream),
                    new JarInputStream(inputStreamSlf4jApi),
                    new JarInputStream(inputStreamLogbackCore),
                    new JarInputStream(inputStreamLogbackClassic)});
            jdbcDriverClassLoader.configureLogback();【调用】
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

3 根据从源码来理解slf4j的绑定,以及logback对配置文件的加载考虑logback会找classpath下的logback.xml,可能有很多

spring boot,自定义加载器为系统类加载器子,优先从父加载器,也就是主目录的resources目录找logback.xml,因此可以不用这段代码重置环境

jetty/tomcat,自定义加载器与web加载器平行,会直接报找不到logback,除非再弄个jar,里面就包一个logback.xml

我们需要特异性加载:

    private void configureLogback() throws Exception {
        Class clLoggerFactory = loadClass("org.slf4j.LoggerFactory");
        Method getILoggerFactory = clLoggerFactory.getMethod("getILoggerFactory");
        Object loggerContext = getILoggerFactory.invoke(null);
        Class clLoggerContext = loadClass("ch.qos.logback.classic.LoggerContext");
        Method method = clLoggerContext.getMethod("reset");
        method.invoke(loggerContext);

        Class clJoranConfigurator = loadClass("ch.qos.logback.classic.joran.JoranConfigurator");
        Object configurator = clJoranConfigurator.newInstance();

        URL url = this.getClass().getClassLoader().getResource("scef-logback.xml");【重点】
        method = clJoranConfigurator.getMethod("setContext", loadClass("ch.qos.logback.core.Context"));
        method.invoke(configurator, loggerContext);
        method = clJoranConfigurator.getMethod("doConfigure", URL.class);
        method.invoke(configurator, url);
    }

 考虑到配置的类在我们自定义加载器中,在主环境ide中没有(因为被我们第1条手动干掉了);或有其他不可靠、引起冲突版本在ide中,全程使用反射

scef-logback.xml放到resources下

非反射版本:https://www.cnblogs.com/zhchoutai/p/8522845.html

File logbackFile = new File("./conf/logback.xml");
        if (logbackFile.exists()) {
            LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
            JoranConfigurator configurator = new JoranConfigurator();
            configurator.setContext(lc);
            lc.reset();
            try {
                configurator.doConfigure(logbackFile);
            }
            catch (JoranException e) {
                e.printStackTrace(System.err);
                System.exit(-1);
            }
        }

4 slf-api包装类

package com.example.demo.testcase;

import java.lang.reflect.Method;

/**
 * https://www.cnblogs.com/silyvin/p/12582740.html
 * Created by joyce on 2020/3/28.
 */
public class ScefLogbackFactory {
    public static ScefLogger getLogger(Class c) {
        try {
            Class cl = FakeJdbcDriverClassLoader.getJdbcDriverClassLoader().loadClass("org.slf4j.LoggerFactory");
            Method method = cl.getMethod("getLogger", Class.class);
            Object log = method.invoke(null, c);

            ScefLogger scefLogger = new ScefLogger();
            scefLogger.setLogger(log);
            return scefLogger;

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static class ScefLogger {
        public Object getLogger() {
            return logger;
        }

        public void setLogger(Object logger) {
            this.logger = logger;
        }

        Object logger;

        public void info(String var1) {
            try {
                Class logcl = FakeJdbcDriverClassLoader.getJdbcDriverClassLoader().loadClass("org.slf4j.Logger");
                Method info = logcl.getMethod("info", String.class);
                info.invoke(this.logger, var1);
            } catch (Exception e) {
                throw new RuntimeException(e) ;
            }
        }

        public void info(String var1, Object... var2) {
            try {
                Class logcl = FakeJdbcDriverClassLoader.getJdbcDriverClassLoader().loadClass("org.slf4j.Logger");
                Method info = logcl.getMethod("info", String.class, new Object[0].getClass());
                info.invoke(this.logger, var1, var2);
            } catch (Exception e) {
                throw new RuntimeException(e) ;
            }
        }

        public void debug(String var1, Object... var2) {
            try {
                Class logcl = FakeJdbcDriverClassLoader.getJdbcDriverClassLoader().loadClass("org.slf4j.Logger");
                Method info = logcl.getMethod("debug", String.class, new Object[0].getClass());
                info.invoke(this.logger, var1, var2);
            } catch (Exception e) {
                throw new RuntimeException(e) ;
            }
        }

        public void debug(String var1) {
            try {
                Class logcl = FakeJdbcDriverClassLoader.getJdbcDriverClassLoader().loadClass("org.slf4j.Logger");
                Method info = logcl.getMethod("debug", String.class);
                info.invoke(this.logger, var1);
            } catch (Exception e) {
                throw new RuntimeException(e) ;
            }
        }

        public void warn(String var1, Object... var2) {
            try {
                Class logcl = FakeJdbcDriverClassLoader.getJdbcDriverClassLoader().loadClass("org.slf4j.Logger");
                Method info = logcl.getMethod("warn", String.class, new Object[0].getClass());
                info.invoke(this.logger, var1, var2);
            } catch (Exception e) {
                throw new RuntimeException(e) ;
            }
        }

        public void warn(String var1) {
            try {
                Class logcl = FakeJdbcDriverClassLoader.getJdbcDriverClassLoader().loadClass("org.slf4j.Logger");
                Method info = logcl.getMethod("warn", String.class);
                info.invoke(this.logger, var1);
            } catch (Exception e) {
                throw new RuntimeException(e) ;
            }
        }

        public void error(String var1, Object... var2) {
            try {
                Class logcl = FakeJdbcDriverClassLoader.getJdbcDriverClassLoader().loadClass("org.slf4j.Logger");
                Method info = logcl.getMethod("error", String.class, new Object[0].getClass());
                info.invoke(this.logger, var1, var2);
            } catch (Exception e) {
                throw new RuntimeException(e) ;
            }
        }

        public void error(String var1) {
            try {
                Class logcl = FakeJdbcDriverClassLoader.getJdbcDriverClassLoader().loadClass("org.slf4j.Logger");
                Method info = logcl.getMethod("error", String.class);
                info.invoke(this.logger, var1);
            } catch (Exception e) {
                throw new RuntimeException(e) ;
            }
        }
    }
}

4.22 append:

        void error(String var1, Throwable var2) {
            try {
                Class logcl = FakeJdbcDriverClassLoader.getJdbcDriverClassLoader().loadClass("org.slf4j.Logger");
                Method info = logcl.getMethod("error", String.class, Throwable.class);
                info.invoke(this.logger, var1, var2);
            } catch (Exception e) {
                throw new RuntimeException(e) ;
            }
        }

        void error(Throwable var1) {
            this.error(var1.getMessage(), var1);
        }

5 调用

@Controller
@RequestMapping("/log")
public class LogController {

    private static final ScefLogbackFactory.ScefLogger logger = ScefLogbackFactory.getLogger(LogController.class);
    
    @RequestMapping(value = "/get")
    @ResponseBody
    public String get() {
        logger.info("create");
        logger.info("create {}  df {} {}", "xx", "xx", "xx");
        return "true";
    }
}

4.22 修改

@Controller
@RequestMapping("/log")
public class LogController {

    private static final ScefLogbackFactory.ScefLogger logger = ScefLogbackFactory.getLogger(LogController.class);

    @RequestMapping(value = "/get")
    @ResponseBody
    public String get() {
        logger.info("create");
        logger.info("create {}  df {} {}", "xx", "xx", "xx");
        logger.error("create {}  dferror {} {}", "xxerror", "xxerror", "xxerror");
        try {
            int i = 1/0;
        } catch (Exception e) {
            logger.error(e);
        }
        return "true";
    }
}

6 尽量打包调试,不要ide;避免ide母子兄弟项目所有jar包都在exernal library造成干扰,本次实践,虽然MySringBoot项目经过第1步去除了依赖,但有个平级的兄弟项目MyMain中仍然有引用(java.lang.NoClassDefFoundError类似这种错误catch expection是捕获不到的异常),而且jar包都在exernal library中显示,为避免造成干扰,直接打包后运行

7 在生产级得到验证可行

8 如果log文件没出来,考虑

1)logback.xml没写对,我这一次就是,居然没有fileAppender,只有console,所以只在控制台打出来了;或者有错别字,比如手压到键盘了

2)路径在服务器上没有写入权限

9

4.22日,error info文件分离且跨天调试通过

10 在生产级testcase 打印日志出现问题

自定义类加载的类类型比较类的相同通过对是否为同一个类加载器进行判断中的4

11 日志统一

 参考:从源码来理解slf4j的绑定,以及logback对配置文件的加载

依赖包使用 slf4j接口-log4j实现组合,同时jdk的日志导入slf4j接口

我使用logback

1)

-主pom,所有依赖树枝叶反复mvn dependency:tree 去除slf4j-log4j12,log4j.jar本身由于历史原因可以保留(因为有历史代码直接调用log4j的api)

-添加logback-classic-1.2.3作为slf4j的实现,相当于jetty/tomcat 主服务包括其依赖common包内的 原slf4j api、原jdk jul api调用使用logback打印一个目录

-除了jul-to-slf4j.jar,jul -》 slf4j的重定向还需要:

  SLF4JBridgeHandler.removeHandlersForRootLogger();

  SLF4JBridgeHandler.install()

-classpath下留一个logback.xml,删除多余的,以免引起冲突,虽然一般war直接resources下的logback.xml优先;war依赖于scef,干掉war中的logback.xml,保留scef中的,因为scef要跑testcase也需要这个xml

2)自定义加载器(与jetty/tomcat类加载器平行),使用logback配合setContext打印另一个目录,记录一些自己的key 日志,因为依赖common包中有大量slf4j日志,我们需要自己的key日志

3)本地效果:

4)服务器效果

放到服务器上发现没有1)【common】的,只有2)【key】的,本来还想着在war/resources下面也放一个logback.xml,原来被启动脚本指定了启动参数外部logback.xml路径

cat localhost.log|grep logback

ps -ef|grep xxx也一样

点进去看里面的路径配置,找到文件grep

5)testcase 由于自定义加载器是AppCL的子CL,loadClass("org.slf4j.LoggerFactory");永远会返回主pom(appclassloader)的logback,主pom的slf4j与logback对其可见并被其修改context url,故都打印到2)的目录,具体看第10点

 5.26改为,testcase日志都打到主pom对应的【common】文件夹中,而非2)【key】目录,类的相同通过对是否为同一个类加载器进行判断中的3.半

原文地址:https://www.cnblogs.com/silyvin/p/12582740.html