实现jul 日志重定向到 slf4j

需求背景

jul 指的是java.util.logging,是 java 内置的日志模块,目前流行的Java日志组件还包括 jcl(common-logging)、slf4j/log4j/logback 等等 
不同日志框架的定位和特性都存在差异,如 jcl、slf4j 提供的是日志门面(api)定义,log4j、logback则侧重于实现。

通常一个团队会采用统一的日志组件,slf4j 目前的受欢迎程度较高,其在易用性、可移植性方面都优于jul; 
然而项目中采用的一些开源组件可能直接采用了jul 进行日志输出,为保证日志的统一配置管理,需将其迁移到slf4j 日志框架上;

关键要求

  1. 不改动现有开源组件代码;
  2. 按需进行迁移,不影响其他模块的 logging 记录;
  3. 模块支持可插拔,可动态集成和撤销;

方案分析

java.util.logging 架构定义如下: 

Logger 以名称(如package) 为标识,Logger之间为树级结构,与log4j类似; 
Handler 接口实现了真正的日志处理,如实现过滤、输出到文件、网络IO..

public abstract class Handler{

    /**
     * Publish a <tt>LogRecord</tt>.
     * <p>
     * The logging request was made initially to a <tt>Logger</tt> object,
     * which initialized the <tt>LogRecord</tt> and forwarded it here.
     * <p>
     * The <tt>Handler</tt>  is responsible for formatting the message, when and
     * if necessary.  The formatting should include localization.
     *
     * @param  record  description of the log event. A null record is
     *                 silently ignored and is not published
     */
    public abstract void publish(LogRecord record); 

}

为实现slf4j 的桥接,考虑以下方法: 
1 定义日志级别映射,将jul level 映射为 slf4j level; 
比如

FINEST/FINER/FINE/=TRACE
CONFIG=DEBUG
INFO=INFO
WARNING=WARN
SEVERE=ERROR

2 自定义jul 的日志handler, 将jul LogRecord 使用slf4j 进行输出; 
3 为避免所有的日志都生成LogRecord对象产生内存浪费,需提前为jul Logger 设置过滤级别;

代码样例

1. LoggerLevel 映射定义

public enum LoggerLevel {

    TRACE(Level.ALL),
    DEBUG(Level.CONFIG),
    INFO(Level.INFO),
    WARN(Level.WARNING),
    ERROR(Level.SEVERE);

    private Level julLevel;

    private LoggerLevel(Level julLevel) {
        this.julLevel = julLevel;
    }

    public Level getJulLevel() {
        return this.julLevel;
    }
}

2. JulLoggerWrapper 实现 Handler

public class JulLoggerWrapper extends Handler {

    // SEVERE > WARNING > INFO > CONFIG > FINE > FINER > FINEST
    // ERROR > WARN > INFO > DEBUG
    private static final int TRACE_LEVEL_THRESHOLD = Level.FINEST.intValue() - 1;
    private static final int DEBUG_LEVEL_THRESHOLD = Level.CONFIG.intValue();
    private static final int INFO_LEVEL_THRESHOLD = Level.INFO.intValue();
    private static final int WARN_LEVEL_THRESHOLD = Level.WARNING.intValue();

    private List<Handler> julHandlers;
    private boolean julUseParentHandlers = false;
    private Level julLevel;
    private Level targetLevel;
    private String name;

    public JulLoggerWrapper(String name) {
        this.name = name;
    }

    /**
     * install the wrapper
     */
    public void install() {
        java.util.logging.Logger julLogger = this.getJulLogger();

        // remove old handlers
        julHandlers = new ArrayList<Handler>();
        for (Handler handler : julLogger.getHandlers()) {
            julHandlers.add(handler);
            julLogger.removeHandler(handler);
        }

        // disable parent handler
        this.julUseParentHandlers = julLogger.getUseParentHandlers();
        julLogger.setUseParentHandlers(false);

        // record previous level
        this.julLevel = julLogger.getLevel();
        if (this.targetLevel != null) {
            julLogger.setLevel(this.targetLevel);
        }

        // install wrapper
        julLogger.addHandler(this);
    }

    /**
     * uninstall the wrapper
     */
    public void uninstall() {
        java.util.logging.Logger julLogger = this.getJulLogger();

        // uninstall wrapper
        for (Handler handler : julLogger.getHandlers()) {
            if (handler == this) {
                julLogger.removeHandler(handler);
            }
        }

        // recover work..
        julLogger.setUseParentHandlers(this.julUseParentHandlers);

        if (this.julLevel != null) {
            julLogger.setLevel(julLevel);
        }

        if (this.julHandlers != null) {
            for (Handler handler : this.julHandlers) {
                julLogger.addHandler(handler);
            }
            this.julHandlers = null;
        }
    }

    private java.util.logging.Logger getJulLogger() {
        return java.util.logging.Logger.getLogger(name);
    }

    private Logger getWrappedLogger() {
        return LoggerFactory.getLogger(name);
    }

