首页 > 代码库 > 实现jul 日志重定向到 slf4j

实现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/=TRACECONFIG=DEBUGINFO=INFOWARNING=WARNSEVERE=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 =http://www.mamicode.com/ 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 =http://www.mamicode.com/ 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 loglog4j.appender.console=org.apache.log4j.ConsoleAppenderlog4j.appender.console.layout=org.apache.log4j.PatternLayoutlog4j.appender.console.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}] %p ~ %m%n## for jul logginglog4j.logger.org.zales.dmo.samples.logging=TRACE,julAppenderlog4j.additivity.org.zales.dmo.samples.logging=falselog4j.appender.julAppender=org.apache.log4j.ConsoleAppenderlog4j.appender.julAppender.layout=org.apache.log4j.PatternLayoutlog4j.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/

实现jul 日志重定向到 slf4j