日志技术是一种记录和存储应用程序运行时信息的技术。它可以捕获应用程序的状态、事件、错误和警告等信息,并将其保存到日志文件或其他存储介质中。日志技术可以帮助开发人员和运维团队了解应用程序的运行情况,进行故障排查、性能优化、安全监控和运营分析等工作。
以下是日志技术的一些关键概念和组成部分:
我们之前都是使用输出语句(System.out.println)打印日志的,有什么问题呢?
引入日志技术是为了更好地管理和跟踪应用程序的运行情况。以下是一些使用日志技术的好处:
引入日志技术可以帮助开发人员和运维团队更好地理解和管理应用程序的运行情况,提高开发效率、减少故障修复时间,并提供更好的用户体验。
日志技术的体系结构:
日志门面是设计日志框架的一套标准,所有的日志框架都需要实现这些接口
Log4j实现的是JCL
Logbook实现的是SLF4j
Log4J2-core实现的是Log4j2 API
说明:
因为对JCL接口不满意,所以有人就搞出来了SLF4J;因为对Log4j的性能不满意,所以就出现了Logback
Logback是基于SLF4J的日志规范实现的日志框架
日志框架出现的历史顺序
Log4j --> JUL --> JCL -> Slf4j --> Loback --> Log4j2
JUL(Java Util Logging)是Java平台自带(原生)的日志框架。它是Java标准库的一部分,不需要额外的依赖。JUL提供了一套简单的日志记录API,可以用于在应用程序中记录日志信息。
JUL的主要组件包括:
JUL的配置文件是一个文本文件,通常命名为logging.properties。可以通过修改配置文件来配置日志记录的级别、输出目标、格式等。
1)获取Logger实例:使用 Logger.getLogger() 方法获取Logger实例,传入一个唯一的名称作为参数。通常使用类的全限定名作为名称。
public static final Logger LOGGER = Logger.getLogger(类名);
2)配置日志级别:可以通过修改配置文件 logging.properties来配置日志级别。在配置文件中,可以为不同的包或类设置不同的日志级别。
配置文件可以放置在以下位置之一:
src/main/resources
目录下(对于Maven或Gradle项目)。WEB-INF/classes
目录下(对于Web应用程序)logging.properties
# 设置根日志级别为INFO
.level=INFO
# 设置com.clear包的日志级别为DEBUG
com.clear.level=DEBUG
3)使用Logger实例的不同方法记录不同级别的日志信息。
演示:
import java.util.logging.Logger;
public class JULTest {
// 获取Logger实例
public static final Logger LOGGER = Logger.getLogger("com.clear.JULTest");
@Test
public void test1(){
LOGGER.severe("server"); // 严重
LOGGER.warning("warning"); // 警告
LOGGER.info("info"); // 信息
}
}
记录日志信息:使用Logger实例的不同方法记录不同级别的日志信息。
常用的日志级别包括:
LOGGER.severe("This is a severe message");
LOGGER.warning("This is a warning message");
LOGGER.info("This is an info message");
LOGGER.config("This is a config message");
LOGGER.fine("This is a fine message");
LOGGER.finer("This is a finer message");
LOGGER.finest("This is a finest message");
此外,还有两个特殊的级别:
// 获取Logger实例
public static final Logger LOGGER = Logger.getLogger("com.clear.JULTest");
@Test
public void test2() throws IOException {
// 禁用父级处理器。日志消息将不会传递给父级处理器进行处理。
LOGGER.setUseParentHandlers(false);
// 创建了一ConsoleHandler实例
ConsoleHandler consoleHandler = new ConsoleHandler();
// 设置其日志格式为SimpleFormatter。ConsoleHandler用于将日志消息输出到控制台。
consoleHandler.setFormatter(new SimpleFormatter());
// 设置日志级别,所有级别的日志消息都将被记录。
consoleHandler.setLevel(Level.ALL);
// 创建一个FileHandler实例
// 为了简单,我们将异常抛出去
FileHandler fileHandler = new FileHandler("D:/test/jul.log",true);
fileHandler.setFormatter(new SimpleFormatter());
fileHandler.setLevel(Level.WARNING);
LOGGER.setLevel(Level.ALL);
// 将ConsoleHandler添加到LOGGER的处理器列表中,以便将日志消息发送到控制台。
LOGGER.addHandler(consoleHandler);
// 将FileHandler添加到LOGGER的处理器列表中,以便将日志消息发送到文件中
LOGGER.addHandler(fileHandler);
// 日志输出
LOGGER.severe("severe");
LOGGER.warning("warning");
LOGGER.info("info");
LOGGER.config("config");
LOGGER.fine("fine");
LOGGER.finer("finer");
LOGGER.finest("finest");
}
在JUL(Java Util Logging)中,日志记录器(Logger)之间可以建立父子关系。这种父子关系是通过日志记录器的名称来确定的(即建立树状结构来存储父子关系)。
父子关系的建立是通过日志记录器的名称来实现的。当创建一个新的日志记录器时,可以指定其父级日志记录器的名称。如果没有指定父级日志记录器的名称,则默认为根日志记录器(RootLogger)。
默认情况下,子Logger会继承父Logger的属性。
// 获取Logger实例
public static final Logger LOGGER = Logger.getLogger("com.clear.JULTest");
@Test
public void test3() {
// 任何一个logger都是单例的,名字相同的只有一个
Logger logger = Logger.getLogger(JULTest.class.getName());
System.out.println(LOGGER.hashCode()); // 4959864
System.out.println(logger.hashCode()); // 4959864
System.out.println(LOGGER == logger); // true
}
所有的Logger实例都是由LoggerManager统一管理的
@Test
public void test4(){
Logger logger = Logger.getLogger("com.clear.JULTest");
// todo 如果没有指定父级日志记录器的名称,则默认为根日志记录器(RootLogger)
System.out.println("默认父Logger:"+logger.getParent());
// 默认父Logger:java.util.logging.LogManager$RootLogger@4bae78
System.out.println("默认父Logger名字:"+logger.getParent().getName());
// 默认父Logger名字:
// 父Logger
// todo 父子关系的建立是通过日志记录器的名称来实现的
Logger pLogger = Logger.getLogger("com.clear");
System.out.println("建立父子关系以后:");
System.out.println("父Logger:"+logger.getParent());
// 父Logger:java.util.logging.Logger@1764bce
System.out.println("父Logger名字:"+logger.getParent().getName());
// 父Logger名字:com.clear
System.out.println("父Logger的父Logger:"+pLogger.getParent());
// 父Logger的父Logger:java.util.logging.LogManager$RootLogger@4bae78
System.out.println("父Logger的父Logger名称:"+pLogger.getParent().getName());
// 父Logger的父Logger名称:
}
// 默认情况下,子Logger会继承父Logger的属性。
@Test
public void test5(){
Logger logger = Logger.getLogger("com.clear.JULTest");
// 父Logger
Logger pLogger = Logger.getLogger("com.clear");
// 父Logger的默认日志打印级别为info
pLogger.info("info");
pLogger.fine("fine");
logger.setLevel(Level.ALL);
// todo 设置父类Logger
// 禁用父级处理器
pLogger.setUseParentHandlers(false);
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setFormatter(new SimpleFormatter());
consoleHandler.setLevel(Level.ALL);
pLogger.addHandler(consoleHandler);
// 父Logger可以打印fine级别的信息了
pLogger.info("info");
pLogger.fine("fine");
// todo 子Logger继承了父Logger的属性,也可以打印fine级别的信息了
logger.info("info");
logger.fine("fine");
}
在JUL(Java Util Logging)中,可以使用格式化器(Formatter)来定义日志消息的输出格式。格式化器可以将日志消息的各个部分(如日期、级别、类名、方法名、消息内容等)组合成一个字符串,并指定其输出的格式。
JUL提供了两种内置的格式化器:
SimpleFormatter
:这是默认的格式化器,它将日志消息格式化为一行文本。默认格式为[日期时间] [级别] [类名] [方法名] - [消息内容]
。XMLFormatter
:这是一个将日志消息格式化为XML格式的格式化器。它将日志消息的各个部分作为XML元素,并使用属性来表示其值。可以通过以下方式来配置日志记录器的格式化器:
Handler.setFormatter(Formatter formatter)
方法来设置格式化器。例如,handler.setFormatter(new SimpleFormatter())
将handler
的格式化器设置为SimpleFormatter
。java.util.logging.ConsoleHandler.formatter
属性来设置控制台处理器的格式化器。例如,java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
表示将控制台处理器的格式化器设置为SimpleFormatter
。java.util.logging.FileHandler.formatter
属性来设置文件处理器的格式化器。例如,java.util.logging.FileHandler.formatter=java.util.logging.XMLFormatter
表示将文件处理器的格式化器设置为XMLFormatter
。除了使用内置的格式化器,还可以自定义格式化器来满足特定的需求。自定义格式化器需要实现java.util.logging.Formatter
接口,并实现其中的format(LogRecord record)
方法来定义日志消息的输出格式。
通过配置日志记录器的格式化器,可以灵活地控制日志消息的输出格式,以满足不同的需求和标准。
String类的format方法用于创建格式化的字符串以及连接多个字符串对象。
public static String format(String format, Object... args) {
return new Formatter().format(format, args).toString();
}
public static String format(Locale l, String format, Object... args) {
return new Formatter(l).format(format, args).toString();
}
在这个方法中,我们可以定义字符串模板,例如:
String str = String.format("hello %s","world");
转换符 | 说明 | 举例 |
---|---|---|
%s | 字符串类型 | “hello” |
%c | 字符类型 | ‘a’ |
%b | 布尔类型 | true |
%d | 整数类型(十进制) | 88 |
%x | 整数类型(十六进制) | FF |
%o | 整数类型(八进制) | 77 |
%f | 浮点类型 | 8.888 |
%a | 十六进制浮点类型 | FF.35AE |
%e | 指数类型 | 9.38e+5 |
%n | 换行符 | |
%tx | 日期与时间类型(x代表不同的日期与时间转换符) |
标志 | 说明 | 举例 | 结果 |
---|---|---|---|
+ | 为正数或负数添加符合,因为一般整数不会主动加符号 | (“+%d”, 15) | +15 |
0 | 数字前面补0,用于对齐 | (“%04d”, 99) | 0099 |
空格 | 在整数之前添加指定数量的空格 | (“%4d”, 99) | 99 |
, | 以","对数字进行分组(常用于显示金额) | (“%,f”, 9999.99) | 9,999,990000 |
( | 使用括号包含负数 | (“%(f”, -99.99) | (99.990000) |
默认情况下,我们的可变参数就是按照顺序依次替换,但是如果我们想重复利用可变参数的情况:
我们可以采用在转换符中加 数字$
,完成匹配,例如:
System.out.printf("%1$s %1$s %1$s","小明");
// 其中,1$就代表第一个参数,那么2$就代表第二个参数
前面的 %tx 中,x就代表日期转换符
标志 | 说明 | 举例 |
---|---|---|
c | 包括全部日期和时间信息 | 周四 10月 21 14:30:30 GMT+08:00 2023 |
F | "年-月-日"格式 | 2023-7-18 |
D | "年/月/日"格式 | 2023/7/18 |
r | "HH:MM:SS PM"格式(12时制) | 8:20:30 下午 |
T | "HH:MM:SS"格式(12时制) | 20:20:30 |
R | "HH:MM"格式(24时制) | 20:20 |
b | 月份本地化 | 7月 |
y | 两位的年 | 23 |
Y | 四位的年 | 2023 |
m | 月 | 7 |
d | 日 | 18 |
H | 24小时制的时 | 20 |
l | 12小时制的时 | 8 |
M | 分 | 55 |
S | 秒 | 55 |
s | 秒为单位的时间戳 | 23144232 |
p | 上午还是下午 | 下午 |
我们可以使用以下三个类去进行格式化,其中可能存在不支持的情况,比如LocalDateTime不支持c:
System.out.println("tc", new Date());
System.out.println("tc", ZonedDateTime.now());
System.out.println("tF", LocalDateTime.now();
JUL(Java Util Logging)加载配置文件的源码解读如下:
首先,JUL使用LogManager
类来管理日志记录器和配置信息。LogManager
是一个单例类,通过LogManager.getLogManager()
方法获取实例。
当调用LogManager.getLogManager().readConfiguration(inputStream)
方法时,会执行以下步骤:
LogManager
类的readConfiguration
方法会创建一个新的Properties
对象,用于存储配置信息。Properties
对象的load
方法,将配置文件的内容加载到Properties
对象中。这里的inputStream
参数是通过getClass().getClassLoader().getResourceAsStream("logging.properties")
获取的,它会从类路径中读取logging.properties
文件的内容。LogManager
会根据配置信息更新日志记录器的配置。具体的配置信息包括日志级别、日志输出目标等。如果没有指定配置文件或加载配置文件失败,JUL会使用默认的配置。默认的配置包括将日志输出到控制台,并使用默认的日志级别。
总结:JUL加载配置文件的源码主要涉及LogManager
类的readConfiguration
方法,该方法通过读取配置文件的内容,并根据配置信息更新日志记录器的配置。
在JUL(Java Util Logging)中,加载配置文件的常规步骤如下:
1)创建一个名为logging.properties
的文本文件,该文件包含了JUL的配置信息。
2)将logging.properties
文件放置在类路径下,通常是放在src/main/resources
目录下。
3)在代码中使用以下代码片段来加载配置文件:
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("logging.properties");
if (inputStream != null) {
LogManager.getLogManager().readConfiguration(inputStream);
}
演示:
1)在src/main/resources目录下创建 logging.properties 文件,内容如下:
# 设置根日志记录器的级别为FINE
.level=FINE
handlers=java.util.logging.ConsoleHandler,java.util.logging.FileHandler
# 配置控制台处理器
java.util.logging.ConsoleHandler.level=FINE
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
# 配置文件处理器
java.util.logging.FileHandler.level=FINE
# 指定日志文件的路径和名称模式
java.util.logging.FileHandler.pattern=%h/java%u.log
# 指定日志文件的大小限制
java.util.logging.FileHandler.limit=50000
# 指定日志文件的数量限制
java.util.logging.FileHandler.count=1
# 指定日志消息的格式化器
java.util.logging.FileHandler.formatter=java.util.logging.XMLFormatter
2)编写代码读取配置文件
// 读取配置文件
@Test
public void readConfig() {
LogManager manager = LogManager.getLogManager();
// 获取配置文件
InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("logging.properties");
// InputStream inputStream = getClass().getClassLoader().getResourceAsStream("logging.properties");
try {
manager.readConfiguration(inputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
Logger logger = Logger.getLogger(JULTest.class.getName());
// 默认的日志打印级别为INFO,加载我们自己的配置文件后级别为FINE
// 如果能打印fine级别的日志,证明加载成功
logger.fine("fine");
}
}
# 设置根日志记录器的级别为INFO
.level=INFO
# 配置控制台处理器、文件处理器
handlers=java.util.logging.ConsoleHandler,java.util.logging.FileHandler
# 为RootLogger根日志记录器配置handlers,指定处理器的列表,多个处理器之间使用逗号分隔
# 配置文件处理器的相关属性
# 设置日志输出级别
java.util.logging.FileHandler.level=INFO
# 指定日志文件的路径和名称模式 %h 表示用户家目录
java.util.logging.FileHandler.pattern=%h/java%u.log
# 指定日志文件的大小限制
java.util.logging.FileHandler.limit=50000
# 指定日志文件的数量限制(不能小于1)
java.util.logging.FileHandler.count=1
# 指定日志文件是追加写
java.util.logging.FileHandler.append
# 指定日志消息的格式化器
java.util.logging.FileHandler.formatter=java.util.logging.XMLFormatter
# 配置控制台处理器的相关属性
# 设置日志输出级别
java.util.logging.ConsoleHandler.level=INFO
# 指定日志消息的格式化器
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
# 指定日志消息的格式
java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n
logging.properites
在配置文件中加入如下配置
# 设置 com.clear 日志记录器
com.clear.handlers=java.util.logging.ConsoleHandler
com.clear.level=WARNING
# 禁用父类的Handler
com.clear.useParentHandlers=false
为了让我们自定义的日志记录器生效,必须在获取Logger实例之前加载配置文件
我们可以采用静态代码块的方式实现
public class JULTest {
static {
LogManager manager = LogManager.getLogManager();
// 获取配置文件
InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("logging.properties");
try {
manager.readConfiguration(inputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 读取配置文件
@Test
public void readConfig() {
// 此时,com.clear包下,只会打印WARNING级别及以上的日志
LOGGER.warning("warning");
LOGGER.fine("fine");
}
}
在JUL(Java Util Logging)中,过滤器用于过滤日志消息,只有符合特定条件的日志消息才会被记录或处理。JUL提供了两种类型的过滤器:日志记录器过滤器(Logger Filter)和处理器过滤器(Handler Filter)。
日志记录器过滤器(Logger Filter):
java.util.logging.Filter
接口定义了日志记录器过滤器的方法boolean isLoggable(LogRecord record)
,该方法接收一个LogRecord
对象作为参数,并返回一个布尔值,表示是否允许记录该日志消息。Filter
接口来自定义日志记录器过滤器,然后将其设置给特定的日志记录器。处理器过滤器(Handler Filter):
java.util.logging.Handler
接口定义了处理器过滤器的方法boolean isLoggable(LogRecord record)
,该方法接收一个LogRecord
对象作为参数,并返回一个布尔值,表示是否允许处理该日志消息。Handler
接口来自定义处理器过滤器,然后将其设置给特定的处理器。演示:
public class FilterTest {
public static final Logger LOGGER = Logger.getLogger(FilterTest.class.getName());
// 日志记录器过滤器测试
@Test
public void loggerFilterTest() {
// 给日志记录器设置过滤器
LOGGER.setFilter(logRecord -> // 只打印级别>= INFO的日志
logRecord.getLevel().intValue() >= Level.INFO.intValue());
LOGGER.info("hello");
LOGGER.fine("world");
LOGGER.finer("apache");
}
// 处理器过滤器测试
@Test
public void handlerFilterTest() {
// 禁用父级处理器
LOGGER.setUseParentHandlers(false);
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setFormatter(new SimpleFormatter());
consoleHandler.setLevel(Level.ALL);
// 给处理器设置过滤器
consoleHandler.setFilter(logRecord -> // 只打印a开头的日志
logRecord.getMessage().startsWith("a"));
// 将处理器Handler加入到日志记录器Logger中
LOGGER.addHandler(consoleHandler);
LOGGER.info("hello");
LOGGER.info("world");
LOGGER.info("apache");
}
}
在JUL(Java Util Logging)中,可以通过配置日志记录器的级别和处理器来打印异常堆栈信息。
配置日志记录器的级别:
Logger.setLevel(Level level)
方法来设置日志记录器的级别。Level.SEVERE
或更低的级别,以便记录所有的异常信息。配置处理器:
Handler.setFormatter(Formatter formatter)
方法来设置处理器的格式化器。SimpleFormatter
类作为格式化器,它会将日志消息格式化为包含时间戳、日志级别和消息内容的字符串。演示:
将异常的堆栈信息打印至控制台和文件中
package com.clear;
import org.junit.Test;
import java.io.IOException;
import java.util.logging.*;
public class ExceptionTest {
public static final Logger LOGGER = Logger.getLogger(ExceptionTest.class.getName());
@Test
public void printStackTest() throws IOException {
// 设置日志记录器的级别为FINER
LOGGER.setLevel(Level.FINER);
// 创建处理器
Handler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(Level.FINER);
// 设置处理器的格式化器
consoleHandler.setFormatter(new SimpleFormatter());
Handler fileHandler = new FileHandler();
fileHandler.setLevel(Level.FINER);
fileHandler.setFormatter(new SimpleFormatter());
// 将处理器添加到日志记录器
LOGGER.addHandler(consoleHandler);
LOGGER.addHandler(fileHandler);
try {
// 抛出异常
int a = 10 / 0;
} catch (ArithmeticException e) {
// 打印异常堆栈信息
LOGGER.throwing(ExceptionTest.class.getName(), "printStackTest", e);
}
}
}
LOG4J是一个流行的Java日志框架,它提供了强大的日志记录和管理功能。LOG4J可以帮助开发人员在应用程序中实现灵活的日志记录,并提供了多种配置选项和输出格式。
LOG4J的一些主要特点和用法:
log4j.properties
,也可以使用XML格式的配置文件log4j.xml
。DEBUG
、INFO
、WARN
、ERROR
和FATAL
。可以根据需要设置不同的日志级别,以控制日志记录的详细程度。PatternLayout
,也可以自定义格式化器。1)添加POM依赖
添加LOG4J依赖:在项目的构建文件(如Maven的pom.xml)中添加LOG4J的依赖项
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
2)创建LOG4J配置文件
创建LOG4J配置文件:在项目的资源目录下创建一个LOG4J的配置文件,命名为log4j.properties
或log4j.xml
。配置文件中定义了日志记录器、日志级别、输出目标等配置信息
参考如下:
# 设置根日志级别为INFO
log4j.rootLogger=INFO, console
# 定义控制台输出目标
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
3)在代码中使用
在代码中使用LOG4J:在需要记录日志的类中,通过获取LOG4J的Logger对象来进行日志记录
import org.apache.log4j.Logger;
import org.junit.Test;
public class Log4jTest {
// 获取Logger实例
public static final Logger logger = Logger.getLogger(Log4jTest.class);
@Test
public void test1() {
// 日志输出
logger.fatal("fatal");
logger.error("error");
logger.warn("warn");
logger.info("info");
logger.debug("debug");
logger.trace("trace");
}
}
如果我们不添加配置文件,会报如下错误:
D:\software\jdk1.8.0_131\bin\java.exe ...
log4j:WARN No appenders could be found for logger (com.clear.Log4jTest).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
JUL没有添加配置文件,不会报错,是因为他是Java原生的日志框架,拥有默认的配置文件
如果我们没有添加配置文件,加上如下方法也是可以的,他会初始化一个默认的配置文件
BasicConfigurator.configure();
4)运行和查看日志
运行项目时,LOG4J会根据配置文件中的设置进行日志记录。日志消息将根据配置的输出目标(如控制台、文件)进行输出。可以根据配置文件中的日志级别设置,过滤和查看不同级别的日志消息。
BasicConfigurator.configure();
当我们在代码中加上如上一行时,可以在使用log4j时不需要添加配置文件
演示:
// 获取Logger实例
public static final Logger logger = Logger.getLogger(Log4jTest.class);
@Test
public void test1() {
BasicConfigurator.configure();
// 日志输出
logger.fatal("fatal");
logger.error("error");
logger.warn("warn");
logger.info("info");
logger.debug("debug");
logger.trace("trace");
}
结果如下:
0 [main] FATAL com.clear.Log4jTest - fatal
1 [main] ERROR com.clear.Log4jTest - error
1 [main] WARN com.clear.Log4jTest - warn
1 [main] INFO com.clear.Log4jTest - info
1 [main] DEBUG com.clear.Log4jTest - debug
源码解读:
BasicConfigurator.configure(); 这一行代码会给RootLogger加上一个控制台的输出源,类似于JUL的JHandler
在LOG4J中,BasicConfigurator.configure()
是一个静态方法,用于简单配置日志记录器。它会自动创建一个默认的日志记录器,并将其配置为使用控制台输出日志消息。
BasicConfigurator.configure()
方法的主要作用是:
1)创建一个默认的日志记录器:该方法会创建一个名为"root"的日志记录器,并将其设置为根日志记录器。
2)配置日志记录器:默认的配置会将日志级别设置为DEBUG,并将一个ConsoleAppender
添加到日志记录器中。
3)设置日志输出格式:默认的配置会使用PatternLayout
来定义日志消息的输出格式。
使用BasicConfigurator.configure()
方法可以快速启动LOG4J的基本配置,方便进行简单的日志记录。但是,它的配置选项有限,如果需要更复杂的配置,可以使用自定义的配置文件或编程方式进行配置。
public static void configure() {
Logger root = Logger.getRootLogger();
root.addAppender(new ConsoleAppender(
new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN)));
}
// 其中 PatternLayout.TTCC_CONVERSION_PATTERN)
public final static String TTCC_CONVERSION_PATTERN = "%r [%t] %p %c %x - %m%n"
在Log4j中,日志级别从低到高分别为(与JUL略有不同)
和JUL一样,也有两个特殊的级别:
可以根据需要选择适当的日志级别来记录日志,通常建议在生产环境中使用INFO级别或更高级别,而在开发和调试阶段使用DEBUG级别。
Logger(日志记录器):用于记录日志消息的主要组件。每个Logger对象都与一个特定的日志记录器名称相关联,可以通过该名称来标识不同的日志记录器。
实例的命名就是类"XX"的 full quailied name(类的全限定名),Logger的名字对大小写敏感,其命名有继承机制:例如:名称为 com.clear.service 的logger 会继承自名称为 com.clear 的logger,与JUL一致。
Log4j有一个特殊的Logger叫做"root",它是所有Logger的根,所有的Logger都会直接或间接继承自它。root logger可以用Logger.getRootLogger()方法获取。在JUL中也有一个 名为.
的根
Appender(日志输出器):与JUL的Handler类似,用于将日志消息输出到不同的目标,如控制台、文件、数据库等。Log4j提供了多种类型的Appender,可以根据需求选择合适的输出目标。
输出端类型 | 作用 |
---|---|
ConsoleAppender | 将日志输出到控制台 |
FileAppender | 将日志输出到文件中 |
DailyRollingFileAppender | 将日志输出到日志文件中,并且每天输出到一个新的文件 |
RollingFileAppender | 将日志输出到日志文件中,并指定文件的尺寸,当文件大小到达指定尺寸时,会自动把文件改名,同时产生一个新的文件 |
JDBCAppender | 将日志输出到数据集中 |
例如:
// 配置一个控制台输出源
ConsoleAppender consoleAppender = new ConsoleAppender();
consoleAppender.setName("clear");
consoleAppender.setWriter(new PrintWriter(System.out));
logger.addAppender(consoleAppender);
Layout(日志布局器):用于定义日志消息的格式。Layout将日志消息转换为特定的字符串格式,以便输出到Appender指定的目标。
Level(日志级别):用于控制日志消息的输出级别。可以根据需要设置不同的日志级别,只有达到指定级别及以上的日志消息才会被记录和输出。
Configuration(配置):用于配置Log4j的各个组件。可以通过配置文件或编程方式来配置Log4j,包括设置日志级别、指定Appender和Layout等。
总结
这些核心组件共同工作,实现了灵活、可配置的日志记录功能。通过合理配置和使用这些组件,可以实现对日志消息的记录、输出和管理。
在Log4j中,可以通过编程方式进行配置
// 获取Logger实例
public static final Logger logger = Logger.getLogger(Log4jTest.class);
@Test
public void test2(){
// 获取根Logger实例
Logger rootLogger = Logger.getRootLogger();
// 创建一个ConsoleAppender对象
ConsoleAppender consoleAppender = new ConsoleAppender();
// 设置输出流为标准输出流
consoleAppender.setWriter(new PrintWriter(System.out));
Layout layout = new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN);
// 将consoleAppender添加到根Logger实例中,以便将日志输出到控制台
consoleAppender.setLayout(layout);
rootLogger.addAppender(consoleAppender);
logger.warn("warn");
logger.info("info");
logger.debug("debug");
}
Log4j的配置文件是一个用于配置日志记录器的文本文件,它定义了日志记录器的行为和输出方式。Log4j支持多种配置文件格式,包括XML、JSON和属性文件
配置文件的命名通常为log4j.properties或log4j.xml,可以放置在类路径下或指定的位置。在应用程序启动时,Log4j会自动加载并解析配置文件,根据配置文件的定义进行日志记录
例如:
# 设置根日志级别为INFO,并将日志输出到Console和File(其实这里的名字可以随意起,影响不大)
log4j.rootLogger=INFO, Console, File
# 定义控制台输出
log4j.appender.Console=org.apache.log4j.ConsoleAppender
# 指定控制台输出的Layout为 PatternLayout
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
# 设置控制台输出的日志消息格式
log4j.appender.Console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5p %c{1} - %m%n
# 定义文件输出
log4j.appender.File=org.apache.log4j.RollingFileAppender
# 指定日志文件的路径和文件名(这里定义的是相对路径,可根据实际需要进行修改)
log4j.appender.File.File=logs/app.log
# 设置日志文件的最大大小为10MB
log4j.appender.File.MaxFileSize=10MB
# 设置保留的日志文件备份数量为10个
log4j.appender.File.MaxBackupIndex=10
# 指定文件输出的 Layout为 PatternLayout
log4j.appender.File.layout=org.apache.log4j.PatternLayout
# 设置文件输出的日志消息格式
log4j.appender.File.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5p %c{1} - %m%n
说明
控制台输出使用PatternLayout进行格式化,文件输出使用RollingFileAppender进行日志文件的滚动。日志文件将按照文件大小进行切割,每个文件最大为10MB,最多保留10个备份文件。
如果需要分别控制输出到控制台和文件中的日志级别,则如下:
# 输出到控制台的日志级别
log4j.rootLogger=INFO, Console
# 输出到文件的日志级别(自定义Logger)
log4j.logger.myapp=WARN, File
# 禁止将myapp Logger的日志事件传递给父Logger(在自定义Logger时需要配置,否则会重复打印日志)
log4j.additivity.myapp=false
# 控制台输出的配置
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
# 文件输出的配置
log4j.appender.File=org.apache.log4j.RollingFileAppender
log4j.appender.File.File=/path/to/log/file.log
log4j.appender.File.MaxFileSize=10MB
log4j.appender.File.MaxBackupIndex=10
log4j.appender.File.layout=org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
Console>
<RollingFile name="File" fileName="logs/app.log"
filePattern="logs/app-%d{yyyy-MM-dd}.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
<Policies>
<SizeBasedTriggeringPolicy size="10MB" />
Policies>
<DefaultRolloverStrategy max="10" />
RollingFile>
Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="Console" />
<AppenderRef ref="File" />
Root>
Loggers>
Configuration>
:指定XML文件的版本和编码。
:设置Log4j的全局配置,其中status
属性设置为WARN
表示只输出警告级别以上的日志信息。
:定义日志输出的Appenders。
:定义控制台输出的Appender,其中name
属性为Appender的名称,target
属性指定输出目标为系统标准输出。
:设置控制台输出的日志消息格式。
:定义文件输出的Appender,其中name
属性为Appender的名称,fileName
属性指定日志文件的路径和文件名,filePattern
属性指定日志文件的滚动模式。
:定义日志文件滚动的策略。
:设置日志文件的滚动触发策略为基于文件大小,当日志文件达到10MB时触发滚动。
:设置日志文件的滚动策略为默认策略,最多保留10个备份文件。
:定义日志记录器。
:设置根日志记录器的级别为INFO。
和
:将控制台输出和文件输出的Appender引用添加到根日志记录器中。log4j 采用类似 C 语言的 printf 函数的打印格式格式化日志信息,具体的占位符及其含义如下:
转换符 | 说明 |
---|---|
%m | 输出代码中指定的日志信息 |
%p | 输出优先级,如 DEBUG、INFO 等 |
%n | 换行符(Windows平台的换行符为 “\n”,Unix 平台为 “\n”) |
%r | 输出自应用启动到输出该 log 信息耗费的毫秒数 |
%c | 输出打印语句所属的类的全限定名 |
%t | 输出产生该日志的线程全名 |
%d | 输出服务器当前时间,默认格式为 ISO8601,也可以在后面指定格式。 如:%d{yyyy-MM-dd HH:mm:ss} |
%l | 输出日志时间发生的位置,包括类名、发生的线程,以及在代码中的行数,如:Test.main(Test.java:10) |
%F | 输出日志消息产生时所在的文件名称 |
%L | 输出代码中的行号 |
%x | 输出和当前线程相关的 NDC(嵌套诊断环境) |
%% | 输出一个 “%” 字符 |
可以在 % 与字符之间加上修饰符来控制最小宽度、最大宽度和文本的对其方式。如:
PatternLayout这个实现类,可以根据特定的占位符进行转换,类似于JUL,但是又不一样,如下是他的构造器:
public PatternLayout(String pattern) {
this.pattern = pattern;
head = createPatternParser((pattern == null) ? DEFAULT_CONVERSION_PATTERN :
pattern).parse();
}
传入一个pattern字符,他会根据pattern字符串创建一个链表
接着查看解析器:
protected PatternParser createPatternParser(String pattern) {
return new PatternParser(pattern);
}
查看parse()方法:
// 如下是这个方法的简略版
public
PatternConverter parse() {
char c;
i = 0;
while(i < patternLength) {
c = pattern.charAt(i++);
switch(state) {
.....
finalizeConverter(c); // 这个方法最重要的就是这个
......
return head;
}
下面是finalizeConverter()方法:
protected
void finalizeConverter(char c) {
PatternConverter pc = null;
switch(c) {
case 'c':
pc = new CategoryPatternConverter(formattingInfo,
extractPrecisionOption());
//LogLog.debug("CATEGORY converter.");
//formattingInfo.dump();
currentLiteral.setLength(0);
break;
// 处理类名的转换器
case 'C':
pc = new ClassNamePatternConverter(formattingInfo,
extractPrecisionOption());
//LogLog.debug("CLASS_NAME converter.");
//formattingInfo.dump();
currentLiteral.setLength(0);
break;
// 处理时间的转换器
case 'd':
String dateFormatStr = AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT;
DateFormat df;
String dOpt = extractOption();
if(dOpt != null)
dateFormatStr = dOpt;
if(dateFormatStr.equalsIgnoreCase(
AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT))
df = new ISO8601DateFormat();
else if(dateFormatStr.equalsIgnoreCase(
AbsoluteTimeDateFormat.ABS_TIME_DATE_FORMAT))
df = new AbsoluteTimeDateFormat();
else if(dateFormatStr.equalsIgnoreCase(
AbsoluteTimeDateFormat.DATE_AND_TIME_DATE_FORMAT))
df = new DateTimeDateFormat();
else {
try {
df = new SimpleDateFormat(dateFormatStr);
}
catch (IllegalArgumentException e) {
LogLog.error("Could not instantiate SimpleDateFormat with " +
dateFormatStr, e);
df = (DateFormat) OptionConverter.instantiateByClassName(
"org.apache.log4j.helpers.ISO8601DateFormat",
DateFormat.class, null);
}
}
pc = new DatePatternConverter(formattingInfo, df);
//LogLog.debug("DATE converter {"+dateFormatStr+"}.");
//formattingInfo.dump();
currentLiteral.setLength(0);
break;
// 输出日志时间发生的位置,包括类名、线程、及代码所在行数
case 'F':
pc = new LocationPatternConverter(formattingInfo,
FILE_LOCATION_CONVERTER);
//LogLog.debug("File name converter.");
//formattingInfo.dump();
currentLiteral.setLength(0);
break;
case 'l':
pc = new LocationPatternConverter(formattingInfo,
FULL_LOCATION_CONVERTER);
//LogLog.debug("Location converter.");
//formattingInfo.dump();
currentLiteral.setLength(0);
break;
case 'L':
pc = new LocationPatternConverter(formattingInfo,
LINE_LOCATION_CONVERTER);
//LogLog.debug("LINE NUMBER converter.");
//formattingInfo.dump();
currentLiteral.setLength(0);
break;
case 'm':
pc = new BasicPatternConverter(formattingInfo, MESSAGE_CONVERTER);
//LogLog.debug("MESSAGE converter.");
//formattingInfo.dump();
currentLiteral.setLength(0);
break;
case 'M':
pc = new LocationPatternConverter(formattingInfo,
METHOD_LOCATION_CONVERTER);
//LogLog.debug("METHOD converter.");
//formattingInfo.dump();
currentLiteral.setLength(0);
break;
case 'p':
pc = new BasicPatternConverter(formattingInfo, LEVEL_CONVERTER);
//LogLog.debug("LEVEL converter.");
//formattingInfo.dump();
currentLiteral.setLength(0);
break;
case 'r':
pc = new BasicPatternConverter(formattingInfo,
RELATIVE_TIME_CONVERTER);
//LogLog.debug("RELATIVE time converter.");
//formattingInfo.dump();
currentLiteral.setLength(0);
break;
case 't':
pc = new BasicPatternConverter(formattingInfo, THREAD_CONVERTER);
//LogLog.debug("THREAD converter.");
//formattingInfo.dump();
currentLiteral.setLength(0);
break;
/*case 'u':
if(i < patternLength) {
char cNext = pattern.charAt(i);
if(cNext >= '0' && cNext <= '9') {
pc = new UserFieldPatternConverter(formattingInfo, cNext - '0');
LogLog.debug("USER converter ["+cNext+"].");
formattingInfo.dump();
currentLiteral.setLength(0);
i++;
}
else
LogLog.error("Unexpected char" +cNext+" at position "+i);
}
break;*/
case 'x':
pc = new BasicPatternConverter(formattingInfo, NDC_CONVERTER);
//LogLog.debug("NDC converter.");
currentLiteral.setLength(0);
break;
case 'X':
String xOpt = extractOption();
pc = new MDCPatternConverter(formattingInfo, xOpt);
currentLiteral.setLength(0);
break;
default:
LogLog.error("Unexpected char [" +c+"] at position "+i
+" in conversion patterrn.");
pc = new LiteralPatternConverter(currentLiteral.toString());
currentLiteral.setLength(0);
}
addConverter(pc);
}
下面就是一个典型的链表结构的创建:
protected void addConverter(PatternConverter pc) {
currentLiteral.setLength(0);
// Add the pattern converter to the list.
addToList(pc);
// Next pattern is assumed to be a literal.
state = LITERAL_STATE;
// Reset formatting info
formattingInfo.reset();
}
private void addToList(PatternConverter pc) {
if(head == null) {
head = tail = pc;
} else {
tail.next = pc;
tail = pc;
}
}
构建完转换器链表之后,就是循环这个链表,依次处理对应的占位符,他的核心格式化的方法也是format方法,在format方法中通过一个转换器链来完成转化:
public static format(LoggingEvent event){
// 在format方法中是通过转换器链来完成的
PatternConverter c = head;
while(c != null){
// 这一句是核心,第一个参数是StringBuilder,第二个参数是LoggingEvent
c.format(sbuf, event);
c = c.next;
}
return sbuf.toString;
}
这里就是通过一个pattern字符串,这个字符串可能张这个样子(%-d{yyyy-MM-dd HH:mm:ss} [%t:%r] -[%p] %m%n),使用createPatternParser().parse()构建第一个处理器的链表,这个每个处理器处理一个占位符,例如:%d
进入 c.format()方法,我们会进入一个抽象类 PatternConverter 中的format方法,该方法的核心代码如下:
public void format(StringBuilder sbuf, LoggingEvent e){
// 核心语句如下
String s = convert(e);
}
在Log4j中,可以通过配置来实现日志的分割,即将日志按照一定的规则拆分成多个文件,以便于管理和查看。
Log4j提供了多种方式来实现日志的分割,其中常用的方式包括:
源码中,FileAppender类有几个子类,用于实现日志文件的滚动、分割:
DailyRollingFileAppender
:按照日期滚动的Appender。它可以按照一定的时间间隔(如每天、每周、每月)生成新的日志文件RollingFileAppender
:按照文件大小滚动的Appender。它可以在日志文件达到一定大小时生成新的日志文件
ExternallyRolledFileAppende
:外部滚动的Appender。它允许外部程序控制日志文件的滚动。可以通过外部程序的信号或命令来触发日志文件的滚动。如下是使用DailyRollingFileAppender
和RollingFileAppender
来实现日志文件的分割和滚动:
# 设置根日志级别为INFO
log4j.rootLogger=INFO, dailyRollingFile, rollingFile
# 配置DailyRollingFileAppender
log4j.appender.dailyRollingFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyRollingFile.File=logs/application.log
# 指定按照日期滚动的规则
log4j.appender.dailyRollingFile.DatePattern='.'yyyy-MM-dd
log4j.appender.dailyRollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.dailyRollingFile.layout.ConversionPattern=%d{HH:mm:ss.SSS} [%t] %-5p %c{1} - %m%n
# 配置RollingFileAppender
log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender
log4j.appender.rollingFile.File=logs/application.log
# 日志文件到达10MB时生成新文件
log4j.appender.rollingFile.MaxFileSize=10MB
# 保留最多5个备份文件
log4j.appender.rollingFile.MaxBackupIndex=5
log4j.appender.rollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.rollingFile.layout.ConversionPattern=%d{HH:mm:ss.SSS} [%t] %-5p %c{1} - %m%n
# 设置日志输出格式
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{HH:mm:ss.SSS} [%t] %-5p %c{1} - %m%n
# 配置日志级别
log4j.logger.com.example=DEBUG
# 关闭Log4j内部日志输出
log4j.logger.org.apache.log4j=OFF
在Java源代码中,Log4j的日志分割功能由RollingFileAppender
类实现。它通过检查日志文件的大小或时间来决定是否需要生成新的日志文件。
例如:
import org.apache.log4j.RollingFileAppender;
import org.apache.log4j.PatternLayout;
RollingFileAppender appender = new RollingFileAppender();
appender.setFile("logs/application.log");
appender.setLayout(new PatternLayout("%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"));
appender.setMaxFileSize("10MB");
appender.setMaxBackupIndex(5);
appender.activateOptions();
Logger logger = Logger.getLogger("com.clear.MyClass");
logger.addAppender(appender);
log4j.properties
# 设置根Logger的日志级别和输出目标
log4j.rootLogger=INFO, console
# 设置自定义Logger的日志级别和输出目标
log4j.logger.com.clear=DEBUG, myappFile
# 禁止将myapp Logger的日志事件传递给父Logger(在自定义Logger时需要配置,否则会重复打印日志)
log4j.additivity.com.clear=false
# 设置输出目标为控制台
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d [%t] %-5p %c - %m%n
# 设置输出目标为文件
log4j.appender.myappFile=org.apache.log4j.FileAppender
log4j.appender.myappFile.File=D:/test/myapp.log
log4j.appender.myappFile.layout=org.apache.log4j.PatternLayout
log4j.appender.myappFile.layout.ConversionPattern=%d [%t] %-5p %c - %m%n
测试
public static final Logger LOGGER = Logger.getLogger(DIY.class);
@Test
public void test(){
// 测试自定义Logger
// 我们配置文件中设置了DEBUG级别以及上的才输出到日志文件
LOGGER.trace("trace");
LOGGER.debug("debug");
LOGGER.info("info");
LOGGER.warn("warn");
LOGGER.error("error");
LOGGER.fatal("fatal");
}
结果
D:\test\myapp.log 内容如下,确实只打印了DEBUG及以上的日志
2023-07-21 21:33:00,514 [main] DEBUG com.clear.DIY - debug
2023-07-21 21:33:00,515 [main] INFO com.clear.DIY - info
2023-07-21 21:33:00,515 [main] WARN com.clear.DIY - warn
2023-07-21 21:33:00,515 [main] ERROR com.clear.DIY - error
2023-07-21 21:33:00,515 [main] FATAL com.clear.DIY - fatal
日志门面(Logging Facade)是一种设计模式,用于在应用程序中实现日志记录的抽象层。它的作用是提供一个统一的接口,使应用程序可以使用不同的日志库进行日志记录,而不需要直接依赖于特定的日志库。
日志门面的作用有以下几个方面:
总之,日志门面的作用是提供一个抽象层,使应用程序可以灵活地使用不同的日志库,并提供统一的日志记录接口和功能,以提高代码的可维护性、可读性和灵活性
【强制】应用中不可直接使用日志系统(Log4、Logback)中的API ,而应依赖使用日志框架(SLF4)、JCL–Jakarta Commons Logging)中的API,使用门面模式的日志框架 ,有利于维护和各个类的日志处理方式统一。
说明:日志框架 (SLF4)JCL–Jakarta Commons Logging)的使用方式(推荐使用 SLF4J)
// 使用 SLF4J:
import org.slf4j Logger;
import org slf4j. LoggerFactory;
private static final Logger logger = LoggerFactory getLogger(Test.class);
// 使用 JCL:
import org.apache.commons.logging.Log;
import org.apache.commons.logging. LogFactory;
private static final Log log = LogFactory getLog(Test.class);
【强制】所有日志文件至少保存 15 天,因为有些异常具备以“周” 为频次发生的特点。对于当天日志,以“应用名.log〞 来保存,保存在/home/admin/应用名/logs/目录下,过往日志格式为:{logname.log}.{保存日期},日期格式:yyyy-MM-dd
正例:以aap 应用为例,日志保存在/home/admin/aapserver/logs/aap.log,历史日志名称为aap.log.2016-08-01
【强制】根据国家法律,网络运行状态、网络安全事件、个人敏感信息操作等相关记录,留存的日志不少于六个月,并且进行网络多机备份。
【强制】应用中的扩展日志(如打点、临时监控、访问日志等) 命名方式:
appName log Type Iog Name.log。 logType:日志类型
如stats/monitor/access 等; logName:日志描述。这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。
说明:推荐对日志进行分类,如将错误日志和业务日志分开存放,便于开发人员查看,也便于通过日志对系统进行及时监控。
正例:mppserver 应用中单独监控时区转换异常,如:mppserver_monitor_timeZoneConvert.log
【强制】在日志输出时 ,字符串变量之间的拼接使用占位符的方式。
说明:因为String 字符串的拼接会使用 StringBuilder 的append0方式,有一定的性能损耗。使用占位符仅是替换动作,可以有效提升性能。
正例:
```java
logger.debug("Processing trade with id: ( and symbol: (", id, symbol);
```
【强制】对于 trace/debue/info 级别的日志输出,必须进行日志级别的开关判断。
说明:虽然在 debug(参数的方法体内第一行代码 isDisabled(Level.DEBUGLINT)为真时( S1f4j的常见实现Log4j 和Logback),就直接return ,但是参数可能会进行字符串拼接运算。此外,如果 debug(getNameo这种参数内有getName0方法调用,无谓浪费方法调用的开销。
正例:
// 如果判断为真,那么可以输出 trace 和 debug 级别的日志
if (logger.isDebugEnabled0) {
logger.debug(“Current ID is: & and name is: , id, getNamel);
}
【强制】避免重复打印日志,浪费磁密空间 ,务必在日志配置文件中设置 additivity =false。
正例:
<logger name=”com.taobao.dubbo.config” additivity="false”>
【强制】生产环境禁止直接使用 System.out 或System.err 输出日志或使用e.printStackTrace(打印昇常堆桟。
【强制】异常信息应该包括两类信息 :案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 throws 往上抛出。
正例:
logger.error("inputParams:{} and errorMessage:{}”,各类参数或者对系 toString(), e.getMessage(), e);
【强制】日志打印时禁止直接用 JSON 工具将对象转换成 String。
【推荐】递慎地记录日志。生产环境禁止输出 debug 日志:有选择地输出 info 日志:如果使用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。
【推荐】可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。如非必要,请不要在此场景打出 error级别,避免频繁报警。
【推荐】尽量用英文来描述日志错误信息,如果日志中的错误信息用英文描述不清楚的话使用中文描述即可,否则容易产生歧义。
为什么要使用日志规约:
简单日志门面(Simple Logging Facade For Java)SLF4J主要是为了给Java日志访问提供一套标准、规范的API框架,其意义主要是提供接口,具体的实现交由日志框架,例如log4j、logback等。当然slf4j 自己也提供了功能较为简单的日志实现,但是我们一般不使用。
对于一般的Java项目而言,我们会选择 slf4j-api 作为门面,再配上具体的日志实现(log4j、logback等),中间使用桥接器完成桥接。
SLF4J主要提供了两大功能:
SLF4J(Simple Logging Facade for Java)是一个日志门面框架,它提供了一组简单的接口,用于在Java应用程序中进行日志记录。SLF4J本身提供了一个简单的日志实现,称为SimpleLogger。SimpleLogger是SLF4J的默认日志实现,它不需要额外的依赖,可以直接在项目中使用
要使用SimpleLogger,只需按照以下步骤进行配置:
1)添加SLF4J依赖:在项目的构建文件(如pom.xml)中添加SLF4J的依赖。
pom.xml
<dependencies>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.32version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-simpleartifactId>
<version>1.7.32version>
dependency>
dependencies>
2)配置日志级别:在项目的根目录下创建一个名为simplelogger.properties
的文件,并添加以下内容:
这一步可以不配置
org.slf4j.simpleLogger.defaultLogLevel=debug
上述配置将日志级别设置为DEBUG,你可以根据需要将其调整为其他级别,如INFO、WARN、ERROR等。
3)在代码中使用SLF4J接口进行日志记录:在需要记录日志的地方,使用SLF4J提供的接口进行日志记录。
例如:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SLF4JTest {
// 获取Logger实例
public static final Logger logger = LoggerFactory.getLogger(SLF4JTest.class);
public static void main(String[] args) {
logger.debug("Debug message");
logger.info("Info message");
logger.warn("Warning message");
logger.error("Error message");
}
}
通过以上步骤,就可以在Java应用程序中使用SLF4J的SimpleLogger进行日志记录。SimpleLogger的实现非常简单,适用于简单的应用程序或测试环境。如果需要更高级的日志功能,可以考虑使用其他日志实现框架,如Logback或Log4j2
SLF4J支持各种日志框架。SLF4J发行版附带了几个称为“SLF4J绑定”的jar文件,每个绑定对应一个受支持的框架。
使用SLF4J的日志绑定流程:
<dependencies>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.32version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-jdk14artifactId>
<version>1.7.32version>
dependency>
dependencies>
<dependencies>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.32version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.32version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
dependencies>
说明
要切换日志框架,只需要替换类路径上的slf4j绑定。例如,要从java.util.logging(jul)切换到log4j,只需要将 slf4j-jdk14-1.7.32.jar 替换为 slf4j-log4j12-1.7.32.jar 即可。
SLF4J不依赖于任何特殊的类加载。实际上,每个SLF4J绑定在编译时都是硬连线的,以使用一个且一个特定的日志记录框架。例如, slf4j-log4j12-1.7.32.jar绑定在编译时绑定以使用log4j
SLF4J提供了日志桥接(Logging Bridges)的功能,用于将其他日志框架的日志记录转发到SLF4J接口。这样可以在项目中使用SLF4J进行日志记录,同时仍然能够使用其他日志框架的功能
通常,您依赖的某些组件依赖于SLF4J以外的日志记录APl。您也可以假设这些組件在不久的将来不会切换到SLF4J。为了解决这种情况,SLF4J附带了几个桥接模块,这些模共将对log4j,JCL和 java.util.logging API 的调用重定向,就好像它们是对SLF4J API一样。
就是你还用log4j的api写代码,但是具体的实现给你抽离了,我们依赖了一个中间层,这个层其实是用旧的的api操作slf4j,而不是操作具体的实现。
桥接解決的是项目中日志的遗留问题,当系统中存在之前的日志API,可以通过桥接转换到slf4j的实现
SLF4J提供的桥接组件
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>log4j-over-slf4jartifactId>
<version>1.7.32version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>jul-to-slf4jartifactId>
<version>1.7.32version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>jcl-over-slf4jartifactId>
<version>1.7.32version>
dependency>
注意:
全称为Jakarta Conmons Logging,是Apache提供的一个通用日志API。该日志门面的使用并不是很广泛。
它是为“所有的Java日志实现"提供一个统一的接口,它自身也提供一个日志的实现,但是功能非常的弱 (Simplelog)。所以一般不会单独使用它。他允许开发人员使用不同的具体日志实现工具:Log4j、Jdk自带的日志 (JUL)
JCL有两个基本的抽象类:Log(基本记录器) 和 LogFactory(负责创建Log实例)
1)添加依赖
pom.xml
<dependencies>
<dependency>
<groupId>commons-logginggroupId>
<artifactId>commons-loggingartifactId>
<version>1.2version>
dependency>
dependencies>
2)编写代码
public class JCLTest {
// 创建Logger实例
public static final Log log = LogFactory.getLog(JCLTest.class);
public static void main(String[] args) {
log.fatal("fatal");
log.error("error");
log.warn("warn");
log.info("info");
log.debug("debug");
}
Java日志系统历史从入门到崩溃 - 个人文章 - SegmentFault 思否
Logback是一个功能强大的Java日志框架,是Log4j框架的继任者(Logback是由Log4j创始人编写的)。它提供了灵活的配置选项和高性能的日志记录功能,被广泛用于Java应用程序的日志管理。
以下是一些Logback的特点和功能:
Logback框架包含三个主要的模块:logback-core、logback-classic 和 logback-access。
logback-core:logback-core是Logback框架的核心模块,提供了日志记录和输出的基本功能。
logback-classic:logback-classic是Logback框架的经典模块,是logback-core模块的扩展。
logback-access:logback-access是Logback框架的访问模块,用于记录和访问HTTP请求的日志。
这三个模块共同构成了Logback框架的核心功能,开发人员可以根据需要选择和配置相应的模块,以满足应用程序的日志管理需求。
1)要想使用Logbook框架,至少需要在pom中导入如下三个依赖:
pom.xml
<dependencies>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.32version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-coreartifactId>
<version>1.2.3version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.3version>
dependency>
dependencies>
2)在模块的src目录下创建核心配置文件 logback.xml
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<target>System.outtarget>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %c [%thread] : %msg%npattern>
encoder>
appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%npattern>
<charset>utf-8charset>
encoder>
<file>C:/code/data.logfile>
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>C:/code/data-%d{yyyy-MMdd}.log%i.gzfileNamePattern>
<maxFileSize>1MBmaxFileSize>
rollingPolicy>
appender>
<root level="ALL">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE" />
root>
configuration>
3)创建Logback框架提供的Logger对象,然后用Logger对象调用其提供的方法就可以记录系统的日志信息。
public static final Logger LOGGER = LoggerFactory.getLogger("类名");
简单演示:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogBackTest {
// 创建Logger日志对象
public static final Logger LOGGER = LoggerFactory.getLogger("LogBackTest");
public static void main(String[] args) {
try {
LOGGER.info("div方法开始执行~~~");
div(10, 0);
LOGGER.info("div方法执行成功~~~");
} catch (ArithmeticException e) {
LOGGER.error("div方法执行失败!!!");
}
}
public static void div(int a, int b) {
LOGGER.debug("参数a: " + a);
LOGGER.debug("参数b: " + b);
System.out.println(a + " / " + b + " = " + a / b);
LOGGER.info(a + " / " + b + " = " + a / b);
}
}
结果
16:08:44.962 [main] INFO LogBackTest - div方法开始执行~~~
16:08:44.964 [main] DEBUG LogBackTest - 参数a: 10
16:08:44.964 [main] DEBUG LogBackTest - 参数b: 0
16:08:44.964 [main] ERROR LogBackTest - div方法执行失败!!!
日志的输出位置、输出格式的设置
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<root level="ALL">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE" />
root>
如下是一个logback.xml的模板
<configuration>
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
<property name="LOG_DIR" value="/path/to/log/directory"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
root>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}pattern>
encoder>
appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/application.logfile>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIR}/application.%d{yyyy-MM-dd}.%i.logfileNamePattern>
<maxHistory>30maxHistory>
rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}pattern>
encoder>
appender>
configuration>
元素定义一个名为LOG_PATTERN
的属性,可以在后面的配置中引用该属性。
元素定义一个名为LOG_DIR
的属性,可以在后面的配置中引用该属性。
元素定义根日志的级别,以及要使用的日志输出器(appender)。
元素定义一个名为CONSOLE
的控制台输出器,使用ConsoleAppender
类,并配置日志格式。
元素定义一个名为FILE
的文件输出器,使用RollingFileAppender
类,并配置日志文件路径、滚动策略、日志格式等。打印日志到控制台
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<target>System.outtarget>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %c [%thread] : %msg%npattern>
encoder>
appender>
ConsoleAppender类表示日志信息输出到控制台
打印日志到文件
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%npattern>
<charset>utf-8charset>
encoder>
<file>C:/code/data.logfile>
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>C:/code/data-%d{yyyy-MMdd}.log%i.gzfileNamePattern>
<maxFileSize>1MBmaxFileSize>
rollingPolicy>
appender>
RollingFileAppender类表示日志信息输出到文件
以下是Logback中定义的日志级别:(从小到大)
TRACE
:最低的日志级别,用于输出最详细的日志信息,通常用于调试目的。(追踪,指明程序运行轨迹)DEBUG
:用于输出调试信息,比TRACE级别更高,通常用于开发和调试阶段。INFO
:用于输出一般的信息性消息,表示程序正常运行的状态。WARN
:用于输出警告信息,表示潜在的问题或不符合预期的情况,但不会影响程序的正常运行。ERROR
:用于输出错误信息,表示严重的问题或错误,可能会导致程序的异常终止或不可恢复的错误。除了以上五个标准的日志级别,Logback还支持自定义的日志级别。可以根据具体的需求定义自己的日志级别,并在配置文件中进行配置。
在Logback中,可以通过配置文件(通常是logback.xml
或logback.groovy
)来设置日志级别。可以为不同的日志记录器(Logger)设置不同的日志级别,以控制不同部分的日志输出。
例如,可以使用以下配置将日志级别设置为DEBUG
:
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE" />
root>
在 Logback 中,可以通过配置来实现日志的拆分。拆分日志可以按照时间、文件大小或者其他条件进行
TimeBasedRollingPolicy
可以按照时间来拆分日志文件。可以通过
指定拆分后的日志文件名格式,例如 logfile-%d{yyyy-MM-dd}.%i.log
,其中 %d{yyyy-MM-dd}
表示按照日期进行拆分,%i
表示拆分后的文件索引。可以通过
指定保留的历史日志文件的数量。SizeBasedTriggeringPolicy
可以按照文件大小来拆分日志文件。可以通过
指定每个日志文件的最大大小,例如 10MB
。当日志文件达到指定大小时,会自动拆分为新的日志文件。OnMarkerEvaluator
可以根据自定义的条件来拆分日志文件。可以通过
配置一个自定义的 OnMarkerEvaluator
,并在代码中使用 Marker
来触发日志拆分。演示:
将日志按照时间和文件大小进行拆分
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/path/to/logfile.logfile>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>/path/to/logfile-%d{yyyy-MM-dd}.%i.logfileNamePattern>
<maxHistory>30maxHistory>
rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>10MBmaxFileSize>
triggeringPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%npattern>
encoder>
appender>
<root level="INFO">
<appender-ref ref="FILE" />
root>
configuration>
上面配置中,日志文件将按照日期进行拆分,每天生成一个新的日志文件。同时,当日志文件达到 10MB 时,也会触发拆分为新的日志文件
在 Logback 中,可以使用过滤器来控制日志的输出。过滤器可以根据不同的条件来决定是否输出某条日志。
Logback 提供了多种类型的过滤器,常用的过滤器包括:
LevelFilter
:根据日志级别进行过滤。可以通过设置 level
属性来指定过滤的日志级别,只有达到指定级别的日志才会被输出。ThresholdFilter
:根据日志级别的阈值进行过滤。可以通过设置 level
属性来指定阈值,只有达到或超过指定级别的日志才会被输出。EvaluatorFilter
:根据自定义的评估器进行过滤。可以通过设置 evaluator
属性来指定一个自定义的评估器,根据评估器的结果来决定是否输出日志。MarkerFilter
:根据 Marker 进行过滤。可以通过设置 marker
属性来指定一个 Marker,只有包含指定 Marker 的日志才会被输出。TurboFilter
:自定义的高性能过滤器。可以通过继承 ch.qos.logback.core.spi.TurboFilter
类来实现自定义的过滤器。演示:
使用 LevelFilter
过滤器只输出 INFO 级别的日志
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%npattern>
encoder>
appender>
<root level="DEBUG">
<appender-ref ref="CONSOLE" />
root>
<logger name="com.clear" level="DEBUG" />
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFOlevel>
<onMatch>ACCEPTonMatch>
<onMismatch>DENYonMismatch>
filter>
configuration>
在上述配置中,所有的日志都会输出到控制台。但是通过 LevelFilter
过滤器,只有 INFO 级别的日志才会被接受(输出),其他级别的日志会被拒绝(不输出)
打印日志到文件中需要涉及到大量的文件IO,性能比较低,Logback之所以高效就是因为它支持异步日志
在 Logback 中,可以使用异步日志来提高日志的性能和吞吐量。异步日志将日志的写入操作放在一个独立的线程中进行,不会阻塞主线程的执行。
使用方式:
要启用异步日志,可以使用 Logback 提供的 AsyncAppender
。AsyncAppender
是一个包装器,可以将其他的 Appender 转换为异步的。
演示:
<configuration>
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE" />
<queueSize>512queueSize>
<discardingThreshold>0discardingThreshold>
appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/myapp.logfile>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%npattern>
encoder>
appender>
<root level="DEBUG">
<appender-ref ref="ASYNC_FILE" />
root>
configuration>
Log4j 2 是一个用于 Java 应用程序的日志框架,它提供了灵活的配置和强大的日志功能。与 Logback 类似,Log4j 2 也支持异步日志,可以提高日志的性能和吞吐量(其实就是参考了Logback而设计出来的)
LOG4J和LOG4J2都是Java的日志框架,它们都提供了强大的日志记录和管理功能。然而,它们在设计和实现上有一些区别。
log4j.properties
)或基于XML的配置文件(如log4j.xml
)来配置日志记录器和输出目标。LOG4J2则引入了新的配置方式,支持使用XML、JSON、YAML等格式的配置文件,同时也支持使用编程API进行配置。总的来说,LOG4J2是LOG4J的升级版,它在性能、配置方式和功能扩展方面有所改进。如果你正在开始一个新的项目,或者希望提升现有项目的日志性能和功能,那么LOG4J2可能是一个更好的选择。但如果你已经在使用LOG4J,并且没有特别的需求,那么继续使用LOG4J也是可以的。
DefaultConfiguration类提供的默认配置
private volatile Configuration configuration = new DefaultConfiguration();
要使用 Log4j 2,需要在项目中添加 Log4j 2 的依赖,并配置 Log4j 2 的配置文件
1)pom.xml
<dependencies>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-apiartifactId>
<version>2.14.1version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>2.14.1version>
dependency>
dependencies>
2)配置 Log4j 2 的配置文件,位于类路径的根目录下。
log4j2.xml
<Configuration status="INFO">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
Console>
Appenders>
<Loggers>
<Root level="DEBUG">
<AppenderRef ref="Console" />
Root>
Loggers>
Configuration>
3)代码中使用
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;
public class Log4j2Test {
private static final Logger LOGGER = LogManager.getLogger(Log4j2Test.class);
@Test
public void test(){
LOGGER.fatal("fatal");
LOGGER.error("error");
LOGGER.warn("warn");
LOGGER.info("info");
LOGGER.debug("debug");
LOGGER.trace("trace");
}
}
结果:
21:24:15.027 [main] FATAL com.clear.Log4j2Test - fatal
21:24:15.027 [main] ERROR com.clear.Log4j2Test - error
21:24:15.027 [main] WARN com.clear.Log4j2Test - warn
21:24:15.027 [main] INFO com.clear.Log4j2Test - info
21:24:15.027 [main] DEBUG com.clear.Log4j2Test - debug
使用slf4j作为日志的门面,使用log4j2作为日志的实现
1)pom.xml
<dependencies>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-apiartifactId>
<version>2.14.1version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>2.14.1version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.32version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-slf4j-implartifactId>
<version>2.12.1version>
dependency>
dependencies>
要使用 Log4j 2 的异步日志功能,你需要进行以下配置:
1)添加 Log4j 2 的异步日志依赖
pom.xml
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-asyncartifactId>
<version>2.14.1version>
dependency>
2)创建 Log4j 2 的配置文件 log4j2.xml
,并将其放置在类路径下
log4j2.xml
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
Console>
Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
Root>
Loggers>
Configuration>
3)在代码中使用log4j的API记录日志
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;
public class Log4j2Test {
private static final Logger LOGGER = LogManager.getLogger(Log4j2Test.class);
@Test
public void test(){
LOGGER.fatal("fatal");
LOGGER.error("error");
LOGGER.warn("warn");
LOGGER.info("info");
LOGGER.debug("debug");
LOGGER.trace("trace");
}
}
注意:
异步日志可以提高性能,但也可能会导致日志消息的顺序不一致。如果你需要确保日志消息的顺序,请使用同步日志。
基本格式
必须使用参数化信息的方式:
logger.debug("Processing ing trade with id:[{}] and symbol:[{}]",id,symbol);
不要进行字符串拼接,那样会产生多个String对象,占用空间,影响性能。
错误示例:
logger.debug("Processing ing trade with id:“ + id + ” symbol:" + symbol);
ERROR,影响到程序正常运行、当前请求正常运行的异常情况:
打开配置文件失败
所有第三方对接的异常(包括第三方返回异常码)
所有影响功能使用的异常,包括 SQLException 和除了业务异常之外的所有异常(RuntimeException和Exception)
不应该出现的情况,比如使用阿里云传图片,但是未响应
如果有Throwable信息,需要记录完整的堆栈信息
log.error("获取用户[{}]的用户信息时出错", userName, e);
说明:
如果进行了抛出异常操作,请不要记录error日志,由最终处理方进行处理
反例(错误示例):
try{
...
}catch(Exception e){
String errorMessage = String.format("Error while reading information of user [%s]",userName);
logger.error(errorMessage, e);
throw new UserServiceException(errorMessage, e);
}
WARN,不应该出现但是不影响程序、当前请求正常运行的异常情况:
有容错机制的时候出现的错误情况
找不到配置文件,但是系统能自动创建配置文件,比如我们第一次格式化Hadoop时就会出现WARN,并帮我们创建日志
即将接近临界点的时候,比如缓存池占用达到警戒线,业务异常的记录,比如用户锁定异常
INFO,系统运行信息
说明
并不是所有的service都进行出入口大点记录,单一、简单service是没有意义的(job除外,job需要记录开始、结束)
反例:
public List listByBaseType(Integer baseTypeId){
log.info("开始查询基地");
BaseExample e = new BaseExample();
BaseExample.Criteria ctr = e.createCriteria();
ctr.andIsDeleteEqualTo(IsDelete.USE.getValue());
Options.doIfPresent(baseTypeId, ctr::andIsDeleteEqualTo);
log.info("查询基地结束");
return baseRepository.selectByExample(e);
}
DEBUG,可以填写所有的想知道的信息(但是也需要填写有意义的信息)
生成环境需要关闭DEBUG信息
如果在生产环境情况下需要开启DEBUG,需要使用开关进行管理,不能一直开启
说明
如果代码中出现以下代码,可以进行优化:
1.获取用户基本薪资
2.获取用户休假情况
3.计算用户所得薪资
logger.debug("开始获取员工[{}] [{}]年基本薪资", employee, year);
logger.debug("获取员工[{}] [{}]年基本薪资为[{}]", employee, year, basicSalary);
logger.debug("开始获取员工[{}] [{}]年[{}]月休假情况", employee, year, month);
logger.debug("员工[{}] [{}]年[{}]月年假/病假/事假为[{}]/[{}]/[{}]", employee, year, month, annualLeaveDays, sickLeaveDays, noPayLeaveDays);
logger.debug("开始计算员工[{}] [{}]年[{}]月应得薪资", employee, year, month);
logger.debug("员工[{}] [{}]年[{}]月应得薪资为[{}]", employee, year, month, actualSaraly);
TRACE,特别详细的系统运行完全信息,业务代码中,不要使用(除非有特殊用意,否则使用DEBUG代替)