需求背景
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

