一、摘要
不管是使用何种编程语言,何种框架,日志输出几乎无处不再,也是任何商业软件中必不可少的一部分。
总结起来,日志的用途大致可以归纳成以下三种:
- 问题追踪:通过日志不仅仅包括我们程序的一些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
方法。