目录
一、JUL日志框架(日常使用)
主流日志框架
-
日志实现(具体干活的):JUL(java util logging)、logback、log4j、log4j2
-
日志门面(指定规则的):JCL(Jakarta Commons Logging)、slf4j( Simple Logging Facade for Java)
一、JUL日志框架
Java util Logging是java原生的日志框架,使用时不需要另外引用第三方类库,使用方便,学习简单,能在小型应用中灵活使用。
在JUL中有以下组件:
- Loggers:被称为记录器,应用程序通过获取Logger对象,调用其API来来发布日志信息。Logger 通常时应用程序访问日志系统的入口程序。
- Handler :负责将日志记录发送到适当的目的地,如控制台、文件或者网络目的地。下面是一些常见的 Handler 类型:
ConsoleHandler - 将日志记录输出到标准错误流(通常是控制台)。
FileHandler - 将日志记录输出到文件中。可以通过配置指定文件名模式,允许创建滚动文件。
SocketHandler - 将日志记录发送到套接字服务器。
StreamHandler - 将日志记录输出到任意的输出流。
MemoryHandler - 将日志记录存储在内存缓冲区中,当满足某些条件时,将缓冲区中的日志记录推送到目标 Handler。 - Formatters:Formatter 接口用于定义日志记录的格式。通过自定义 Formatter,可以控制日志输出的具体样式,比如日期格式、消息前缀等。也有一些内置的 Formatter 类。
- Level:每条日志消息都有一个关联的日志级别。该级别粗略指导了日志消息的重要性和紧迫,可以将Level和Loggers,Handler 做关联以便于我们过滤消息。
- Filters:过滤器,根据需要定制哪些信息会被记录,哪些信息会被放过。
(一)、入门案例
import java.io.IOException;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
public class LoggingExample {
public static void main(String[] args) {
// 获取 Logger 实例
Logger logger = Logger.getLogger(LoggingExample.class.getName());
try {
// 1.创建 FileHandler 实例,指定日志文件的位置和是否追加日志
FileHandler fileHandler = new FileHandler("app.log", true);
// 2.设置日志格式化器
fileHandler.setFormatter(new SimpleFormatter());
// 3.创建 ConsoleHandler 实例
ConsoleHandler consoleHandler = new ConsoleHandler();
// 添加 Handler 到 Logger
logger.addHandler(fileHandler);
logger.addHandler(consoleHandler);
// 4.设置 Logger 的日志级别
logger.setLevel(java.util.logging.Level.ALL);
// 记录一条日志消息
logger.info("This is an info message.");
} catch (IOException e) {
logger.severe("Exception occurred while setting up logging handlers: " + e.getMessage());
}
}
}
(二)、日志的级别
java.util.logging.Level中定义日志的级别:
- SEVERE(最高值)
- WARNING
- INFO (默认级别)
- CONFIG
- FINE
- FINER
- FINEST(最低值)
再例如:我们查看tomcat的日志,能明显的看到不同级别的日志,其实tomcat默认使用的就是JUL:
还有两个特殊的级别:
- OFF,可用来关闭日志记录。
- ALL,启用所有消息的日志记录。
虽然我们测试了7个日志级别,
@Test
public void testLogger() {
Logger logger = Logger.getLogger(LoggerTest.class.getName());
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
我们发现能够打印的只有三行,这是为什么呢?
我们找一下这个文件,下图是jdk11的日志配置文件:
或者在jdk1.8中:
就可以看到系统默认在控制台打印的日志级别了,我们可以单独创建配置文件修改配置文件。
(三)、日志配置
import java.io.IOException;
import java.util.logging.*;
public class LoggingExample {
public static void main(String[] args) throws IOException {
// a.创建日志记录器对象
Logger logger = Logger.getLogger(LoggingExample.class.getName());
// 关闭系统默认配置
logger.setUseParentHandlers(false);
// b.创建handler对象
FileHandler fileHandler = new FileHandler("jul.log");
ConsoleHandler consoleHandler = new ConsoleHandler();
// c.创建formatter对象
SimpleFormatter simpleFormatter = new SimpleFormatter();
// d.进行关联
consoleHandler.setFormatter(simpleFormatter);
fileHandler.setFormatter(simpleFormatter);
logger.addHandler(consoleHandler);
logger.addHandler(fileHandler);
// e.设置日志级别
logger.setLevel(Level.ALL);
consoleHandler.setLevel(Level.ALL);
// 日志记录输出
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
}
文件、控制台全都输出全部级别
(四)、日志格式化
上面我们用的Formatters是框架自带的,我们可以继承Formatter自己实现一个模版:
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
public class CustomFormatter extends Formatter {
private final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
@Override
public String format(LogRecord record) {
StringBuilder builder = new StringBuilder(1000);
String timeString = dateFormat.format(record.getMillis());
builder.append(timeString)
.append(" [")
.append(record.getLevel())
.append("] ")
.append(record.getLoggerName())
.append("方法:")
.append(record.getSourceMethodName())
.append(System.lineSeparator())
.append(formatMessage(record))
.append(System.lineSeparator());
return builder.toString();
}
}
把之前的
SimpleFormatter simpleFormatter = new SimpleFormatter();
修改为
CustomFormatter simpleFormatter = new CustomFormatter();
运行:
对比系统自带,去除了日期:
(五)、配置文件
看一看jdk文件夹下的logging.properties文件
#处理器这里设置的是 ConsoleHandler,意味着所有的日志消息将会被发送到标准错误流
#(通常是控制台)
handlers= java.util.logging.ConsoleHandler
.level= INFO
#定义了 FileHandler 使用的文件名模式。%h 表示用户的主目录,%u 表示一个唯一的编号,
#用于防止文件名冲突。因此,这行配置的意思是在用户的主目录下创建名为 java0.log
#(如果存在冲突则可能是 java1.log 等)的日志文件。
java.util.logging.FileHandler.pattern = %h/java%u.log
#设置每个日志文件的最大字节数。
java.util.logging.FileHandler.limit = 50000
#指定要保留的日志文件的数量。当文件达到其大小限制时,它将被覆盖。如果设置为更大的数值,
#例如 3,则会保留最近的三个日志文件。
java.util.logging.FileHandler.count = 1
#日志格式化器
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
#为 ConsoleHandler 单独设置了日志记录级别。通过控制台输出的日志消息级别至少需要是 INFO。
java.util.logging.ConsoleHandler.level = INFO
#为特定的命名空间(这里是 com.xyz.foo)设置了日志记录级别。
#意味着只有级别为 SEVERE 的日志消息才会被记录下来。这可以用来控制特定组件的日志输出,
#比如只记录错误信息。
com.xyz.foo.level = SEVERE
自定义配置
自定义的配置文件。假设我们将其命名为 logging.properties
,用默认配置做更改
在代码中加载自定义配置文件
创建好配置文件后,你需要告诉 java.util.logging
使用这个自定义的配置文件。这通常在程序启动时完成,例如在主类的 main
方法中:
LogManager.getLogManager().readConfiguration(
Main.class.getResourceAsStream("/YouLogging.properties")
);
//之后正常使用就行
logger.info("Application started.");
到这里,你应该了解怎么在代码中使用和配置日志框架了,其实其他日志框架都差别不大。
通过简单的JUL框架,我们后面将学习主流框架搭配:log4j2(实现) + slf4j(门面)
二、日志门面
当我们的系统变的复杂的之后,难免会集成其他的系统,不同的系统之间可能会使用不同的日志系统。那么在一个系统中,我们的日志框架可能会出现多个,会出现混乱,而且随着时间的发展,可能会出现新的效率更高的日志系统,如果我们想切换代价会非常的大。如果我们的日志系统能和jdbc一样,有一套自己的规范,其他实现均按照规范去实现,就能很灵活的使用日志框架了。
日志门面就是为了解决这个问题而出现的一种技术,日志门面是规范,其他的实现按照规范实现各自的日志框架即可,我们程序员基于日志门面编程即可。
减少依赖冲突
在一个复杂的项目中,可能会使用多个第三方库,而这些库可能依赖于不同的日志框架。如果不使用日志门面,就可能导致版本冲突或者冗余依赖的问题。通过引入日志门面,可以解决这类问题,因为所有组件都通过门面来记录日志,而不是直接依赖具体的日志实现。
日志桥接
......日志门面搭配日志实现可有多种搭配,主流为slf4+ log4j2,我就不去做分析了。只做slf4+ log4j2的教程。
三、log4j2的使用
篇幅过长,请移步:log4j2的使用-优快云博客
四、项目中怎么使用日志
使用参数化信息的方式而不是字符串拼接:
int id = 12345;
String symbol = "AAPL";
LOG.error("id:[{}],symbol:[{}]",id,symbol);
不同级别的使用
-
ERROR
影响到程序正常运行、当前请求正常运行的异常情况:
- 打开配置文件失败
-
所有第三方对接的异常(包括第三方返回错误码)
-
所有影响功能使用的异常,包括:SQLException和除了业务异常之外的所有异常(RuntimeException和Exception)
-
不应该出现的情况,比如要使用阿里云传图片,但是未响应
-
如果有Throwable信息,需要记录完成的堆栈信息:
log.error("获取用户[{}]的用户信息时出错",userName,e);
说明,如果进行了抛出异常操作,请不要记录error日志,由最终处理方进行处理:
反例(不要这么做):
try{
....
}catch(Exception ex){
String errorMessage=String.format("Error while reading information of user [%s]",userName);
logger.error(errorMessage,ex);
throw new UserServiceException(errorMessage,ex);
}
这样做会重复打印错误异常
-
WARN
不应该出现但是不影响程序、当前请求正常运行的异常情况:
-
有容错机制的时候出现的错误情况
-
找不到配置文件,但是系统能自动创建配置文件
-
即将接近临界值的时候,例如:缓存池占用达到警告线,业务异常的记录,比如:用户锁定异常
-
INFO
-
Service方法中对于系统/业务状态的变更
-
主要逻辑中的分步骤:1,初始化什么 2、加载什么
-
外部接口部分
-
客户端请求参数(REST/WS)
-
调用第三方时的调用参数和调用结果
-
对于复杂的业务逻辑,需要进行日志打点,以及埋点记录,比如电商系统中的下订单逻辑,以及OrderAction操作(业务状态变更)。
-
调用其他第三方服务时,所有的出参和入参是必须要记录的(因为你很难追溯第三方模块发生的问题)
说明 并不是所有的service都进行出入口打点记录,单一、简单service是没有意义的(job除外,job需要记录开始和结束,)。反例(不要这么做):
public List listByBaseType(Integer baseTypeId) {
log.info("开始查询基地");
BaseExample ex=new BaseExample();
BaseExample.Criteria ctr = ex.createCriteria();
ctr.andIsDeleteEqualTo(IsDelete.USE.getValue());
Optionals.doIfPresent(baseTypeId, ctr::andBaseTypeIdEqualTo);
log.info("查询基地结束");
return baseRepository.selectByExample(ex);
}
-
DEBUG
可以填写所有的想知道的相关信息(但不代表可以随便写,debug信息要有意义,最好有相关参数)
生产环境需要关闭DEBUG信息
如果在生产情况下需要开启DEBUG,需要使用开关进行管理,不能一直开启。
-
TRACE
特别详细的系统运行完成信息,业务代码中,不要使用(除非有特殊用意,否则请使用DEBUG级别替代)