iis 新建网站 没有注册类别,铜仁市网站建设,国家工商核名查询入口,设计上海展会2021时间Java 日志库是最能体现 Java 库在进化中的渊源关系的#xff0c;在理解时重点理解日志框架本身和日志门面#xff0c;以及比较好的时间等。要关注其历史渊源和设计#xff08;比如桥接#xff09;#xff0c;而具体在使用时查询接口即可#xff0c;否则会陷入 JUL#x… Java 日志库是最能体现 Java 库在进化中的渊源关系的在理解时重点理解日志框架本身和日志门面以及比较好的时间等。要关注其历史渊源和设计比如桥接而具体在使用时查询接口即可否则会陷入 JULJava Util LogJCLCommons LoggingLog4jSLF4JLogbackLog4j2 傻傻分不清楚的境地。 1. 日志库简介 理解日志库可以从下面三个角度去理解 最重要的一点是区分日志系统和日志门面其次是日志库的使用包含配置与 API 使用配置侧重于日志系统的配置API 使用侧重于日志门面最后是选型改造和最佳实践等。
2. 日志库之日志系统
2.1 java.util.loggingJUC
JDK1.4 开始通过 java.util.logging 提供日志功能虽然是官方自带的 log libJUL 的使用却不广泛。主要原因
JUL 从 JDK1.4 才开始加入2002 年当时各种第三方 log lib 以及被广泛使用了JUL 早期存在性能问题到 JDK1.5 才有了不错的进步但现在和 Logback/Log4j2 相比还是有所不如JUL 的功能不如 Logback/Log4j2 等完善比如 Output Handler 就没有 Log’back/Log4j2 的丰富有时候需要自己来集成定制又比如默认没有从 ClassPath 里加载配置文件的功能。
2.2 Log4j
Log4j 是 apache 的一个开源项目创始人 Ceki Gulcu。Log4j 应该说是 Java 领域资格最老应用最广的日志工具。Log4j 是高度可配置的并可通过在运行时的外部文件配置。它根据记录的优先级别并提供机制以指示记录信息到许多的目的地注入数据库文件控制台UNIX 系统日志等。
Log4j 中有三个主要组成部分
loggers负责捕获记录信息。appenders负责发布日志信息以不同的首选目的地。layouts负责格式化不同风格的日志信息。
官网地址Apache Log4j :: Apache Log4j
Log4j 的短板在于性能在 Logback 和 Log4j2 出来之后Log4j 的使用也减少了。
2.3 Logback
Logback 是由 log4j 创始人 Ceki Gulcu 设计的又一个开源日志组件是作为 Log4j 的继承者来开发的提供了性能更好的实现异步 loggerFilter 等更多的特性。
logback 当前分成三个模块logback-core、logback-classic 和 logback-access。
logback-core是其它两个模块的基础模块。logback-classic是 log4j 的一个改良版本。此外 logback-classic 完整实现 SLF4J API 使你可以很方便地更换成其它日志系统如 log4j 或 JDK14 Logging。logback-access访问模块与 Servlet 容器集成提供通过 Http 来访问日志的功能。
官网地址Logback Home
2.4 Log4j2
维护 Log4j 的人为了性能又高出了 Log4j2。
Log4j2 和 Log4j1.x 并不兼容设计上很大程度上模仿了 SLF4J/Logback性能上也获得了很大的提升。
Log4j2 也做了 Facade/Implementation 分离的设计分成了 log4j-api 和 log4j-core。
官网地址Apache Log4j :: Apache Log4j
2.5 Log4j vs Logback vs Log4j2 从性能上 Log4j2 要强但从生态上 Logback SLF4J 优先。 初步对比 logback 和 log4j2 都宣称自己是 log4j 的后代一个是出于同一个作者另一个则是在名字上根正苗红。 比较 log4j2 和 logback
log4j2 比 logback 更新log4j2 的 GA 版在 2014 年底才推出比 logback 晚了好几年这期间 log4j2 确实吸收了 slf4j 和 logback 的一些优点比如日志模板同时应用了不少的新技术由于采用了更先进的锁机制和 LMAX Disruptor 库log4j2 的性能优于 logback特别是在多线程环境下和使用异步日志的环境下二者都支持 Filter应该说是 log4j2 借鉴了 logback 的 Filter能够实现灵活的日志记录规则例如仅对一部分用户记录 debug 级别的日志二者都支持对配置文件的动态更新二者都能够适配 slf4jlogback 与 slf4j 的适配应该会更好一些毕竟省掉了一层适配库logback 能够自动压缩/删除旧日志logback 提供了对日志的 HTTP 访问功能log4j2 实现了无垃圾和低垃圾模式。简单地说log4j2 在记录日志时能够重用对象如 String 等尽可能避免实例化新的临时对象减少因日志记录产生的垃圾对象减少垃圾回收带来的性能下降log4j2 和 logback 各有所长 总体来说如果对性能要求比较高的话log4j2 相对还是较优的选择。
性能对比 附上 log4j2 与 logback 性能对比的 benchmark这份 benchmark 是 Apache Logging 出的仅供参考。 同步写文件日志的 benchmark 异步写日志的 benchmark
当然这些 benchmark 都是在日志 Pattern 中不包含 Location 信息如日志代码行号调用者信息Class 名/源码文件名等时测定的如果输出 Location 信息的话性能谁也拯救不了 3. 日志库之日志门面
3.1 common-logging common-logging 是 apache 的一个开源项目。也称 Jakarta Commons Logging缩写 JCL。 common-logging 的功能是提供日志功能的 API 接口本身并不提供日志的具体实现当然common-logging 内部有一个 Simple logger 的简单实现但是功能很弱直接忽略而是在运行时动态绑定日志实现组件来工作如 log4j、java.util.logging。
官网地址Apache Commons Logging – Overview
3.2 slf4j 全称为 Simple Logging Facade for Java即 java 简单日志门面。 作者也是 Ceki Gulcu 类似于 Common-Loggingslf4j 是对不同日志框架提供的一个 API 封装可以在部署的时候不修改配置即可接入一种日志实现方案。但是slf4j 在编译时静态绑定真正的 Log 库。使用 SLF4J 时如果你需要使用某一种日志实现那么你必须选择正确的 SLF4J 的 jar 包的集合各种桥接包。
官网地址SLF4J 3.3 common-logging vs slf4j slf4j 库类似于 Apache Common-Logging。但是它在编译时静态绑定真正的日志库。这点似乎很麻烦其实也不过是导入桥接 jar 包而已。 sjf4j 一大亮点是提供了更方便的日志记录方式
不需要使用 logger.isDebugEnabled() 来解决日志因为字符拼接产生的性能问题。slf4j 的方式是使用 {} 作为字符串替换符形式如下
logger.debug(id:{},name:{}, id, name);4. 日志库使用方案
使用日志解决方案基本可分为三步
引入 jar 包配置使用 API
常见的各种日志解决方案的第 2 步和第 3 步基本一样实施上的差别主要在第 1 步也就是使用不同的库。
4.1 日志库 jar 包
这里首选推荐使用 slf4j logback 的组合。
如果你习惯了 common-logging可以选择 common-logging log4j。
强烈建议不要直接使用日志实现组件logback、log4j、java.util.logging理由前面也说过就是无法灵活替换日志库。
还有一种情况你的老项目使用了 common-logging或是直接使用日志实现组件。如果修改老的代码工作量太大需要兼容处理。在下文都将看到各种应对方法。
slf4j 直接绑定日志组件
slfj loback
添加依赖到 pom.xml 即可。
logback-classic-1.0.13 jar 会自动将 slf4j-api-1.7.21.jar 和 logabck-core-1.0.13.jar 也添加到你的项目中。
dependencygroupIdch.qos.logback/groupIdartifactIdlogback-classic/artifactIdversion1.0.13/version
/dependencyslf4j log4j
添加依赖到 pom.xml 中即可。
slf4j-log4j12-1.7.21.jar 会自动将 slf4j-api-1.7.21.jar 和 log4j-1.2.17.jar 也添加到你的项目中。
dependencygroupIdorg.slf4j/groupIdartifactIdslf4j-log4j12/artifactIdversion1.7.21/version
/dependencyslf4j java.util.logging
添加依赖到 pom.xml 中即可。
slf4j-jdk14-1.7.21.jar 会自动将 slf4j-api-1.7.21.jar 也添加到你的项目中。
dependencygroupIdorg.slf4j/groupIdartifactIdslf4j-jdk14/artifactIdversion1.7.21/version
/dependencyslf4j 兼容非 slf4j 日志组件
在介绍解决方案前先提一个概念 一一 桥接
什么是桥接
假如你正在开发应用程序所调用的组件当中已经使用了 common-logging这时你需要 jcl-over-slf4j.jar 把日志信息输出重定向到 slf4j-apislf4j-api 再去调用 slf4j 实际依赖的日志组件。这个过程称为桥接。下图是官方的 slf4j 桥接策略图 从图中应该可以看出无论你的老项目中使用的是 common-logging 或是直接使用 log4j、java.util.logging都可以使用对应的桥接 jar 包来解决兼容问题。
slf4j 兼容 common-logging
dependencygroupIdorg.slf4j/groupIdartifactIdjcl-over-slf4j/artifactIdversion1.7.12/version
/dependencyslf4j 兼容 log4j
dependencygroupIdorg.slf4j/groupIdartifactIdlog4j-over-slf4j/artifactIdversion1.7.12/version
/dependencyslf4j 兼容 java.util.logging
dependencygroupIdorg.slf4j/groupIdartifactIdjul-to-slf4j/artifactIdversion1.7.12/version
/dependencyspring 集成 slf4j
做 java web 开发基本离不开 spring 框架。很遗憾spring 使用的日志解决方案是 common-logging log4j。
所以你需要一个桥接 jar 包logback-ext-spring。
dependencygroupIdch.qos.logback/groupIdartifactIdlogback-classic/artifactIdversion1.1.3/version
/dependency
dependencygroupIdorg.logback-extensions/groupIdartifactIdlogback-ext-spring/artifactIdversion0.1.2/version
/dependency
dependencygroupIdorg.slf4j/groupIdartifactIdjcl-over-slf4j/artifactIdversion1.7.12/version
/dependencycommon-logging 绑定日志组件
common-logging log4j
dependencygroupIdcommons-logging/groupIdartifactIdcommons-logging/artifactIdversion1.2/version
/dependency
dependencygroupIdlog4j/groupIdartifactIdlog4j/artifactIdversion1.2.17/version
/dependency4.2 日志库配置 - 针对于日志框架
log4j2 配置
log4j2 基本配置形式如下
?xml version1.0 encodingUTF-8?
ConfigurationPropertiesProperty namename1value/PropertyProperty namename2 valuevalue2//PropertiesFilter typetype ... /AppendersAppender typetype namenameFilter typetype ... //Appender.../AppendersLoggersLogger namename1Filter typetype ... //LoggerRoot levellevelAppenderRef refname//Root/Loggers
/Configuration 配置示例
?xml version1.0 encodingUTF-8 ?
ConfigurationPropertiesProperty namefilenametarget/test.log/Property/Properties!-- 配置全局过滤器只记录 trace 及以上级别的日志 --Filte typeThresholdFilter leveltrace/!-- 定义日志输出方式 --Appenders!-- 控制台输出 --Appender typeConsole nameSTDOUT!-- 日志格式 --Layout typePatternLayout pattern%m MDC%X%n/!-- 过滤器拒绝标记为 FLOW 的日志接收标记为 EXCEPTION 的日志 --FiltersFilter typeMarkerFilter markerFLOW onMatchDENY onMismaatchNEUTRAL/Filter typeMarkerFilter markerEXCEPTION onMatchDENY onMismaatchACCEPT//Filters/AppenderAppender typeConsole nameFLOWLayout typePatternLayout pattern%C{1}.%M %m %ex%n/FiltersFilter typeMarkerFilter markerFLOW onMatchACCEPT onMismaatchNEUTRAL/Filter typeMarkerFilter markerEXCEPTION onMatchACCEPT onMismaatchDENY//Filters/Appender!-- 文件输出 --Appender typeFile nameFile fileName${filename}!-- 日志格式 --Layout typePatternLayoutPattern%d %p $C{1.} [%t] %m%n/Pattern/Layout/Appender/Appenders!-- 定义日志记录器 --Loggers!-- 名为 org.apache.logging.log4j.test1 的日志记录器 --Logger nameorg.apache.logging.log4j.test1 levelDEBUG additivityFALSE!-- 过滤器只有当 ThreadContext 中包含键值对 test123 时才记录日志 --Filter typeThreadContextMapFilterKeyValuePair keytest value123//Filter/Logger!-- 名为 org.apache.logging.log4j.test2 的日志记录器 --Logger nameorg.apache.logging.log4j.test2 levelDEBUG additivityFALSE !--5--!-- 引用名为 file 的 Appender --AppenderRef reffile//Logger!-- 根日志记录器所有未指定记录器的日志都会到这里 --Root leveltrace!-- 引用名为 STDOUT 的 Appender --AppenderRef refSTDOUT//Root/Loggers
/ConfigurationLogback 配置
?xml version1.0 encodingUTF-8 ?
!-- logback 中一共有 5 种有效级别分别是 TRACE、DEBUG、INFO、WARN、ERROR优先级依次从低到高 --
configuration scantrue scanPeiriod30 seconds debugfalseproperty nameDIR_NAME valuespring/!-- 将记录日志打印到控制台 --appender nameSTDOUT classch.qos.logback.core.ConsoleAppenderencoderpattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n/pattern/encoder/appender!-- RollingFilterAppender begin --appender nameALL classch.qos.logback.core.rolling.RollingFileAppender!-- 根据时间来制定滚动策略 --rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicyfileNamePattern${user.dir}/logs/${DIR_NAME}/all.%d{yyyy-MM-dd}.log/fileNamePatternmaxHistory30/maxHistory/rollingPolicy!-- 根据文件大小来制定滚动策略 --trggeringPolicy classch.qos.logback.core.rolling.SizeBasedTriggeringPolicymaxFileSize30MB/maxFileSize/trggeringPolicyencoderpattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n/pattern/encoder/appenderappender nameERROR classch.qos.logback.core.rolling.RollingFileAppender!-- 根据时间来制定滚动策略 --rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicyfileNamePattern${user.dir}/logs/${DIR_NAME}/error.%d{yyyy-MM-dd}.log/fileNamePatternmaxHistory30/maxHistory/rollingPolicy!-- 根据文件大小来制定滚动策略 --trggeringPolicy classch.qos.logback.core.rolling.SizeBasedTriggeringPolicymaxFileSize10MB/maxFileSize/trggeringPolicyfilter classch.qos.logback.classic.filter.LevelFilterlevelERROR/levelonMatchACCEPT/onMatchonMismatchDENY/onMismatch/filterencoderpattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n/pattern/encoder/appenderappender nameWARN classch.qos.logback.core.rolling.RollingFileAppender!-- 根据时间来制定滚动策略 --rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicyfileNamePattern${user.dir}/logs/${DIR_NAME}/warn.%d{yyyy-MM-dd}.log/fileNamePatternmaxHistory30/maxHistory/rollingPolicy!-- 根据文件大小来制定滚动策略 --trggeringPolicy classch.qos.logback.core.rolling.SizeBasedTriggeringPolicymaxFileSize10MB/maxFileSize/trggeringPolicyfilter classch.qos.logback.classic.filter.LevelFilterlevelWARN/levelonMatchACCEPT/onMatchonMismatchDENY/onMismatch/filterencoderpattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n/pattern/encoder/appenderappender nameINFO classch.qos.logback.core.rolling.RollingFileAppender!-- 根据时间来制定滚动策略 --rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicyfileNamePattern${user.dir}/logs/${DIR_NAME}/info.%d{yyyy-MM-dd}.log/fileNamePatternmaxHistory30/maxHistory/rollingPolicy!-- 根据文件大小来制定滚动策略 --trggeringPolicy classch.qos.logback.core.rolling.SizeBasedTriggeringPolicymaxFileSize10MB/maxFileSize/trggeringPolicyfilter classch.qos.logback.classic.filter.LevelFilterlevelINFO/levelonMatchACCEPT/onMatchonMismatchDENY/onMismatch/filterencoderpattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n/pattern/encoder/appenderappender nameDEBUG classch.qos.logback.core.rolling.RollingFileAppender!-- 根据时间来制定滚动策略 --rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicyfileNamePattern${user.dir}/logs/${DIR_NAME}/debug.%d{yyyy-MM-dd}.log/fileNamePatternmaxHistory30/maxHistory/rollingPolicy!-- 根据文件大小来制定滚动策略 --trggeringPolicy classch.qos.logback.core.rolling.SizeBasedTriggeringPolicymaxFileSize10MB/maxFileSize/trggeringPolicyfilter classch.qos.logback.classic.filter.LevelFilterlevelDEBUG/levelonMatchACCEPT/onMatchonMismatchDENY/onMismatch/filterencoderpattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n/pattern/encoder/appenderappender nameSPRING classch.qos.logback.core.rolling.RollingFileAppender!-- 根据时间来制定滚动策略 --rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicyfileNamePattern${user.dir}/logs/${DIR_NAME}/springframework.%d{yyyy-MM-dd}.log/fileNamePatternmaxHistory30/maxHistory/rollingPolicy!-- 根据文件大小来制定滚动策略 --trggeringPolicy classch.qos.logback.core.rolling.SizeBasedTriggeringPolicymaxFileSize10MB/maxFileSize/trggeringPolicyencoderpattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n/pattern/encoder/appender!-- RollingFilterAppender end--!-- logger begin --!-- 本项目的日志记录分级打印 --logger nameio.zhanbo.log levelTRACE additivityfalseappender-ref refSTDOUT/appender-ref refERROR/appender-ref refWARN/appender-ref refINFO/appender-ref refDEBUG/appender-ref refTRACE//logger!-- SPRING 框架日志 --logger nameorg.springframework levelWARN additivityfalseappender-ref refSPRING//loggerlogger levelTRACEappender-ref refALL//logger!-- logger end --
/configurationlog4j 配置
完整的 log4j.xml 参考示例
?xml version1.0 encodingUTF-8?
!DOCTYPE log4j:configuration SYSTEM log4j.dtdlog4j:configuration xmlns:log4jhttp://jakarta.apache.org/log4j/appender nameSTDOUT classorg.apache.log4j.ConsoleAppenderlayout classorg.apache.log4j.PatternLayoutparam nameConversionPatternvalue%d{yyyy-MM-dd HH:mm:ss,SSS\} [%-5p] [%t] %c{36\}.%M - %m%n//layout!--过滤器设置输出的级别--filter classorg.apache.log4j.varia.LevelRangeFilterparam namelevelMin valuedebug/param namelevelMax valuefatal/param nameAcceptOnMatch valuetrue//filter/appenderappender nameALL classorg.apache.log4j.DailyRollingFileAppenderparam nameFile value${user.dir}/logs/spring-common/jcl/all/param nameAppend valuetrue/!-- 每天重新生成日志文件 --param nameDatePattern value-yyyy-MM-dd.log/!-- 每小时重新生成日志文件 --!--param nameDatePattern value-yyyy-MM-dd-HH.log/--layout classorg.apache.log4j.PatternLayoutparam nameConversionPatternvalue%d{yyyy-MM-dd HH:mm:ss,SSS\} [%-5p] [%t] %c{36\}.%M - %m%n//layout/appender!-- 指定logger的设置additivity指示是否遵循缺省的继承机制--logger nameio.zhanbo.log additivityfalselevel valueerror/appender-ref refSTDOUT/appender-ref refALL//logger!-- 根logger的设置--rootlevel valuewarn/appender-ref refSTDOUT//root
/log4j:configuration4.3 日志库 API - 针对于日志门面
slf4j 用法
使用 slf4j 的 API 很简单。使用LoggerFactory 初始化一个 Logger 实例然后调用 Logger 对应的打印等级函数就行了。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class Test {private static final Logger logger LoggerFactory.getLogger(Test.class);public static void main(String[] args) {String msg print log, current level: {};logger.trace(msg, trace);logger.debug(msg, debug);logger.info(msg, info);logger.warn(msg, warn);logger.error(msg, error);}
}common-logging 用法
common-logging 用法和 slf4j 几乎一样但是支持的打印等级多了一个更高的级别fatal。
此外common-logging 不支持 {} 替换参数你只能选择拼接字符串这种方式了。
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;public class Test {private static final Log log LogFactory.getLog(Test.class);public static void main(String[] args) {String msg print log, current level;log.trace(msg trace);log.debug(msg debug);log.info(msg info);log.warn(msg warn);log.error(msg error);log.fatal(msg fatal);}
}5. 日志库选型与改造
5.1 对 Java 日志组件选型的建议
slf4j 已经成为了 Java 日志组件的明星选手可以完美代替 JCL使用 JCL 桥接库也能完美兼容一切使用 JCL 作为日志门面的类库现在的新系统已经没有不使用 slf4j 作为日志 API 的理由了。
日志记录服务方面log4j 在功能上输于 logback 和 log4j2在性能方面 log4j2 则全面超越 log4j 和 logback。所以新系统应该在 logback 和 log4j2 中做出选择对于性能有很高要求的系统应优先考虑 log4j2。
5.2 对日志架构使用比较好的实践
总是使用 Log Facade而不是具体 Log Implementation
正如之前所说的使用 Log Facade 可以方便的切换具体的日志实现。而且如果依赖多个项目使用了不同的 Log Facade还可以方便的通过 Adapter 转接到同一个实现上。如果依赖项目使用了多个不同的日志实现就麻烦的多了。
具体来说现在推荐使用 Log4j-API 或者 SLF4J不推荐继续使用 JCL。
只添加一个 Log Implementation 依赖
毫无疑问项目中应该只使用一个具体的 Log Implementation建议使用 Logback 或者 Log4j2。 如果有依赖的项目中使用的 Log Facade 不支持直接使用当前的 Log Implementation就添加合适的桥接器依赖。具体的桥接关系可以看上一节的图。
具体的日志实现依赖应该设置为 optional 和使用 runtime scope
在项目中Log Implementation 的依赖强烈建议设置为 runtime scope并且设置为 optional。例如项目中使用了 SLF4J 作为 Log Facade然后想使用 Log4j2 作为 Implementation那么使用 maven 添加依赖的时候这样设置
dependencygroupIdorg.apache.logging.log4j/groupIdartifactIdlog4j-core/artifactIdversion${log4j.version}/versionscoperuntime/scopeoptionaltrue/optional
/dependency
dependencygroupIdorg.apache.logging.log4j/groupIdartifactIdlog4j-slf4j-impl/artifactIdversion${log4j.version}/versionscoperuntime/scopeoptionaltrue/optional
/dependency 设为 optional依赖不会传递这样如果你是个 lib 项目然后别的项目使用了你这个 lib不会被引入不想要的 Log Implementation 依赖
Scope 设置为 runtime是为了防止开发人员在项目中直接使用 Log Implementation 中的类而不使用 Log Facade 中的类。
如果有必要排除依赖的第三方库中的 Log Impementation 依赖
这是很常见的一个问题第三方库的开发者未必会把具体的日志实现或者桥接器的依赖设置为 optional然后你的项目继承了这些依赖 一一 具体的日志实现未必是你想使用的比如他依赖的 Log4j你想使用 Logback这时就很尴尬。另外如果不同的第三方依赖使用了不同的桥接器和 Log 实现也极容易形成环。
这种情况下推荐的处理方法是使用 exclude 来排除所有的这些 Log 实现和桥接器的依赖只保留第三方库里面对 Log Facade 的依赖。
比如阿里的 JStorm 就没有很好的处理这个问题依赖 jstorm 会引入对 Logback 和 log4j-over-slf4j 的依赖如果你想在自己的项目中使用 Log4j 或者其它 Log 实现的话就需要加上 excludes
dependencygroupIdcom.alibaba.jstorm/groupIdartifactIdjstorm-core/artifactIdversion2.1.1/versionexclusionsexclusiongroupIdorg.slf4j/groupIdartifactIdlog4j-over-slf4j/artifactId/exclusionexclusiongroupIdch.qos.logback/groupIdartifactIdlogback-classic/artifactId/exclusion/exclusions
/dependency避免为不会输出的 log 付出代价
Log 库都可以灵活的设置输出界面所以每一条程序中的 log都是有可能不会被输出的。这时候要注意不要额外的付出代价。
先看两个有问题的写法
logger.debug(start process request, url: url);
logger.debug(receive request: {}, toJson(request)); 第一条是直接做了字符串拼接所以即使日志级别高于 debug 也会做一个字符串连接操作第二条虽然用了 SLF4J/Log4j2 中的懒求值方式来避免不必要的字符串拼接开销但是 toJson() 这个函数却是都会被调用并且开销很大。
推荐的写法如下
logger.debug(start process request, url:{}, url); // SLF4J/LOG4J2
logger.debug(receive request: {}, () - toJson(request)); // LOG4J2
logger.debug(() - receive request: toJson(request)); // LOG4J2
if (logger.isDebugEnabled()) { // SLF4J/LOG4J2logger.debug(receive request: toJson(request));
}日志格式中最好不要使用行号函数名等字段
原因是为了获取语句所在的函数名或者行号log 库的实现都是获取当前的 stacktrace然后分析取出这些信息而获取 stacktrace 的代价是很昂贵的。如果有很多的日志输出就会占用大量的 CPU。在没有特殊需要的情况下建议不要在日志中输出这些字段。
最后log 中不要输出稀奇古怪的字符
部分开发人员为了方便看到自己的 log会在 log 语句中加上醒目的前缀比如
logger.debug(start process request); 虽然对于自己来说是方便了但是如果所有人都这样来做的话那 log 输出就没法看了正确的做法是使用 grep 来看自己只关心的日志。
5.3 对现有系统日志架构的改造建议
如果现有系统使用 JCL 作为日志门面又确实面临着 JCL 的 ClassLoader 机制带来的问题完全可以引入 slf4j 并通过桥接库将 JCL api 输出的日志桥接至 slf4j再通过适配库配置现有的日志输出服务如 log4j如下图 这样做不需要任何代码级的改造就可以解决 JCL 的 ClassLoader 带来的问题但没有办法享受日志模板等 slf4j 的 api 带来的优点。不过之后在现有系统上开发的新功能就可以使用 slf4j 的 api 了老代码也可以分批进行改造。
如果现有系统使用 JCL 作为日志门面又头疼 JCL 不支持 logback 和 log4j2 等新的日志服务也可以通过桥接库以 slf4j 代替 JCL但同样无法直接享受 slf4j api 的优点。
如果想要使用 slf4j 的 api那么就不得不进行代码改造了当然改造也可以参考1中提到的方法逐步进行。
如果现有系统面临着 log4j 的性能问题可以使用 Apache Logging 提供的 log4j 到 log4j2 的桥接库 log4j-1.2-api把通过 log4j api 输出的日志桥接至 log4j2.这样可以最快地使用上 log4j2 的先进性能但组件中缺失了 slf4j对后续进行日志架构改造的灵活性有影响。另一种办法是先把 log4j 桥街知 slf4j再使用 slf4j 到 log4j2 的适配库。这样做稍微麻烦了一点但可以逐步将系统中的日志输出标准化为使用 slf4j 的 api为后面的工作打好基础。