    /**
     * 更新级别
     *
     * @param targetLevel
     */
    public void updateLevel(LoggerLevel targetLevel) {
        if (targetLevel == null) {
            return;
        }

        updateLevel(targetLevel.getJulLevel());
    }

    /**
     * 更新级别
     *
     * @param targetLevel
     */
    public void updateLevel(Level targetLevel) {
        if (targetLevel == null) {
            return;
        }

        java.util.logging.Logger julLogger = this.getJulLogger();
        if (this.julLevel == null) {
            this.julLevel = julLogger.getLevel();
        }

        this.targetLevel = targetLevel;
        julLogger.setLevel(this.targetLevel);
    }

    @Override
    public void publish(LogRecord record) {
        // Silently ignore null records.
        if (record == null) {
            return;
        }

        Logger wrappedLogger = getWrappedLogger();
        String message = record.getMessage();

        if (message == null) {
            message = "";
        }

        if (wrappedLogger instanceof LocationAwareLogger) {
            callWithLocationAwareMode((LocationAwareLogger) wrappedLogger, record);
        } else {
            callWithPlainMode(wrappedLogger, record);
        }
    }

    /**
     * get the record‘s i18n message
     *
     * @param record
     * @return
     */
    private String getMessageI18N(LogRecord record) {
        String message = record.getMessage();

        if (message == null) {
            return null;
        }

        ResourceBundle bundle = record.getResourceBundle();
        if (bundle != null) {
            try {
                message = bundle.getString(message);
            } catch (MissingResourceException e) {
            }
        }
        Object[] params = record.getParameters();
        // avoid formatting when 0 parameters.
        if (params != null && params.length > 0) {
            try {
                message = MessageFormat.format(message, params);
            } catch (RuntimeException e) {
            }
        }
        return message;
    }

    private void callWithPlainMode(Logger slf4jLogger, LogRecord record) {

        String i18nMessage = getMessageI18N(record);
        int julLevelValue = record.getLevel().intValue();

        if (julLevelValue <= TRACE_LEVEL_THRESHOLD) {
            slf4jLogger.trace(i18nMessage, record.getThrown());
        } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) {
            slf4jLogger.debug(i18nMessage, record.getThrown());
        } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) {
            slf4jLogger.info(i18nMessage, record.getThrown());
        } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) {
            slf4jLogger.warn(i18nMessage, record.getThrown());
        } else {
            slf4jLogger.error(i18nMessage, record.getThrown());
        }
    }

    private void callWithLocationAwareMode(LocationAwareLogger lal, LogRecord record) {
        int julLevelValue = record.getLevel().intValue();
        int slf4jLevel;

        if (julLevelValue <= TRACE_LEVEL_THRESHOLD) {
            slf4jLevel = LocationAwareLogger.TRACE_INT;
        } else if (julLevelValue <= DEBUG_LEVEL_THRESHOLD) {
            slf4jLevel = LocationAwareLogger.DEBUG_INT;
        } else if (julLevelValue <= INFO_LEVEL_THRESHOLD) {
            slf4jLevel = LocationAwareLogger.INFO_INT;
        } else if (julLevelValue <= WARN_LEVEL_THRESHOLD) {
            slf4jLevel = LocationAwareLogger.WARN_INT;
        } else {
            slf4jLevel = LocationAwareLogger.ERROR_INT;
        }
        String i18nMessage = getMessageI18N(record);
        lal.log(null, java.util.logging.Logger.class.getName(), slf4jLevel, i18nMessage, null,
                record.getThrown());
    }

    @Override
    public void flush() {
        // TODO Auto-generated method stub

    }

    @Override
    public void close() throws SecurityException {
        // TODO Auto-generated method stub

    }

}

3. 集成代码

public class JulRouter {
    private static String loggerName = JulRouter.class.getPackage().getName();
    private static Logger logger = Logger.getLogger(loggerName);
    private static void writeLogs() {
        logger.warning("this the warining message");
        logger.severe("this the severe message");
        logger.info("this the info message");
        logger.finest("this the finest message");
    }
    public static void main(String[] args) {
        Thread.currentThread().setName("JUL-Thread");
        JulLoggerWrapper wrapper = new JulLoggerWrapper(loggerName);
        wrapper.updateLevel(LoggerLevel.DEBUG);
        System.out.println("slf4j print===========");
        wrapper.install();
        writeLogs();
        System.out.println("jul print===========");
        wrapper.uninstall();
        writeLogs();
    }
}

4. log4j,properties 配置

采用slf4j + log4j的方案,在classpath中设置log4j.properties即可

log4j.rootLogger=INFO, console
# simple console log
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}] %p ~ %m%n
## for jul logging
log4j.logger.org.zales.dmo.samples.logging=TRACE,julAppender
log4j.additivity.org.zales.dmo.samples.logging=false
log4j.appender.julAppender=org.apache.log4j.ConsoleAppender
log4j.appender.julAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.julAppender.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}]--[%t] [%p] -%l - %m%n

