需求背景
jul 指的是java.util.logging,是 java 内置的日志模块,目前流行的Java日志组件还包括 jcl(common-logging)、slf4j/log4j/logback 等等
不同日志框架的定位和特性都存在差异,如 jcl、slf4j 提供的是日志门面(api)定义,log4j、logback则侧重于实现。
通常一个团队会采用统一的日志组件,slf4j 目前的受欢迎程度较高,其在易用性、可移植性方面都优于jul;
然而项目中采用的一些开源组件可能直接采用了jul 进行日志输出,为保证日志的统一配置管理,需将其迁移到slf4j 日志框架上;
关键要求
-
不改动现有开源组件代码;
-
按需进行迁移,不影响其他模块的 logging 记录;
- 模块支持可插拔,可动态集成和撤销;
方案分析
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