一、前言
日志记录是应用程序运行中必不可少的一部分。具有良好格式和完备信息的日志记录可以在程序出现问题时帮助开发人员迅速地定位错误的根源。记录日志只是有效地利用日志的第一步,更重要的是如何对程序运行时产生的日志进行处理和分析。
一般来说日志分为两种:业务日志和异常日志。使用日志我们希望能达到以下目标:
- 对程序运行情况的记录和监控;
- 在必要时可详细了解程序内部的运行状态;
- 对系统性能的影响尽量小。
二、日志基础
1、日志组件
Java日志API由以下三个核心组件组成:
- Loggers:负责捕捉事件并将其发送给合适的Appender。
- Appenders:也称为Handlers,它负责接收日志事件,使用Layout格式化,然后输出到指定位置。
- Layouts:也被称为Formatters,它负责对日志事件中的数据进行转换和格式化。
2、日志框架
在Java中,输出日志需要使用一个或者多个日志框架,这些框架提供了必要的对象、方法和配置来传输消息。Java在包中提供了一个默认的框架。除此之外,还有很多其它第三方框架,包括Log4j、Logback以及tinylog。还有其它一些开发包,例如SLF4J和Apache Commons Logging,它们提供了一些抽象层,对你的代码和日志框架进行解耦,从而允许你在不同的日志框架中进行切换。
- [x] Java Logging API (Oracle) Java默认的日志框架
- [x] Log4j (Apache) 开源日志框架
- [x] Logback (Logback Project) 开源项目,被设计成Log4j版本1的后续版本
- [x] tinylog (tinylog) 轻量级开源logger
3、日志抽象层
诸如SLF4J这样的抽象层,会将你的应用程序从日志框架中解耦。应用程序可以在运行时选择绑定到一个特定的日志框架(例如、Log4j或者Logback),这通过在应用程序的类路径中添加对应的日志框架来实现。
如果在类路径中配置的日志框架不可用,抽象层就会立刻取消调用日志的相应逻辑。抽象层可以让我们更加容易地改变项目现有的日志框架,或者集成那些使用了不同日志框架的项目。
4、日志配置
尽管所有的Java日志框架都可以通过代码进行配置,但是大部分配置还是通过外部配置文件完成的。这些文件决定了日志消息在何时通过什么方式进行处理,日志框架可以在运行时加载这些文件。
4.1、配置实例
Log4j支持两种配置文件格式,一种是XML格式的文件,一种是properties格式的文件。比较常用的是properties文件。
- properties格式
# ====================================================================== #
# 配置根 logger (日志级别, 输出位置)
log4j.rootLogger=debug, fileR, Console
# ====================================================================== #
# 特殊类的控制
#=INFO
#=INFO
#=DEBUG
#=DEBUG
#=DEBUG
# ====================================================================== #
# 控制台输出 Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
log4j.appender.Console.layout=${log4j.log.layout}
log4j.appender.Console.layout.ConversionPattern=${log4j.log.layout.pattern}
# ====================================================================== #
# 文件输出 file (${}/logs/)
log4j.appender.fileR=org.apache.log4j.DailyRollingFileAppender
log4j.appender.fileR.DatePattern = '.'yyyy-MM-dd
log4j.appender.fileR.File=D\://apache-tomcat-7.0.70/logs/JavaUtils_info.log
log4j.appender.fileR.layout=${log4j.log.layout}
log4j.appender.fileR.layout.ConversionPattern=${log4j.log.layout.pattern}
# ====================================================================== #
# 配置参数
#=ALL,TRACE,DEBUG,INFO,WARN,ERROR,FATAL,OFF
#=CONSOLE,FILE,DATABASE,EMAIL,SOCKET
log4j.log.encoding=UTF-8
log4j.log.layout=org.apache.log4j.PatternLayout
log4j.log.layout.pattern=%d [%t] [%-5p] [%l] - %m%n
# ====================================================================== #
- XML格式
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//log4j/log4j Configuration//EN" "">
<log4j:configuration xmlns:log4j="/log4j/">
<appender name="RollingAppender" class=".">
<param name="File" value="logs/" />
<param name="Encoding" value="UTF-8" />
<param name="DatePattern" value="'.'yyyy-MM-dd" />
<layout class=".">
<param name="ConversionPattern" value="[%p] %d %c %M - %m%n" />
</layout>
</appender>
<root>
<priority value="DEBUG" />
<appender-ref ref="RollingAppender" />
</root>
</log4j:configuration>
4.2、配置说明
4.2.1、rootLogger
- = [ level ] , appenderName, appenderName, …
level:是log4j的日志级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG。
appenderName:就是指定日志信息输出到哪个地方。可同时指定多个输出目的地。
4.2.2、配置日志信息输出目的地Appender,其语法为:
log4j.appender.appenderName = fully.qualified.name.of.appender.class
log4j.appender.appenderName.option1 = value1
...
log4j.appender.appenderName.optionN = valueN
# 其中,Log4j提供的appender有以下几种:
org.apache.log4j.ConsoleAppender(控制台);
org.apache.log4j.FileAppender(文件);
org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件);
org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件);
org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)
4.2.3、置好appender后,针对appender的配置
4.2.3.1、.ConsoleAppender选项
- Threshold=WARN:指定日志消息的输出最低层次。
- ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。
- Target=:默认情况下是:,指定输出控制台
4.2.3.2、.FileAppender 选项
- Threshold=WARN:指定日志消息的输出最低层次。
- ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。
- File=:指定消息输出到文件。
- Append=false:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。
4.2.3.3、.DailyRollingFileAppender 选项
- Threshold=WARN:指定日志消息的输出最低层次。
- ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。
- File=:指定消息输出到文件。
- Append=false:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。
-
DatePattern=’.’yyyy-ww:每周滚动一次文件,即每周产生一个新的文件。当然也可以指定按月、周、天、时和分。即对应的格式如下:
- ’.’yyyy-MM:每月
- ‘.’yyyy-ww: 每周
- ‘.’yyyy-MM-dd: 每天
- ‘.’yyyy-MM-dd-a: 每天两次
- ‘.’yyyy-MM-dd-HH: 每小时
- ‘.’yyyy-MM-dd-HH-mm: 每分钟
4.2.3.4、.RollingFileAppender 选项
- Threshold=WARN:指定日志消息的输出最低层次。
- ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。
- File=:指定消息输出到文件。
- Append=false:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。
- MaxFileSize=100KB:后缀可以是KB, MB 或者是 GB. 在日志文件到达该大小时,将会自动滚动,即将原来的内容移到.1文件
- MaxBackupIndex=2:指定可以产生的滚动文件的最大数。
4.2.4、配置日志信息的布局,其语法为:
log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class
log4j.appender.appenderName.layout.option1 = value1
...
log4j.appender.appenderName.layout.option = valueN
# 其中,Log4j提供的layout有以下几种:
org.apache.log4j.HTMLLayout(以HTML表格形式布局),
org.apache.log4j.PatternLayout(可以灵活地指定布局模式),
org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串),
org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)
4.2.5、打印参数如下:
- %d:输出日志时间点的日期或时间,默认格式为ISO8601。
- %t:输出产生该日志线程的线程名。
- %c:输出所属的类目,通常就是所在类的全名。
- %p:输出日志级别。
- %m:输出代码中指定的消息。
- %n:输出一个回车换行符。
- %r:输入自应用启动到输出该log信息耗费的毫秒数。
- %l:输出日志事件发生的位置,包括类名、线程名,以及所在代码的行数。
4.3、日志级别
SLF4J把Log由高到低分成了Error,Warn,Info,Debug和Trace五个级别。我们可以分成用户级别和开发级别。
4.3.1 用户级别
Error、Warn和Info这三个级别的Log会出现在生产环境上,他们必须是运维人员能阅读明白的
4.3.1.1 Error
- 影响到程序正常运行、当前请求正常运行的异常情况,例如:
- 打开配置文件失败
- 第三方应用网络连接异常
- SQLException
- 不应该出现的情况,例如:
- 某个Service方法返回的List里面应该有元素的时候缺获得一个空List
- 做字符转换的时候居然报错说没有GBK字符集
4.3.1.2 Warn
- 不应该出现但是不影响程序、当前请求正常运行的异常情况,例如:
- 有容错机制的时候出现的错误情况
- 找不到配置文件,但是系统能自动创建配置文件
- 即将接近临界值的时候,例如:
- 缓存池占用达到警告线
4.3.1.3 Info
- 系统运行信息
- Service方法的出入口
- 主要逻辑中的分步骤
- 外部接口部分
- 客户端请求参数和返回给客户端的结果
- 调用第三方时的调用参数和调用结果
4.3.2 开发级别
Debug和Trace这俩个级别主要是在开发期间使用或者当系统出现问题后开发人员介入调试的时候用的,需要有助于提供详细的信息。
4.3.2.1 Debug
- 用于记录程序变量,例如:
- 多次迭代中的变量
- 用于替代代码中的注释
4.3.2.2 Trace
主要用于记录系统运行中的完整信息,比如完整的HTTP Request和Http Response
5、调用实例
package ;
import ;
import org.;
import org.;
/**
* 日志管理的Demo
*/
public class LoggingDemo {
private Integer a = 1;
private Integer b = 0;
private String name = "图灵";
// 创建LOGGER
private final static Logger LOGGER = ();
@Test
public void LoggingDemo() {
try {
("读取被除数b的值为[{}]", b);
("读取配置文件[{}]", "/src/main/resources/");
("添加[{}]为管理员", name);
a = a / b;
("-->出现异常后,try内异常后面的代码不再执行,所以try范围不能过大");
} catch (ArithmeticException e) {
// ();
("被除数不能为0", e);
} finally {
("--> finally内的代码始终被执行");
}
("--> 异常捕获后,try外的代码正常执行");
}
}
三、日志记录的注意点
通常情况下在程序日志里记录一些比较有意义的状态数据:程序启动,退出的时间点;程序运行消耗时间;耗时程序的执行进度;重要变量的状态变化。
-
如何正确的写日志
- 合理使用日志分级;
- 正确的使用输出模式;
- 尽量带入上下文的信息;
- 尽量不要包含敏感信息;
- 变量用[]与普通文本区分开来;
- 日志信息应该简洁,易读,易解析;
- 不允许出现printStackTrace、println语句。
-
那些地方需要写日志
- 任何业务异常都应该记下来完整的异常信息;
- 非预期执行,为程序在“有可能”执行到的地方打印日志;
- 程序中对外部系统与模块的依赖调用前后都记下日志,方便接口调试;
- 程序中重要的状态信息的变化应该记录下来,方便查问题时还原现场,推断程序运行过程;
- 系统入口与出口,这个粒度可以是重要方法级或模块级。记录它的输入与输出,方便定位。
具体参考:阿里巴巴JAVA编程规范之异常日志
四、参考模板
<properties>
<!-- log4j版本号 -->
<>1.7.5</>
<>1.2.17</>
</properties>
<dependencies>
<!-- log start -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${}</version>
</dependency>
<!-- log end -->
<dependencies>
五、配置过程中遇到的问题
- 如何以json格式输出日志
- 日志输出路径没有权限的问题
- 如何把日志写入数据库或发送邮件
六、日志分析工具
- [x] 一个Java写的日志分析工具
- [x] ELK(ElasticSearch, Logstash, Kibana)搭建实时日志分析平台
- [x] 用Kibana和logstash快速搭建实时日志查询、收集与分析系统
七、拓展阅读
- [x] 使用AOP与注解记录Java日志的方法
八、参考资料
- [x] Java日志终极指南
- [x] Slf4j提供的最佳实践
- [x] 正确使用日志的10个技巧
- [x] 阿里巴巴JAVA编程规范之异常日志
- [x] 程序那些事:日志记录的作用和方法
- [x] JAVA - 优雅的记录日志(log4j实战篇)
- [x] Java程序员修炼之道 之 Logging(2/3) - 怎么写Log
- [x] java日志组件介绍(common-logging,log4j,slf4j,logback )
八、后记
本文缺少日志分析和源码分析部分内容,仅适用于小型项目应用参考,有待后续完善。