近期做一个项目打造项目的日志系统时,发现没有一个系统的学习,故准备系统学习一下日志系统,这主要是介绍了如何打造一个项目的日志系统。喜欢的话 点个赞 呗~
java 界里有许多实现日志功能的工具,最早得到广泛使用的是 log4j,许多应用程序的日志部分都交给了 log4j,不过作为组件开发者,他们希望自己的组件不要紧紧依赖某一个工具,毕竟在同一个时候还有很多其他很多日志工具,假如一个应用程序用到了两个组件,恰好两个组件使用不同的日志工具,那么应用程序就会有两份日志输出了。
为了解决这个问题,JCL和SLF4j就出现了,JCL只提供 log 接口,具体的实现则在运行时动态寻找。这样一来组件开发者只需要针对JCL或者slf4j的接口开发,而调用组件的应用程序则可以在运行时搭配自己喜好的日志实践工具。跟 JCL 一样,SLF4J 也是只提供 log 接口,具体的实现是在打包应用程序时所放入的绑定器(名字为 slf4j-XXX-version.jar)来决定,XXX 可以是 log4j12, jdk14, jcl, nop 等,他们实现了跟具体日志工具(比如 log4j)的绑定及代理工作。举个例子:如果一个程序希望用 log4j 日志工具,那么程序只需针对 slf4j-api 接口编程,然后在打包时再放入 slf4j-log4j12-version.jar 和 log4j.jar 就可以了。
项目中我们选择了SLF4j+Log4j2来打造日志系统,log4j2的性能还是比Logback好一些的,下面有对比。
如果转载此博文,请附上本文链接:https://blog.csdn.net/CSDN___LYY/article/details/84394244 谢谢合作~
首先我们应该先删除项目已经依赖的其他日志组件,这里指的是没有用到的日志组件,例如janusgraph会间接依赖log4j1的组件,这个组件删除就会报错,所以我们只要删除没有使用的日志组件,这样可以使项目更加干净~
方法:我们可以观察项目目录下的External Libraries下的依赖文件,如果有log4j1或者其他日志依赖,我们将他们在pom文件中找到删除即可。
如果依赖中有但是pom文件中找不到,就是被间接依赖进来的了,我们在pom 文件中右击鼠标,选中Diagrams->show dependences就可以看到整个项目的依赖图,在其中找到对应的log依赖,选中右击Exclude即可。
添加的所有依赖都是截止2018.11.22日最新的稳定版本
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.25version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>jcl-over-slf4jartifactId>
<version>1.7.25version>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-apiartifactId>
<version>2.11.1version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>2.11.1version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-webartifactId>
<version>2.11.1version>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-slf4j-implartifactId>
<version>2.11.1version>
dependency>
上述的jcl-over-scf4j的作用以及原因: 即使现在你仍会看到很多程序应用 JCL + log4j 这种搭配,不过当程序规模越来越庞大时,JCL的动态绑定并不是总能成功,具体原因大家可以 Google 一下,这里就不再赘述了。解决方法之一就是在程序部署时静态绑定指定的日志工具,这也是 SLF4J 产生的原因。
现在还有一个问题,假如你正在开发应用程序所调用的组件当中已经使用了 JCL 的,还有一些组建可能直接调用了 java.util.logging,这时你需要一个桥接器(名字为 XXX-over-slf4j.jar)把他们的日志输出重定向到 SLF4J,所谓的桥接器就是一个假的日志实现工具,比如当你把 jcl-over-slf4j.jar 放到 CLASS_PATH 时,即使某个组件原本是通过 JCL 输出日志的,现在却会被 jcl-over-slf4j “骗到”SLF4J 里,然后 SLF4J 又会根据绑定器把日志交给具体的日志实现工具,这样就可以实现日志的统一了。如果你的项目没有使用jcl那么就不必添加这个。
上述的log4j-web是在开发web项目的时候需要的,如果你不是web项目,可以酌情删除
在类路径下新建文件:log4j2.xml ,注意“2”不要缺少,位置放的正确并且文件名符合要求的话,项目会自动扫描到该配置文件。
Log4j2能够在初始化期间自动配置自身。当Log4j2启动时,它将找到所有ConfigurationFactory插件并按加权顺序从最高到最低排列。在交付时,Log4j包含四个ConfigurationFactory实现:一个用于JSON,一个用于YAML,一个用于 properties,一个用于XML,下面为查找加载顺序:
log4j2.xml 文件内容:
<Configuration status="INFO" monitorInterval="30">
<properties>
<property name="logPath">/opt/logs/hrmapp/property>
<property name="logPathForProject">/opt/logs/hrmapp/project/property>
<property name="logPathForTest">/opt/logs/hrmapp/test/property>
properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="[%-5p]:%d{yyyy-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
Console>
<RollingFile name="INFO" filename="${logPath}/info.log"
filepattern="${logPath}/%d{yyyyMMdd}-info-%i.log.zip">
<Filters>
<ThresholdFilter level="INFO"/>
Filters>
<PatternLayout pattern="[ %-5p]:%d{yyyy-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
Policies>
<DefaultRolloverStrategy max="20" />
RollingFile>
<RollingFile name="WARN" filename="${logPath}/warn.log"
filepattern="${logPath}/%d{yyyyMMdd}-warn-%i.log.zip">
<Filters>
<ThresholdFilter level="WARN"/>
<ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
Filters>
<PatternLayout pattern="[ %-5p]:%d{yyyy-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
Policies>
<DefaultRolloverStrategy max="20" />
RollingFile>
<RollingFile name="ERROR" filename="${logPath}/error.log"
filepattern="${logPath}/%d{yyyyMMdd}-error-%i.log.zip">
<ThresholdFilter level="ERROR"/>
<PatternLayout pattern="[ %-5p]:%d{yyyy-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
Policies>
<DefaultRolloverStrategy max="20" />
RollingFile>
<Console name="ConsolePro" target="SYSTEM_OUT">
<PatternLayout pattern="[%-5p]:%d{yyyy-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
Console>
<RollingFile name="INFOPro" filename="${logPathForProject}/info.log"
filepattern="${logPathForProject}/%d{yyyyMMdd}-info-%i.log.zip">
<Filters>
<ThresholdFilter level="INFO"/>
Filters>
<PatternLayout pattern="[ %-5p]:%d{yyyy-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
Policies>
<DefaultRolloverStrategy max="20" />
RollingFile>
<RollingFile name="WARNPro" filename="${logPathForProject}/warn.log"
filepattern="${logPathForProject}/%d{yyyyMMdd}-warn-%i.log.zip">
<Filters>
<ThresholdFilter level="WARN"/>
<ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
Filters>
<PatternLayout pattern="[ %-5p]:%d{yyyy-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
Policies>
<DefaultRolloverStrategy max="20" />
RollingFile>
<RollingFile name="ERRORPro" filename="${logPathForProject}/error.log"
filepattern="${logPathForProject}/%d{yyyyMMdd}-error-%i.log.zip">
<ThresholdFilter level="ERROR"/>
<PatternLayout pattern="[ %-5p]:%d{yyyy-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
Policies>
<DefaultRolloverStrategy max="20" />
RollingFile>
<RollingFile name="INFOTestAppender" filename="${logPathForTest}/logPathForTest.log"
filepattern="${logPathForTest}/%d{yyyyMMdd}-testInfo-%i.log.zip">
<ThresholdFilter level="INFO"/>
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} - %msg%n" />
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
Policies>
<DefaultRolloverStrategy max="60"/>
RollingFile>
Appenders>
<Loggers>
<logger name="com.bj58.renetwork" level="debug" additivity="false">
logger>
<logger name="INFOTest" level="debug" additivity="false">
logger>
<root level="INFO">
root>
Loggers>
Configuration>
注释我写的应该比较清楚了,如果你还是不太明白,下面我会详细介绍一下。
<特别注意!!!>:
语句这里的“1”并不是特指一天,而是数量1,对于单位不管是“天”、“分”、“秒”,也就是1秒打包一次或者1分钟打印一次,都是取决于
中filepattern最小单位。这里我们的filepattern最小单位是天,所以是每天打包一次。1: 根节点Configuration有两个属性:status和monitorinterval
2:根节点下的子节点properties,用于定义变量和修改变量,这里我只定义了两个路径变量,一个是容器log路径,一个是项目log路径
3:根节点下的子节点Appenders,主要用于定义Appender,常见的有三种子节点:Console、RollingFile、File
log4j组件提供了好多种appender供我们使用,介绍看官网吧特别详细: http://logging.apache.org/log4j/2.x/manual/appenders.html
其中:fileName和filePattern不同的作用:
fileName指定的是当天日志输出的日志输出位置
filePattern指的根据配置,对每天的日志文件进行压缩存储的时候的文件名,也就是新建的文件名
4:根节点下的子节点Loggers,用于配置上述添加的appender,两种子节点:Root、Logger
Root节点用来指定项目的根日志,如果没有单独指定Logger,那么就会默认使用该Root日志输出
Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等
5:自定义控制Logger
可以将日志打印精确到 一个类,一个方法,一个Logger 。
上述demo中配置了对一个特定的Logger操作,只将此Logger的日志打印到对应的文件中。
<RollingFile name="INFOTestAppender" filename="${logPathForTest}/logPathForTest.log"
filepattern="${logPathForTest}/%d{yyyyMMdd}-testInfo-%i.log.zip">
<ThresholdFilter level="INFO"/>
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} - %msg%n" />
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
Policies>
<DefaultRolloverStrategy max="60"/>
RollingFile>
......
<logger name="INFOTest" level="debug" additivity="false">
<appender-ref ref="INFOTestAppender"/>
logger>
在java代码中,这样创建Logger:
private static final Logger INFO_TEST= LoggerFactory.getLogger("INFOTest");
然后,Logger : INFO_TEST 打印的日志就会单独的存放在对应的日志文件中。
注意: 注意对应关系,
此处定义了一个名为INFOTest
的自定义Logger,在java代码中获取该Logger进行日志打印。
用途: 这种配置可以用于埋点日志的配置,注意埋点日志的打印格式,尽量只包含关键信息并且分隔符统一,这样可以便于日志分析。例如:%d{yyyy-MM-dd HH:mm:ss} %msg%n
只包含打印时间和日志信息,并且采用四个空格分割,在 %msg
打印的日志信息里面,也尽量采用四个空格分隔不同部分,做到整体日志信息采用统一分隔符
6:输出格式相关:
同步打印日志是最消耗资源的方式,我们在开发的时候,可以选择使用全同步方式打印日志,这样便于我们debug。或者项目并发度不高的情况下也可以使用这种方式。但是,当并发量比较大、对项目响应速度敏感时并且对日志不是强实时性要求的话,最好还是使用全部异步或者混合方式。
上述的demo便是全部同步的案例。在此不再赘述。
全部异步打印日志是对项目请求速度最理想的方式,在500个线程的情况下速度几乎是全同步打印log的10倍,是混合打印的2倍。下面是官网的比较图,可以对照着看一下:
异步Logger是让业务逻辑把日志信息放入Disruptor队列后可以直接返回,具有更高吞吐、调用log方法更低的延迟。但也有一些缺点比如:异常处理麻烦、 可变日志消息问题、更大的CPU开销、需要等待“最慢的Appender”消费完成。
所以我们在并发量高、日志实时性要求不高,并且所暴漏的缺点都可以容忍的情况下最好还是选用全部异步打印日志,这样可以获得更快的响应,也会给用户更好的体验。
异步打印配置有几种方式:
1:在你的classpath下面添加个log4j2.component.properties文件,并且添加以下内容:
这种方式不需要修改原来的log4j2.xml文件
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
2:使用异步标签,修改上面的demo的部分:
如果想要全部异步log的话,一定要所有的相关标签都是用异步标签
<Loggers>
<asyncLogger name="com.bj58.renetwork" level="debug" additivity="false">
asyncLogger>
<asyncRoot level="INFO">
asyncRoot>
Loggers>
3:JVM启动参数(boot.ini)加上:
-DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
混合模式就是既有一异步又有同步日志打印,那些部分需要同步或者异步,这需要根据具体对项目该部分的需求来定了.
下面我设置了项目日志同步打印,容器日志异步打印
<Loggers>
<logger name="com.bj58.renetwork" level="debug" additivity="false">
logger>
<asyncRoot level="INFO">
asyncRoot>
Loggers>
级别只输出“大于等于”自身级别的log,7中level的级别关系如下:
OFF > FATAL > ERROR > WARN > INFO > DEBUG > ALL
1. DEBUG :
DEBUG Level指出细粒度信息事件对调试应用程序是非常有帮助的。
2. INFO
INFO level表明 消息在粗粒度级别上突出强调应用程序的运行过程。
3.WARN
WARN level表明会出现潜在错误的情形。
4.ERROR
ERROR level指出虽然发生错误事件,但仍然不影响系统的继续运行。
5.FATAL
FATAL level指出每个严重的错误事件将会导致应用程序的退出。
6.ALL
ALL Level是最低等级的,用于打开所有日志记录。
7.OFF
OFF Level是最高等级的,用于关闭所有日志记录。
Log4j2和logback都是日志组件,logback就是为了替代log4j1出现的,log4j2是log4j1的升级版,几乎相当于重构了log4j1。
log4j2的效率可以在多线程时,在线程数量大的情况下,超过logback10倍左右!下面是官网提供的数据对比:
速度对比图(来自官网):
在Solaris和windows操作系统上的数据对比(来自官网):
如果转载此博文,请附上本文链接,谢谢合作~ :https://blog.csdn.net/csdn___lyy
如果感觉这篇文章对您有所帮助,请点击一下“喜欢”或者“关注”博主,您的喜欢和关注将是我前进的最大动力!
refer:博客 博客