自行实现的jar包中,日志库的适配实现

​ 日常情况下,我们自己都会自行实现一些基础的jar包,如dao包、service包或一些其他完成特定功能的jar包。如果没有一套调试日志信息,出现问题时想查找问题非常不方便。可能大多数小伙伴都会有自己的一套查找问题的方法。

​ 但在jar包中添加日志信息输出还是有必要的。

​ 我们自己实现的jar包中不能写死使用哪一种日志库输出,必竟现在有好几个比较流行的日志库。jdk中自带有一个,log4j ,logback,apache logging,slf4j等都是效率比较高的日志信息。有这么多可用的日志库,我们完全没有必要自行实现一个日志库。

​ 在自己实现的jar包中,可以实现一个适配,我们将jar提供给别人使用时,别人在使用的过程中如果使用的是slf4j或logback,我们的jar包可以做到自行适配相应的日志库,这种方案是比较完美可行的。

1.实现接口,日志操作接口

public interface Log {
    boolean isDebugEnabled();
    void debug(String msg);
    void debug(String msg, Throwable e);
    boolean isErrorEnabled();
    void error(String msg, Throwable e);
    void error(String msg);
    boolean isInfoEnabled();
    void info(String msg);
    boolean isWarnEnabled();
    void warn(String msg);
    void warn(String msg, Throwable e);
}

​ 与大多数日志库的接口一样,日志输出分为debug,info,error,warn级别,同理也做拉相应级别的日志输出开关。

2.SLF4J日志库的适配实现

public class SLF4JImpl implements Log {

    private static final String callerFQCN = SLF4JImpl.class.getName();
    private static final Logger testLogger = LoggerFactory.getLogger(SLF4JImpl.class);
    static {
        // if the logger is not a LocationAwareLogger instance, it can not get correct stack StackTraceElement
        // so ignore this implementation.
        if (!(testLogger instanceof LocationAwareLogger)) {
            throw new UnsupportedOperationException(testLogger.getClass() + " is not a suitable logger");
        }
    }
    private LocationAwareLogger log;
    public SLF4JImpl(LocationAwareLogger log){
        this.log = log;
    }

    public SLF4JImpl(String loggerName){
        this.log = (LocationAwareLogger) LoggerFactory.getLogger(loggerName);
    }
    public boolean isDebugEnabled() {
        return log.isDebugEnabled();
    }
    public void debug(String msg) {
        log.log(null, callerFQCN, LocationAwareLogger.DEBUG_INT, msg, null, null);
    }
    public void debug(String msg, Throwable e) {
        log.log(null, callerFQCN, LocationAwareLogger.DEBUG_INT, msg, null, e);
    }
    public boolean isErrorEnabled() {
        return log.isErrorEnabled();
    }
    public void error(String msg, Throwable e) {
        log.log(null, callerFQCN, LocationAwareLogger.ERROR_INT, msg, null, e);
    }
    public void error(String msg) {
        log.log(null, callerFQCN, LocationAwareLogger.ERROR_INT, msg, null, null);
    }
    public boolean isInfoEnabled() {
        return log.isInfoEnabled();
    }
    public void info(String msg) {
        log.log(null, callerFQCN, LocationAwareLogger.INFO_INT, msg, null, null);
    }
    public boolean isWarnEnabled() {
        return log.isWarnEnabled();
    }
    public void warn(String msg) {
        log.log(null, callerFQCN, LocationAwareLogger.WARN_INT, msg, null, null);
    }
    public void warn(String msg, Throwable e) {
        log.log(null, callerFQCN, LocationAwareLogger.WARN_INT, msg, null, e);
    }
}

​ 从上述代码可以看到,SLF4J的日志库适配实现,类的内部实现还是以slf4j的内部对象来完成相应的日志输出,与日志级别开关的控制。最重要的一句代码是在静态代码块中,对创建出来的testLogger对象进行对象的比对。

3.日志工厂实现,获取具体的日志操作实现类

public final class LogFactory {
    private static Constructor logConstructor;
    static {
        String logType= System.getProperty("logType");
        if(logType != null){
            if(logType.equalsIgnoreCase("slf4j")){
                tryImplementation("org.slf4j.Logger", "logging.slf4j.SLF4JImpl");
            }
        }
        // 优先选择log4j,而非Apache Common Logging. 因为后者无法设置真实Log调用者的信息
        tryImplementation("org.slf4j.Logger", "logging.slf4j.SLF4JImpl");
        if (logConstructor == null) {
            try {
                logConstructor = NoLoggingImpl.class.getConstructor(String.class);
            } catch (Exception e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
        }
    }
    private static void tryImplementation(String testClassName, String implClassName) {
        if (logConstructor != null) {
            return;
        }

        try {
            Resources.classForName(testClassName);
            Class implClass = Resources.classForName(implClassName);
            logConstructor = implClass.getConstructor(new Class[] { String.class });

            Class<?> declareClass = logConstructor.getDeclaringClass();
            if (!Log.class.isAssignableFrom(declareClass)) {
                logConstructor = null;
            }

            try {
                if (null != logConstructor) {
                    logConstructor.newInstance(LogFactory.class.getName());
                }
            } catch (Throwable t) {
                logConstructor = null;
                //t.printStackTrace();
            }

        } catch (Throwable t) {
           //t.printStackTrace();
        }
    }

    public static Log getLog(Class clazz) {
        return getLog(clazz.getName());
    }

    public static Log getLog(String loggerName) {
        try {
            System.out.println(logConstructor);
            return (Log) logConstructor.newInstance(loggerName);
        } catch (Throwable t) {
            throw new RuntimeException("Error creating logger for logger '" + loggerName + "'.  Cause: " + t, t);
        }
    }
}
  • 使用时,通过LogFactory.getLog方法来获取log对象,进行日志输出。
  • 在静态代码块中,对系统中使用的日志库进行探测,优先选择slf4j日志库。
  • 对于系统中没有匹配到相应的日志,则会适配一个NoLoggingImpl空的日志输出控制对象

4.maven依赖

​ 代码实现需要使用slf4j中的api,所以在pom.xml中需要添加上slf4j包的依赖,但添加上此依赖后,说明我们的jar包就不在是一个比较干净的包实现,因此需要对jar包进行一定范围内的控制。

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.30</version>
    <optional>true</optional>
 </dependency>

​ optional选项,代表的是如果jar依赖者如果显示的调用则会添加上相关的jar依赖,否则不会进行jar的依赖

5.无日志库依赖时测试

​ 外部使用者没有添加任何的日志输出,则不会有相应的jar包来实现

​ 输出结果:

public logging.nologgin.NoLoggingImpl(java.lang.String)

​ 结果中可以查看到,日志库使用的是NoLoggingImpl一个内部自行实现的空的日志输出

6.有日志库依赖时

​ 这里的测试使用的是maven项目,相应的pom文件中,加入logback依赖包。

​ 并且在resource目录下,有相应的logback.xml日志配置文件。

​ 输出结果:

public logging.slf4j.SLF4JImpl(java.lang.String)

​ 结果中可以看到,我们的jar包中,已经自行实现SLF4J日志库的适配

至此一个日志库的自适应适配完成实现 ,代码中只是给出拉SLF4J的日志实现,如果小伙伴们有其他需要可自行实现其他的日志库

相应的代码实现,可以点击以下链接进行下载。

[日志库适配简单实现方式](

posted @ 2020-11-20 16:17  火炎_焱燚  阅读(283)  评论(0编辑  收藏  举报