一、摘要
不管是使用何种编程语言,何种框架,日志输出几乎无处不再,也是任何商业软件中必不可少的一部分。
总结起来,日志的用途大致可以归纳成以下三种:
- 问题追踪:通过日志不仅仅包括我们程序的一些bug,也可以在安装配置时,通过日志可以发现问题。
- 状态监控:通过实时分析日志,可以监控系统的运行状态,做到早发现问题、早处理问题。
- 安全审计:审计主要体现在安全上,通过对日志进行分析,可以发现是否存在非授权的操作。
以 Java 编程语言为例,打印日志的方式有很多,例如通过System.out.print()方法将关键信息输出到控制台,也可以通过 JDK 自带的日志Logger类输出,虽然 JDK 从1.4开始支持日志输出,但是功能单一,无法更好的满足商业要求,于是诞生了很多第三方日志库,像我们所熟悉的主流框架log4j、log4j2、logback等,提供的 API 功能都远胜 JDK 提供的Logger。
二、Log4j
2.1、介绍
Log4j 是一种非常流行的日志框架,由Ceki Gülcü首创,之后将其开源贡献给 Apache 软件基金会。
Log4j 有三个主要的组件:Loggers(记录器),Appenders (输出源)和Layouts(布局)。这里可简单理解为日志类别、日志要输出的地方和日志以何种形式输出。
综合使用这三个组件可以轻松地记录信息的类型和级别,并可以在运行时控制日志输出的样式和位置。
Log4j 的架构大致如下:
当我们使用 Log4j 输出一条日志时,Log4j 自动通过不同的Appender(输出源)把同一条日志输出到不同的目的地。例如:
- console:输出到屏幕;
- file:输出到文件;
- socket:通过网络输出到远程计算机;
- jdbc:输出到数据库
在输出日志的过程中,通过Filter来过滤哪些log需要被输出,哪些log不需要被输出。
在Loggers(记录器)组件中,级别分五种:DEBUG、INFO、WARN、ERROR和FATAL。
这五个级别是有顺序的,DEBUG < INFO < WARN < ERROR < FATAL,分别用来指定这条日志信息的重要程度,明白这一点很重要,Log4j有一个规则:只输出级别不低于设定级别的日志信息。
假设Loggers级别设定为INFO,则INFO、WARN、ERROR和FATAL级别的日志信息都会输出,而级别比INFO低的DEBUG则不会输出。
最后,通过Layout来格式化日志信息,例如,自动添加日期、时间、方法名称等信息。
具体输出样式配置,可以参考如下内容Log4j2 - Layouts布局介绍
2.2、项目应用
以 Java 项目为例,在 Maven 的pom.xml中添加如下依赖!
2.2.1、添加 maven 依赖
<dependencies> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.6.6</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.6</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies>
2.2.2、创建log4j配置
在实际应用中,要使Log4j在系统中运行须事先设定配置文件。
配置文件实际上也就是对Logger、Appender及Layout进行相应设定。
Log4j支持两种配置文件格式,一种是XML格式的文件,一种是properties属性文件,二选一。
创建一个log4j.xml或者log4j.properties,将其放入项目根目录下。
1、XML格式
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <!-- 控制台输出配置 --> <appender name="console" class="org.apache.log4j.ConsoleAppender"> <!-- 目标为控制台 --> <param name="Target" value="System.out" /> <layout class="org.apache.log4j.PatternLayout"> <!-- 输出格式 --> <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %l %m%n" /> </layout> </appender> <!-- 文件输出配置 --> <appender name="log_file" class="org.apache.log4j.DailyRollingFileAppender"> <!-- 目标为文件 --> <param name="File" value="/logs/log/file.log" /> <!-- 向文件追加输出 --> <param name="Append" value="true" /> <!-- 每个小时生成一个log --> <param name="DatePattern" value="'.'yyyy-MM-dd-HH" /> <layout class="org.apache.log4j.PatternLayout"> <!-- 输出格式 --> <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %l %m%n" /> </layout> </appender> <!-- Application Loggers --> <logger name="org.example"> <level value="info" /> </logger> <!-- 根目录 --> <!-- Root Logger --> <root> <priority value="info" /> <appender-ref ref="console" /> <appender-ref ref="log_file" /> </root> </log4j:configuration>
2、XML格式
log4j.rootLogger=INFO,M,C,E log4j.additivity.monitorLogger=false # INFO级别文件输出配置 log4j.appender.M=org.apache.log4j.DailyRollingFileAppender log4j.appender.M.File=/logs/info.log log4j.appender.M.ImmediateFlush=false log4j.appender.M.BufferedIO=true log4j.appender.M.BufferSize=16384 log4j.appender.M.Append=true log4j.appender.M.Threshold=INFO log4j.appender.M.DatePattern='.'yyyy-MM-dd log4j.appender.M.layout=org.apache.log4j.PatternLayout log4j.appender.M.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} %p %l %m %n # ERROR级别文件输出配置 log4j.appender.E=org.apache.log4j.DailyRollingFileAppender log4j.appender.E.File=/logs/error.log log4j.appender.E.ImmediateFlush=true log4j.appender.E.Append=true log4j.appender.E.Threshold=ERROR log4j.appender.E.DatePattern='.'yyyy-MM-dd log4j.appender.E.layout=org.apache.log4j.PatternLayout log4j.appender.E.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} %p %l %m %n # 控制台输出配置 log4j.appender.C=org.apache.log4j.ConsoleAppender log4j.appender.C.Threshold=INFO log4j.appender.C.layout=org.apache.log4j.PatternLayout log4j.appender.C.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %l %m %n
2.2.3、log4j使用
在需要打印日志的类中,引入Logger类,在需要的地方打印即可!
package org.example.log4j.service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LogPrintUtil { /**log静态常量*/ private static final Logger logger = LoggerFactory.getLogger(LogPrintUtil.class); public static void main(String[] args){ logger.info("info信息"); logger.warn("warn信息"); logger.error("error信息"); } }
当然你还可以这样写
if(logger.isInfoEnabled()) { logger.info("info信息"); } if(logger.isWarnEnabled()) { logger.warn("warn信息"); }
2.2.4、isInfoEnabled()有何作用呢?
简单来说,在某些场景下,用isInfoEnabled()方法判断下是能提升性能的!
例如我们打印这段内容logger.info("User:" + userId + appId),程序在打印这行代码时,先对内容("User:" + userId + appId)进行字符串拼接,然后再输出。
如果当前配置文件中日志输出级别是info,是直接输出的,当日志输出级别是error时,logger.info()的内容时不输出的,但是我们却进行了字符串拼接,如果加上if(logger.isInfoEnabled())进行一次判定,logger.info()就不会执行,从而更好的提升性能,这个尤其是在高并发和复杂log打印情况下提升非常显著。
另外,ERROR及其以上级别的log信息是一定会被输出的,所以只有logger.isDebugEnabled、logger.isInfoEnabled和logger.isWarnEnabled()方法,而没有logger.isErrorEnabled方法。
