关键词:
需求背景
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
参考资料
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/
将 SLF4J 日志重定向到 JavaFX 中的 TextArea
】将SLF4J日志重定向到JavaFX中的TextArea【英文标题】:RedirectingSLF4JlogtoTextAreainJavaFX【发布时间】:2017-01-0722:28:26【问题描述】:我想在JavaFX的TextArea中显示SLF4J记录的错误。到目前为止,我在logback-test.xml中有一个appender:<appender... 查看详情
日志技术-java原生日志实现jul
...念二、Java日志框架三、JUL日志框架四、JUL日志框架代码实现1、默认配置日志输出2、直接对应日志级别输出3、自定义编码形式日志输出4、Logger对象的父子关系5、加载自定义配置文件五、JUL日志配置文件1、JDK默认的日志配置文... 查看详情
利用logback+slf4j日志采集整合到sba
...只输出某些包打的日志......logback是一个日志采集系统的实现;slf4j是日志系统的接口,如同jdbc一样定义了一套接口,是一个日志门面可实现多个日志系统间快速切换。logback基础功能包含三个组成部分:Loggers(日志记录器)、Lay... 查看详情
springboot使用日志(代码片段)
1、日志框架日志门面日志实现JCL、SLF4J、jboss-loggingLog4j、JUL、Log4j2、Logback日志门面:SLF4J日志实现:LogbackSpringBoot:底层是Spring框架,Spring框架默认是用JCL;SpringBoot选用SLF4J和Logback.2、SLF4J使用以后开发的时候,日志记录。日志... 查看详情
日志管理,springboot
...gging、logback、log4j、log4j2、slf4j....2.日志门面:SLF4J;日志实现:Logback;SpringBoot:底层是Spring框架,Spring框架默认是用JCL;‘SpringBoot选用SLF4j和logback;3.SLF4j使用3.1如何在系统中使用SLF4j https://www.slf4j. 查看详情
java项目日志系统的总结(代码片段)
...行抽象的接口(类似于JDBC驱动)logback:是基于slf4j接口进行实现的一个具体的日志系统log4j2:同样是基于slf4j接口实现的一个具体日志系统,该系统使用了disruptor库,在异步的情况下,性能提高很多日志的三个组件Logger一个日志系统... 查看详情
qt重定向qdebug,实现日志系统(qtdebugmsgqtinfomsgqtwarningmsgqtcriticalmsgqtfatalmsg)(代码片段)
原理:重定向qDebug、qInfo、qWarning、qCritical、qFatal等宏,输出到txt文件。如果需要输出到Qt控件上,则需要使用Qt提供的反射机制。效果图:目录结构如下:重点关注:qInstallMessageHandler()QMetaObject::i... 查看详情
将 Azure 日志重定向到特定的日志服务
】将Azure日志重定向到特定的日志服务【英文标题】:RedirectingAzurelogstoaparticularlogservice【发布时间】:2016-10-1718:11:10【问题描述】:我有一些在Azure服务上运行的VM。我想将日志从它们(Windows事件日志和MSSQL服务器日志)重定向... 查看详情
如何将 TensorFlow 日志重定向到文件?
】如何将TensorFlow日志重定向到文件?【英文标题】:HowtoredirectTensorFlowloggingtoafile?【发布时间】:2017-03-2608:58:30【问题描述】:我正在使用TensorFlow-Slim,它有一些有用的日志记录由tf.logging打印到控制台。我想将这些日志重定向... 查看详情
logback源码分析(代码片段)
..."Greizmann");本文以Logback日志框架来分析以上代码的实现。slf4j如今日志框架常用的有log4j、log4j2、jul(common-log)以及logback。假如项目中用的是jul,如今想改成用log4j,如果直接引用java.util.logging包中Logger,需要修改大量代码,... 查看详情
将 aws lambda 日志重定向到 cloudwatch 中的特定日志组
】将awslambda日志重定向到cloudwatch中的特定日志组【英文标题】:redirectawslambdalogstoaparticularloggroupincloudwatch【发布时间】:2018-02-1612:34:51【问题描述】:我有多个lambda。有没有办法将所有这些lambda中的日志定向到特定的云监视日... 查看详情
java重定向输出流实现程序日志
publicclassRedirectOutputStream{ publicstaticvoidmain(String[]args){try{PrintStreamout=System.out; //保存原输出流PrintStreamps=newPrintS 查看详情
从 bash 脚本本身将 stdout 的副本重定向到日志文件
】从bash脚本本身将stdout的副本重定向到日志文件【英文标题】:redirectCOPYofstdouttologfilefromwithinbashscriptitself【发布时间】:2011-03-1112:42:56【问题描述】:我知道如何重定向标准输出到一个文件:exec>foo.logechotest这会将“测试”... 查看详情
重定向输出流实现程序日志
实例说明: System类中的out成员变量是Java的标准输出流,程序中经常用他来输出调试信息。out成员变量被定义成final类型的,无法直接重新复制,但是可以通过setOut()方法来设置新的输出流。1packagecom.zeone.lifeline.test;23importjava.io.... 查看详情
在 slf4j 运行时设置消息的日志级别
】在slf4j运行时设置消息的日志级别【英文标题】:Settingloglevelofmessageatruntimeinslf4j【发布时间】:2011-02-0623:31:23【问题描述】:使用log4j时,Logger.log(Priorityp,Objectmessage)方法可用,可用于在运行时确定的日志级别记录消息。我们... 查看详情
Swift - 将控制台日志重定向到文档文件夹
】Swift-将控制台日志重定向到文档文件夹【英文标题】:Swift-RedirectConsoleLogToDocumentFolder【发布时间】:2015-12-0117:09:49【问题描述】:我正在寻找以下ObjectiveC通用代码的Swift等效。在ObjectiveC中,我们将日志重定向到文档文件夹而... 查看详情
在进程完成之前将日志重定向到文件[重复]
】在进程完成之前将日志重定向到文件[重复]【英文标题】:Redirectlogtofilebeforeprocessfinished[duplicate]【发布时间】:2016-10-1601:29:03【问题描述】:test.py(工作):importtime_,a,b=[1,2,3]printaprintb运行代码:pythontest.py>test.log您将在test.... 查看详情
日志框架(代码片段)
1、市面上的日志框架JUL、JCL、Jboss-logging、logback、log4j、log4j2、slf4j....日志门面(日志的抽象层)日志实现JCL(JakartaCommonsLogging)SLF4j(SimpleLoggingFacadeforJava)jboss-loggingLog4jJUL(java.util.logging)Log4j2Logback &nbs 查看详情