参考资料

Java Util Logging 组件介绍 
https://www.loggly.com/ultimate-guide/java-logging-basics/ 
Jul API Turturial 
http://www.vogella.com/tutorials/Logging/article.html 
Log4j -Jul 适配器组件 
https://logging.apache.org/log4j/2.0/log4j-jul/

时间: 2024-12-19 19:08:01

实现jul 日志重定向到 slf4j的相关文章

日志门面框架Slf4j

SLF4J的使用简单日志门面(Simple Logging Facade For Java) SLF4J主要是为了给Java日志访问提供一套标准.规范的API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如log4j和logback等.当然slf4j自己也提供了功能较为简单的实现,但是一般很少用到.对于一般的Java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j.logback等),中间使用桥接器完成桥接.官方网站: https://www.

SecurityCRT输出日志重定向

使用CRT进行抓取log,因为工具本省缓冲区有限,导致,刷屏特别快,可能会错过一些log,可以对CRT的log进行增加输出源,或者说将输出到控制台的log再输出到本地文件中: 文件->点击(勾选)日志文件:这个选项代表打出到控制台的日志将会输出到日志文件中: 选项->会话选项->终端->日志文件:指定输出文件路径以及名称: 如此配置,就可以将log输出到指定的文件中了. SecurityCRT输出日志重定向,布布扣,bubuko.com

log日志重定向

1.重定向: 我们在使用NSLog(@"hahaha") 时,实质是将"hahaha"写入到一个系统默认位置的log文件中,然后控制台通过实时获取这个文件的内容进行显示打印信息. 但有时我们希望不需要链接xcode在手机上直接查看日志信息,这个时候我们就需要将log日志的位置调整到我们想要的指定位置. 好处: 这样调整的好处是我们可以自定义一个textview来读取log日志内容,通过某种方式触发(比如摇晃),来将实时的日志直接像是到手机上. 也可以通过后台接口上传

tomcat 日志log4j,slf4j,logback冲突

问题描述: 启动tomcat,发现tomcat无法启动,catalina.out有如下错误日志: INFO [localhost-startStop-1] org.apache.catalina.core.ApplicationContext.log Closing Spring root WebApplicationContext SEVERE [localhost-startStop-1] org.apache.catalina.core.StandardContext.listenerSt

Java日志commons-logging log4j slf4j之间的关系

一.之前进行日志操作一般都是在一个类中加入如下代码: import org.apache.log4j.Logger; //引入的是log4j的包 private static final Logger LOG = Logger.getLogger(Test.class); 二.后来看见别人的代码是这样写的: import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; //引入的是comm

weblogic启动时日志重定向(nohup.out)

由于weblogic使用  nohup ./startWebLogic.sh &   启动时会将所有日志打印到nohup.out上,长此以往会导致该文件越来越大,不便于管理. 故下面介绍如何重定向该日志: 在startWebLogic.sh 或 startManagedWebLogic.sh文件中 #Start Derby# 这一行的上面增加WLS_REDIRECT_LOG重定向日志文件: WLS_REDIRECT_LOG="/weblogic/domain/base_domain/lo

IDEA整合日志框架Log4j2+Slf4j详细配置过程

日志框架这么多,他们之间到底是什么关系呢?笼统的讲就是slf4j是一系列的日志接口,而log4j2.logback是具体实现了接口功能的日志框架.现在的主流日志接口都使用slf4j,而日志的实现就见仁见智了,至于他们的关系请自行百度,此处选择log4j2作为实现框架.网上看到的教程要么对代码没有解释,对新手不友好:要么时间比较久远,跟不上时代.这里使用新版本并结合大量注释,力求简洁明了,有什么问题欢迎留言交流. 运行环境: log4j2 2.8.1 + slf4j 1.7.25 IntelliJ

Java日志框架:slf4j作用及其实现原理

简单回顾门面模式 slf4j是门面模式的典型应用,因此在讲slf4j前,我们先简单回顾一下门面模式, 门面模式,其核心为外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用.用一张图来表示门面模式的结构为: 门面模式的核心为Facade即门面对象,门面对象核心为几个点: 知道所有子角色的功能和责任 将客户端发来的请求委派到子系统中,没有实际业务逻辑 不参与子系统内业务逻辑的实现 大致上来看,对门面模式的回顾到这里就可以了,开始接下来对SLF4J的学习. 我们为什么要使用sl

Java日志记录工具SLF4J介绍

SLF4J是什么 SLF4J是一个包装类,典型的facade模式的工具,对用户呈现统一的操作方式,兼容各种主流的日志记录框架,典型的有log4j/jdk logging/nop/simple/jakarta commons logging等. 有张图比较形象直观的展示这个知识: 下面给出几个maven配置的例子 logback-classic <dependency>   <groupId>ch.qos.logback</groupId>  <artifactId