一、摘要
不管是使用何种编程语言,何种框架,日志输出几乎无处不再,也是任何商业软件中必不可少的一部分。
总结起来,日志的用途大致可以归纳成以下三种:
-
问题追踪
:通过日志不仅仅包括我们程序的一些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
(输出源)把同一条日志输出到不同的目的地。例如:
在输出日志的过程中,通过
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 依赖
org.slf4j
slf4j-api
1.6.6
org.slf4j
slf4j-log4j12
1.6.6
log4j
log4j
1.2.17
2.2.2、创建log4j配置
在实际应用中,要使
Log4j
在系统中运行须事先设定配置文件。
配置文件实际上也就是对
Logger
、
Appender
及
Layout
进行相应设定。
Log4j
支持两种配置文件格式,一种是
XML
格式的文件,一种是
properties
属性文件,二选一。
创建一个log4j.xml或者log4j.properties,将其放入项目根目录下。
1、XML格式
"1.0" encoding="UTF-8"?>
log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd">
"http://jakarta.apache.org/log4j/">
"console" class="org.apache.log4j.ConsoleAppender">
"Target" value="System.out" />
"org.apache.log4j.PatternLayout">
"ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %l %m%n" />
"log_file" class="org.apache.log4j.DailyRollingFileAppender">
"File" value="/logs/log/file.log" />
"Append" value="true" />
"DatePattern" value="'.'yyyy-MM-dd-HH" />
"org.apache.log4j.PatternLayout">
"ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %l %m%n" />
"org.example">
"info" />
"info" />
"console" />
"log_file" />
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
方法。
三、Log4j2
3.1、介绍
log4j2 是 log4j 1.x 的升级版,参考了 logback 的一些优秀的设计,并且修复了一些问题,因此带来了一些重大的提升,主要特点有:
-
异常处理
:在logback中,Appender中的异常不会被应用感知到,但是在log4j2中,提供了一些异常处理机制。
-
性能提升
, log4j2相较于log4j 1和logback都具有很明显的性能提升,后面会有官方测试的数据。
-
自动重载配置
:参考了logback的设计,当然会提供自动刷新参数配置,最实用的就是我们在生产上可以动态的修改日志的级别而不需要重启应用——那对监控来说,是非常敏感的。
-
无垃圾机制
:log4j2在大部分情况下,都可以使用其设计的一套无垃圾机制,避免频繁的日志收集导致的jvm gc。
3.2、项目应用
3.2.1、添加 maven 依赖
org.slf4j
slf4j-api
1.7.13
org.slf4j
jcl-over-slf4j
1.7.13
runtime
org.apache.logging.log4j
log4j-api
2.4.1
org.apache.logging.log4j
log4j-core
2.4.1
org.apache.logging.log4j
log4j-slf4j-impl
2.4.1
com.lmax
disruptor
3.2.0
3.2.2、创建log4j2配置
在项目的根目录下创建一个
log4j2.xml
的文件,与
log4j
相比,
log4j2
的异步输出日志性能非常强劲,配置如下:
1、同步输出日志
"1.0" encoding="UTF-8"?>
"error">
"fileDir">/logs/log4j2
"fileHistory">/logs/log4j2/history
"Console" target="SYSTEM_OUT">
"UTF-8" pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %l %msg%n" />
"RollingFileInfo" fileName="${fileDir}/info.log" filePattern="${fileHistory}/info/%d{yyyy-MM-dd}-%i.log">
"info" onMatch="ACCEPT" onMismatch="DENY" />
"UTF-8" pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %l %msg%n" />
"100MB" />
"20" />
"RollingFileWarn" fileName="${fileDir}/warn.log" filePattern="${fileHistory}/warn/%d{yyyy-MM-dd}-%i.log">
"warn" onMatch="ACCEPT" onMismatch="DENY" />
"UTF-8" pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %l %msg%n" />
"100MB" />
"20" />
"RollingFileError" fileName="${fileDir}/error.log" filePattern="${fileHistory}/error/%d{yyyy-MM-dd}-%i.log">
"error" onMatch="ACCEPT" onMismatch="DENY" />
"UTF-8" pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %l %msg%n" />
"100MB" />
"20" />
"info" includeLocation="true">
"Console" />
2、异步输出日志
"1.0" encoding="UTF-8"?>
"error">
"fileDir">/logs/log4j2