需求背景

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/

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

  1. 关于log4j、jul、jcl、slf4j等等日志组件的理解

    日志组件: 我们经常在开发项目的时候,需要打印记录项目过程中的一些日志.那我们经常大概会用到 log4j.jul.jcl.slf4j.simple.nop.logback 等等,那我们就详细介绍下这些 ...

  2. springboot日志框架学习------slf4j和log4j2

    springboot日志框架学习------slf4j和log4j2 日志框架的作用,日志框架就是用来记录系统的一些行为的,可以通过日志发现一些问题,在出现问题之后日志是好的一个帮手. 市面上的日志框 ...

  3. log4j日志整合输出(slf4j+commonslog+log4j+jdklogger)

    log4j日志整合输出(slf4j+commonslog+log4j+jdklogger) 博客分类: 日志   J2EE项目中,经常会用到很多第三方的开源组件和软件,这些组件都使用各自的日志组件,比 ...

  4. Qt 之 qInstallMessageHandler(日志重定向至文件)

    Qt 日志重定向到文件 #include <QCoreApplication> #include <QDebug> #include <QMutex> #inclu ...

  5. JUL 日志框架

    1.JUL 简介 JUL 全称 Java Util Logging,位于java.util.logging.Logger 包.它是 java 原生的日志框架,使用时无需另外引用第三方的类库,相对其他的 ...

  6. 001-log-log体系-log4j、jul、jcl、slf4j,日志乱象的归纳与统一

    一.概述 log4j→jul→jcl→slf4j之后就开始百花齐放[slf4j适配兼容新老用户] 1.1.log4j阶段 在JDK出现后,到JDK1.4之前,常用的日志框架是apache的log4j. ...

  7. Java日志框架:SLF4J,Common-Logging,Log4J,Logback说明

    Log4j  Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台.文件.GUI组件.甚至是套接口服务 器.NT的事件记录器.UNIX Syslog守护进程等 ...

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

    由于weblogic使用  nohup ./startWebLogic.sh &   启动时会将所有日志打印到nohup.out上,长此以往会导致该文件越来越大,不便于管理. 故下面介绍如何重 ...

  9. Java日志工具之SLF4J

    SLF4J全称为Simple Logging Facade for Java (简单日志门面),作为各种日志框架的简单门面或者抽象,包括 java.util.logging, log4j, logba ...

随机推荐

  1. [算法] aov图拓扑算法

    #include <stdio.h> #include <string.h> #include <stdlib.h> #include <queue> ...

  2. html5绘图

    html5绘图 这是我在绘图过程中遇到的问题,求助高手帮忙啊... 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ...

  3. 第三方app抽奖发送微信红包

    1.控制器方法: private string SendRedPackge(string OpenId, int Amount, string LuckyCode) { Models.PayWeiXi ...

  4. c#之从服务器下载压缩包,并解压

    项目的配置文件为了和服务器保持一致,每次打包时都从网上下载配置文件,由于下载的是zip压缩包,还需要解压,代码如下: using ICSharpCode.SharpZipLib.Zip; using ...

  5. [iOS]使用signal让app能够在从容崩溃

    前言 虽然大家都不愿意看到程序崩溃,但可能崩溃是每个应用必须面对的现实,既然崩溃已经发生,无法阻挡了,那我们就让它崩也崩得淡定点吧. iOS SDK中提供了一个现成的函数 NSSetUncaughtE ...

  6. [iOS] 响应式编程开发-ReactiveCocoa(一)

    什么是响应式编程 响应式编程是一种面向数据流和变化传播的编程范式.这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播. 例如,在命令式编程环境中 ...

  7. 使用Tomcat搭建图片服务器,使图片能够用链接访问

    在后台和前端交互时,遇到了后台存储的图片,前端根据地址无法访问,使用Tomcat搭建图片服务器 1.找到tomcat下的server.xml文件 2.配置文件下加入service节点 <!--为 ...

  8. [转] 如何让CloudStack使用KVM创建Windows实例成功识别并挂载数据盘

    在使用kvm给windows虚拟机动态挂载virtio类型的硬盘时候遇到问题,通过下面的文章知道需要安装virtio驱动,从而解决问题使挂在正常,在此处mark一下 问题产生背景: 使用CloudSt ...

  9. libusb开发者指南(转)

    源:libusb开发者指南 译者: gashero 作者: Johannes Erdfelt 日期: 2010-04-17 地址: http://libusb.sourceforge.net/doc/ ...

  10. Excel每隔10行取得一个数字

    index(a:a,row(a1)*10) 然后下拉 将一列数字分为好多列 =OFFSET($B$1,(ROW(A1)-1)*11+COLUMN(A1)-1,,) row()返回当前的行,比如A则返回 ...