当前位置: 首页 > news >正文

01-Java-日志框架

1 日志技术概述

1.1 什么是日志技术

​ 日志技术是一种记录和存储应用程序运行时信息的技术。它可以捕获应用程序的状态、事件、错误和警告等信息,并将其保存到日志文件或其他存储介质中。日志技术可以帮助开发人员和运维团队了解应用程序的运行情况,进行故障排查、性能优化、安全监控和运营分析等工作。

以下是日志技术的一些关键概念和组成部分:

  • 日志级别:日志级别用于标识日志的重要性和严重程度。常见的日志级别包括DEBUG、INFO、WARN、ERROR和FATAL等。开发人员可以根据需要选择适当的日志级别来记录不同类型的信息。
  • 日志格式:日志格式定义了日志记录的结构和内容。它通常包括时间戳、日志级别、日志消息和其他相关信息。开发人员可以根据需求自定义日志格式,以便更好地理解和分析日志。
  • 日志输出:日志输出指定了日志记录的目标位置。常见的日志输出包括控制台输出、文件输出和远程日志服务器等。开发人员可以根据需要配置多个日志输出,以便在不同环境下进行日志记录和分析。
  • 日志框架:日志框架是一种用于记录和管理日志的软件库或工具。它提供了一组API和配置选项,使开发人员能够方便地生成和处理日志。常见的日志框架包括Log4j、Logback和java.util.logging等。
  • 日志分析:日志分析是指对大量日志数据进行处理和分析,以提取有用的信息和洞察。日志分析工具可以帮助开发人员和运维团队发现潜在的问题、优化性能、监控安全和了解用户行为等。

1.2 我们接触过的日志

我们之前都是使用输出语句(System.out.println)打印日志的,有什么问题呢?

  • 日志打印在控制台,当我们关闭控制台以后日志就不见了
  • 不能方便的将日志记录到其他的位置(如文件、数据库等)
  • 想要取消或修改日志,需要修改源代码

1.3 引入日志技术的作用

引入日志技术是为了更好地管理和跟踪应用程序的运行情况。以下是一些使用日志技术的好处:

  • 故障排查和调试:当应用程序出现问题时,日志可以提供有关错误和异常的详细信息,帮助开发人员快速定位和解决问题。通过查看日志,您可以了解应用程序在特定时间点的状态、执行路径和输入输出数据。
  • 监控和性能优化:日志可以记录应用程序的性能指标、资源使用情况和系统状态。通过分析日志,您可以识别性能瓶颈、优化代码和资源分配,以提高应用程序的性能和可伸缩性。
  • 安全和合规性:日志可以记录应用程序的安全事件、用户操作和敏感数据访问。这些日志可以用于监测潜在的安全威胁、追踪用户活动和满足合规性要求。
  • 运营和维护:日志可以提供应用程序的运行情况、错误和警告信息。通过监视日志,您可以及时发现并解决潜在的问题,确保应用程序的稳定运行。
  • 分析和洞察:通过对日志进行分析,您可以了解应用程序的使用情况、用户行为和业务趋势。这些洞察可以帮助您做出更好的业务决策、改进产品功能和提供更好的用户体验。

​ 引入日志技术可以帮助开发人员和运维团队更好地理解和管理应用程序的运行情况,提高开发效率、减少故障修复时间,并提供更好的用户体验。

1.4 日志技术的体系

日志技术的体系结构:

  • 日志门面(接口、规则):JCL(Jakarta Commons Logging 由Apache Jakarta提供)、SLF4J(Simple Logging Facade for Java)、Log4j2 API
  • 日志实现(框架):JUL(java util logging)、Logback、Log4j、Log4j2-core

日志门面是设计日志框架的一套标准,所有的日志框架都需要实现这些接口

Log4j实现的是JCL

Logbook实现的是SLF4j

Log4J2-core实现的是Log4j2 API

说明:

​ 因为对JCL接口不满意,所以有人就搞出来了SLF4J;因为对Log4j的性能不满意,所以就出现了Logback

Logback是基于SLF4J的日志规范实现的日志框架

日志框架出现的历史顺序

Log4j --> JUL --> JCL -> Slf4j --> Loback --> Log4j2

2 JUL

​ JUL(Java Util Logging)是Java平台自带(原生)的日志框架。它是Java标准库的一部分,不需要额外的依赖。JUL提供了一套简单的日志记录API,可以用于在应用程序中记录日志信息。

2.1 JUL主要组件

JUL的主要组件包括:

  • Logger(日志记录器):用于记录日志信息的主要组件。可以通过Logger.getLogger()方法获取Logger实例。Logger通常是应用程序访问日志系统的入口程序。
  • Handler(处理器):也称为Appenders,用于处理日志记录的输出。每个Loigger都会关联一组Handlers,Logger会将日志交给关联的Handlers进行处理,由Handler负责将日志做记录。可以将日志记录发送到不同的目标,如控制台、文件、数据库等。(Handler是一个抽象,具体需要将日志发送到哪里还需要具体的实现)
  • Formatter(格式化器):也称为Layout用于格式化日志记录的输出。可以定义日志记录的格式,包括日期、时间、日志级别、类名等信息。
  • Level(日志级别):用于控制日志记录的级别。可以设置不同的日志级别,如INFO、DEBUG、WARNING、ERROR等,只有达到指定级别的日志记录才会被记录。
  • Filters(过滤器):根据需要定制那些消息会被记录,那些消息会被放过

​ JUL的配置文件是一个文本文件,通常命名为logging.properties。可以通过修改配置文件来配置日志记录的级别、输出目标、格式等。

2.2 JUL的使用

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");@Testpublic void test1(){LOGGER.severe("server");  // 严重LOGGER.warning("warning");  // 警告LOGGER.info("info");  // 信息}
}

2.3 日志级别

记录日志信息:使用Logger实例的不同方法记录不同级别的日志信息。

常用的日志级别包括:

  • SEVERE:最高级别的日志,表示严重错误。
  • WARNING:警告级别的日志,表示潜在的问题。
  • INFO:信息级别的日志,表示一般的运行信息。(默认)
  • CONFIG:配置级别的日志,表示配置信息。
  • FINE:细粒度的调试信息。
  • FINER:更细粒度的调试信息。
  • FINEST:最细粒度的调试信息。
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");

此外,还有两个特殊的级别:

  • ALL:启用所有消息的日志记录
  • OFF:可以用来关闭日志记录

2.4 日志配置

// 获取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");
}

2.5 Logger之间的父子关系

​ 在JUL(Java Util Logging)中,日志记录器(Logger)之间可以建立父子关系。这种父子关系是通过日志记录器的名称来确定的(即建立树状结构来存储父子关系)。

父子关系的建立是通过日志记录器的名称来实现的。当创建一个新的日志记录器时,可以指定其父级日志记录器的名称。如果没有指定父级日志记录器的名称,则默认为根日志记录器(RootLogger)

​ 默认情况下,子Logger会继承父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());  // 4959864System.out.println(logger.hashCode());  // 4959864System.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@4bae78System.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@1764bceSystem.out.println("父Logger名字:"+logger.getParent().getName()); // 父Logger名字:com.clearSystem.out.println("父Logger的父Logger:"+pLogger.getParent());  // 父Logger的父Logger:java.util.logging.LogManager$RootLogger@4bae78System.out.println("父Logger的父Logger名称:"+pLogger.getParent().getName());  // 父Logger的父Logger名称:
}
  • 默认情况下,子Logger会继承父Logger的属性。
// 默认情况下,子Logger会继承父Logger的属性。
@Test
public void test5(){Logger logger = Logger.getLogger("com.clear.JULTest");// 父LoggerLogger pLogger = Logger.getLogger("com.clear");// 父Logger的默认日志打印级别为infopLogger.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");
}

2.6 日志格式化

​ 在JUL(Java Util Logging)中,可以使用格式化器(Formatter)来定义日志消息的输出格式。格式化器可以将日志消息的各个部分(如日期、级别、类名、方法名、消息内容等)组合成一个字符串,并指定其输出的格式。

JUL提供了两种内置的格式化器:

  • SimpleFormatter:这是默认的格式化器,它将日志消息格式化为一行文本。默认格式为[日期时间] [级别] [类名] [方法名] - [消息内容]
  • XMLFormatter:这是一个将日志消息格式化为XML格式的格式化器。它将日志消息的各个部分作为XML元素,并使用属性来表示其值。

可以通过以下方式来配置日志记录器的格式化器:

  1. 在代码中使用Handler.setFormatter(Formatter formatter)方法来设置格式化器。例如,handler.setFormatter(new SimpleFormatter())handler的格式化器设置为SimpleFormatter
  2. 在配置文件中使用java.util.logging.ConsoleHandler.formatter属性来设置控制台处理器的格式化器。例如,java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter表示将控制台处理器的格式化器设置为SimpleFormatter
  3. 在配置文件中使用java.util.logging.FileHandler.formatter属性来设置文件处理器的格式化器。例如,java.util.logging.FileHandler.formatter=java.util.logging.XMLFormatter表示将文件处理器的格式化器设置为XMLFormatter

除了使用内置的格式化器,还可以自定义格式化器来满足特定的需求。自定义格式化器需要实现java.util.logging.Formatter接口,并实现其中的format(LogRecord record)方法来定义日志消息的输出格式。

通过配置日志记录器的格式化器,可以灵活地控制日志消息的输出格式,以满足不同的需求和标准。

2.6.1 String的format方法

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");

2.6.2 常用的转换符

转换符说明举例
%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代表不同的日期与时间转换符)

2.6.3 特殊符号

标志说明举例结果
+为正数或负数添加符合,因为一般整数不会主动加符号(“+%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$就代表第二个参数

2.6.4 日期处理

前面的 %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
m7
d18
H24小时制的时20
l12小时制的时8
M55
S55
s秒为单位的时间戳23144232
p上午还是下午下午

我们可以使用以下三个类去进行格式化,其中可能存在不支持的情况,比如LocalDateTime不支持c:

System.out.println("tc", new Date());
System.out.println("tc", ZonedDateTime.now());
System.out.println("tF", LocalDateTime.now();

2.7 配置文件

JUL(Java Util Logging)加载配置文件的源码解读如下:

  • 首先,JUL使用LogManager类来管理日志记录器和配置信息。LogManager是一个单例类,通过LogManager.getLogManager()方法获取实例。

  • 当调用LogManager.getLogManager().readConfiguration(inputStream)方法时,会执行以下步骤:

    • a. LogManager类的readConfiguration方法会创建一个新的Properties对象,用于存储配置信息。
    • b. 使用Properties对象的load方法,将配置文件的内容加载到Properties对象中。这里的inputStream参数是通过getClass().getClassLoader().getResourceAsStream("logging.properties")获取的,它会从类路径中读取logging.properties文件的内容。
    • c. 加载配置文件后,LogManager会根据配置信息更新日志记录器的配置。具体的配置信息包括日志级别、日志输出目标等。
  • 如果没有指定配置文件或加载配置文件失败,JUL会使用默认的配置。默认的配置包括将日志输出到控制台,并使用默认的日志级别。

​ 总结:JUL加载配置文件的源码主要涉及LogManager类的readConfiguration方法,该方法通过读取配置文件的内容,并根据配置信息更新日志记录器的配置。

2.7.1 读取自定义配置文件

在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=FINEhandlers=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");
}
}

2.7.2 配置文件解读

# 设置根日志记录器的级别为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

2.7.3 独立日志的配置

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);}}// 读取配置文件@Testpublic void readConfig() {// 此时,com.clear包下,只会打印WARNING级别及以上的日志LOGGER.warning("warning");LOGGER.fine("fine");}
}

2.8 过滤器

​ 在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());// 日志记录器过滤器测试@Testpublic void loggerFilterTest() {// 给日志记录器设置过滤器LOGGER.setFilter(logRecord ->  // 只打印级别>= INFO的日志logRecord.getLevel().intValue() >= Level.INFO.intValue());LOGGER.info("hello");LOGGER.fine("world");LOGGER.finer("apache");}// 处理器过滤器测试@Testpublic 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");}
}

2.9 打印异常堆栈

在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());@Testpublic void printStackTest() throws IOException {// 设置日志记录器的级别为FINERLOGGER.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);}}
}

3 LOG4J

​ LOG4J是一个流行的Java日志框架,它提供了强大的日志记录和管理功能。LOG4J可以帮助开发人员在应用程序中实现灵活的日志记录,并提供了多种配置选项和输出格式。

LOG4J的一些主要特点和用法:

  • 配置文件:LOG4J使用一个配置文件来定义日志记录器、日志级别、输出目标等配置信息。默认的配置文件名为log4j.properties,也可以使用XML格式的配置文件log4j.xml
  • 日志级别:LOG4J提供了多个日志级别,包括DEBUGINFOWARNERRORFATAL。可以根据需要设置不同的日志级别,以控制日志记录的详细程度。
  • 日志记录器:LOG4J使用日志记录器(Logger)来记录日志消息。每个日志记录器都有一个唯一的名称,可以根据需要创建多个日志记录器。
  • 输出目标:LOG4J支持将日志消息输出到不同的目标,如控制台、文件、数据库等。可以根据需要配置不同的输出目标。
  • 格式化:LOG4J允许自定义日志消息的格式。可以使用预定义的格式化器,如PatternLayout,也可以自定义格式化器。
  • 运行时配置:LOG4J支持在运行时动态修改日志配置。可以通过修改配置文件或使用API来改变日志记录器的级别、输出目标等配置。

3.1 LOG4J的使用

1)添加POM依赖

​ 添加LOG4J依赖:在项目的构建文件(如Maven的pom.xml)中添加LOG4J的依赖项

<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version>
</dependency>

2)创建LOG4J配置文件

​ 创建LOG4J配置文件:在项目的资源目录下创建一个LOG4J的配置文件,命名为log4j.propertieslog4j.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);@Testpublic 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会根据配置文件中的设置进行日志记录。日志消息将根据配置的输出目标(如控制台、文件)进行输出。可以根据配置文件中的日志级别设置,过滤和查看不同级别的日志消息。

3.2 默认配置解读

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"

3.3 日志级别

在Log4j中,日志级别从低到高分别为(与JUL略有不同)

  • TRACE:最低级别的日志,用于追踪程序的详细执行过程。
  • DEBUG:用于调试程序,输出一些调试信息。(默认)
  • INFO:用于输出程序的运行信息,如启动、关闭等。
  • WARN:用于输出警告信息,表示可能出现的问题。
  • ERROR:用于输出错误信息,表示程序出现了错误但不影响程序的继续运行。
  • FATAL:最高级别的日志,用于输出严重的错误信息,表示程序无法继续运行。

和JUL一样,也有两个特殊的级别:

  • OFF 用于关闭日志记录
  • ALL 启用所有消息的日志记录

可以根据需要选择适当的日志级别来记录日志,通常建议在生产环境中使用INFO级别或更高级别,而在开发和调试阶段使用DEBUG级别。

3.4 核心组件

3.4.1 Loggers

​ 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中也有一个 名为.的根

3.4.2 Appenders

​ 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);

3.4.3 Layouts

Layout(日志布局器):用于定义日志消息的格式。Layout将日志消息转换为特定的字符串格式,以便输出到Appender指定的目标。

3.4.4 Lever

​ Level(日志级别):用于控制日志消息的输出级别。可以根据需要设置不同的日志级别,只有达到指定级别及以上的日志消息才会被记录和输出。

3.4.5 Configuration

​ Configuration(配置):用于配置Log4j的各个组件。可以通过配置文件或编程方式来配置Log4j,包括设置日志级别、指定Appender和Layout等。

总结

​ 这些核心组件共同工作,实现了灵活、可配置的日志记录功能。通过合理配置和使用这些组件,可以实现对日志消息的记录、输出和管理。

3.5 编程配置

在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");}

3.6 配置文件

​ Log4j的配置文件是一个用于配置日志记录器的文本文件,它定义了日志记录器的行为和输出方式。Log4j支持多种配置文件格式,包括XML、JSON和属性文件

​ 配置文件的命名通常为log4j.properties或log4j.xml,可以放置在类路径下或指定的位置。在应用程序启动时,Log4j会自动加载并解析配置文件,根据配置文件的定义进行日志记录

例如:

log4j.properties

# 设置根日志级别为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

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<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><!-- 设置根日志级别为INFO --><Root level="INFO"><AppenderRef ref="Console" /><AppenderRef ref="File" /></Root></Loggers>
</Configuration>
  • <?xml version="1.0" encoding="UTF-8"?>:指定XML文件的版本和编码。
  • <Configuration status="WARN">:设置Log4j的全局配置,其中status属性设置为WARN表示只输出警告级别以上的日志信息。
  • <Appenders>:定义日志输出的Appenders。
  • <Console name="Console" target="SYSTEM_OUT">:定义控制台输出的Appender,其中name属性为Appender的名称,target属性指定输出目标为系统标准输出。
  • <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />:设置控制台输出的日志消息格式。
  • <RollingFile name="File" fileName="logs/app.log" filePattern="logs/app-%d{yyyy-MM-dd}.log">:定义文件输出的Appender,其中name属性为Appender的名称,fileName属性指定日志文件的路径和文件名,filePattern属性指定日志文件的滚动模式。
  • <Policies>:定义日志文件滚动的策略。
  • <SizeBasedTriggeringPolicy size="10MB" />:设置日志文件的滚动触发策略为基于文件大小,当日志文件达到10MB时触发滚动。
  • <DefaultRolloverStrategy max="10" />:设置日志文件的滚动策略为默认策略,最多保留10个备份文件。
  • <Loggers>:定义日志记录器。
  • <Root level="INFO">:设置根日志记录器的级别为INFO。
  • <AppenderRef ref="Console" /><AppenderRef ref="File" />:将控制台输出和文件输出的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(嵌套诊断环境)
%%输出一个 “%” 字符

可以在 % 与字符之间加上修饰符来控制最小宽度、最大宽度和文本的对其方式。如:

  • %5c 输出category名称,最小宽度是5,category<5,默认的情况下右对齐
  • %-5c 输出category名称,最小宽度是5,category<5,"-"号指定左对齐,会有空格
  • %.5c 输出category名称,最大宽度是5,category>5,就会将左边多出的字符截掉,<5不会有空格
  • %20.30c category名称<20补空格,并且右对齐,>30字符,就从左边交远销出的字符截掉

3.7 PatternLayout源码

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()方法:

// 如下是这个方法的简略版
publicPatternConverter parse() {char c;i = 0;while(i < patternLength) {c = pattern.charAt(i++);switch(state) {.....finalizeConverter(c);  // 这个方法最重要的就是这个......return head;
}

下面是finalizeConverter()方法:

protectedvoid 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++;}elseLogLog.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 infoformattingInfo.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,第二个参数是LoggingEventc.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);
}

3.10 日志分割

在Log4j中,可以通过配置来实现日志的分割,即将日志按照一定的规则拆分成多个文件,以便于管理和查看。

Log4j提供了多种方式来实现日志的分割,其中常用的方式包括:

  • 按时间分割:可以按照日期、小时、分钟等时间单位来拆分日志文件。例如,每天生成一个新的日志文件,或者每小时生成一个新的日志文件。
  • 按文件大小分割:可以按照文件大小来拆分日志文件。例如,当日志文件达到一定大小时,自动创建一个新的日志文件。
  • 按日志级别分割:可以按照日志级别来拆分日志文件。例如,将不同级别的日志分别记录到不同的文件中

源码中,FileAppender类有几个子类,用于实现日志文件的滚动、分割:

  • DailyRollingFileAppender:按照日期滚动的Appender。它可以按照一定的时间间隔(如每天、每周、每月)生成新的日志文件
  • RollingFileAppender:按照文件大小滚动的Appender。它可以在日志文件达到一定大小时生成新的日志文件
    • ExternallyRolledFileAppende:外部滚动的Appender。它允许外部程序控制日志文件的滚动。可以通过外部程序的信号或命令来触发日志文件的滚动。

如下是使用DailyRollingFileAppenderRollingFileAppender来实现日志文件的分割和滚动:

# 设置根日志级别为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);

3.6 自定义Logger

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

4 日志门面

日志门面(Logging Facade)是一种设计模式,用于在应用程序中实现日志记录的抽象层。它的作用是提供一个统一的接口,使应用程序可以使用不同的日志库进行日志记录,而不需要直接依赖于特定的日志库。

日志门面的作用有以下几个方面:

  • 解耦应用程序和具体的日志库:通过使用日志门面,应用程序可以将日志记录的实现细节与具体的日志库解耦。这意味着应用程序可以在不修改代码的情况下切换或替换不同的日志库,以满足不同的需求或适应不同的环境。
  • 统一日志记录接口:日志门面提供了一个统一的接口,使应用程序可以使用相同的日志记录方法和语法,无论使用哪个具体的日志库。这简化了日志记录的代码编写和维护,并提高了代码的可读性和可维护性。
  • 支持多个日志级别和日志输出目标:日志门面通常支持多个日志级别(如调试、信息、警告、错误等)和多个日志输出目标(如控制台、文件、数据库等)。这使得应用程序可以根据需要选择适当的日志级别和输出目标,以满足不同的调试、监控和故障排查需求。
  • 提供日志记录的扩展和定制能力:通过日志门面,应用程序可以使用日志库提供的扩展和定制功能,如日志过滤、格式化、异步记录等。这使得应用程序可以根据具体需求对日志记录进行定制和优化,以满足特定的业务需求和性能要求。

总之,日志门面的作用是提供一个抽象层,使应用程序可以灵活地使用不同的日志库,并提供统一的日志记录接口和功能,以提高代码的可维护性、可读性和灵活性

4.1 阿里日志规约

  • 【强制】应用中不可直接使用日志系统(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(打印昇常堆桟。

    • 说明:标准日志输出与标准错误输出文件每次 Jboss 重启时才滚动,如果大量输出送往这两个文件,容易造成文件大小超过操作系统大小限制。
  • 【强制】异常信息应该包括两类信息 :案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 throws 往上抛出。

    • 正例:

      logger.error("inputParams:{} and errorMessage:{}”,各类参数或者对系 toString(), e.getMessage(), e);
      
  • 【强制】日志打印时禁止直接用 JSON 工具将对象转换成 String

    • 说明:如果对象里某些 get 方法被覆写,存在抛出异常的情况,则可能会因为打印日志而影响正常业务流程的执行。
    • 正例:打印日志时仅打印出业务相关属性值或者调用其对象的 toString() 方法。
  • 【推荐】递慎地记录日志。生产环境禁止输出 debug 日志:有选择地输出 info 日志:如果使用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。

    • 说明:大量地输出无效日志 ,不利于系统性能提升 ,也不利于快速定位错误点。记录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?
  • 【推荐】可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。如非必要,请不要在此场景打出 error级别,避免频繁报警。

    • 说明:注意日志输出的级别,error 级别只记录系统逻辑出错、异常或者重要的错误信息
  • 【推荐】尽量用英文来描述日志错误信息,如果日志中的错误信息用英文描述不清楚的话使用中文描述即可,否则容易产生歧义。

    • 说明:国际化团队或海外部署的服务器由于字符集问题,使用全英文来注释和描述日志错误信息。

为什么要使用日志规约:

  • 面向接口开发,不再依赖具体的实现类,减少代码的耦合
  • 项目通过导入不同的日志实现类,可以灵活的切换日志框架
  • 统一API、方便开发者学习、使用
  • 统一配置便于项目日志的管理

4.2 SLF4J

​ 简单日志门面(Simple Logging Facade For Java)SLF4J主要是为了给Java日志访问提供一套标准、规范的API框架,其意义主要是提供接口,具体的实现交由日志框架,例如log4j、logback等。当然slf4j 自己也提供了功能较为简单的日志实现,但是我们一般不使用。

对于一般的Java项目而言,我们会选择 slf4j-api 作为门面,再配上具体的日志实现(log4j、logback等),中间使用桥接器完成桥接。

SLF4J主要提供了两大功能:

  • 日志框架的绑定
  • 日志框架的桥接

4.2.1 使用SLF4J入门(slf4j与simple整合)

​ SLF4J(Simple Logging Facade for Java)是一个日志门面框架,它提供了一组简单的接口,用于在Java应用程序中进行日志记录。SLF4J本身提供了一个简单的日志实现,称为SimpleLogger。SimpleLogger是SLF4J的默认日志实现,它不需要额外的依赖,可以直接在项目中使用

要使用SimpleLogger,只需按照以下步骤进行配置:

1)添加SLF4J依赖:在项目的构建文件(如pom.xml)中添加SLF4J的依赖。

pom.xml

<dependencies><!-- SLF4J API --><!-- SLF4J是一个抽象层,它提供了统一的日志接口,可以与不同的日志实现框架(如Logback、Log4j等)进行交互 --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.32</version></dependency><!-- SLF4J 自带的简单日志实现 --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>1.7.32</version></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

4.2.2 绑定其他日志的实现(Binding)

SLF4J支持各种日志框架。SLF4J发行版附带了几个称为“SLF4J绑定”的jar文件,每个绑定对应一个受支持的框架。

使用SLF4J的日志绑定流程:

  • 添加 slf4j-api 的依赖
  • 使用slf4j的API在项目中进行统一的日志记录
  • 绑定具体的日志实现框架
    • 绑定已经实现了slf4j的日志框架,直接添加对应的依赖
    • 绑定没有实现了slf4j的日志框架,先添加日志的适配器,再添加实现类的依赖
  • slf4j有且仅有一个日志实现框架的绑定(如果出现多个默认使用第一个依赖日志实现)

绑定jul的实现(jul是JCL门面的实现),及与jdk14整合

<dependencies><!-- SLF4J API --><!-- SLF4J是一个抽象层,它提供了统一的日志接口,可以与不同的日志实现框架(如Logback、Log4j等)进行交互 --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.32</version></dependency><dependency><groupId>org.slf4j</groupId><!--jdk14指的是JDK1.4开始支持slf4j绑定JUL--><artifactId>slf4j-jdk14</artifactId><version>1.7.32</version></dependency>
</dependencies>

绑定log4j的实现(slf4j与log4j整合)

<dependencies><!-- SLF4J API --><!-- SLF4J是一个抽象层,它提供了统一的日志接口,可以与不同的日志实现框架(如Logback、Log4j等)进行交互 --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.32</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.32</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></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

4.2.3 桥接旧的日志框架(Birdging)

​ SLF4J提供了日志桥接(Logging Bridges)的功能,用于将其他日志框架的日志记录转发到SLF4J接口。这样可以在项目中使用SLF4J进行日志记录,同时仍然能够使用其他日志框架的功能

​ 通常,您依赖的某些组件依赖于SLF4J以外的日志记录APl。您也可以假设这些組件在不久的将来不会切换到SLF4J。为了解决这种情况,SLF4J附带了几个桥接模块,这些模共将对log4j,JCL和 java.util.logging API 的调用重定向,就好像它们是对SLF4J API一样。
​ 就是你还用log4j的api写代码,但是具体的实现给你抽离了,我们依赖了一个中间层,这个层其实是用旧的的api操作slf4j,而不是操作具体的实现。

桥接解決的是项目中日志的遗留问题,当系统中存在之前的日志API,可以通过桥接转换到slf4j的实现

  • 1)先去除之前老的日志框架的依赖,必须去掉。
  • 2)添加SLF4J提供的桥接组件,这个组件就是模仿之前老的日志与了一套相同的api,只不过这个api是在调用slf4j的api。
  • 3)为项目添加SLF4J的具体实现。

SLF4J提供的桥接组件

<!-- 桥接的组件--><!-- log4j转slf4j -->
<dependency><groupId>org.slf4j</groupId><artifactId>log4j-over-slf4j</artifactId><version>1.7.32</version>
</dependency><!-- jul转slf4j -->
<dependency><groupId>org.slf4j</groupId><artifactId>jul-to-slf4j</artifactId><version>1.7.32</version>
</dependency><!-- jcl转slf4j -->
<dependency><groupId>org.slf4j</groupId><artifactId>jcl-over-slf4j</artifactId><version>1.7.32</version>
</dependency>

注意:

  • jcl-over-slf4j.jar 和 slf4j-jcl.jar 不能同时部署。前一个jar文件将导致JCL将日志系统的选择委托给 SLF4J,后一个jar文件将导致SLF4J将日志系统的选择委托给 JCL,从而导致无限循环。
  • log4j-over-slf4j.jar 和 slf4j-log4j12.jar 不能同时出现
  • jul-to-slf4j.jar 和 slf4j-jdk14.jar 不能同时出现
  • 所有的桥接都只对Logger日志记录器有效,如果程序中调用了内部的配置类或者是 Appender、Filter等对象,将无法产生效果

4.2.3 SLF4J原理剖析

4.3 JCL

全称为Jakarta Conmons Logging,是Apache提供的一个通用日志API。该日志门面的使用并不是很广泛。

它是为“所有的Java日志实现"提供一个统一的接口,它自身也提供一个日志的实现,但是功能非常的弱 (Simplelog)。所以一般不会单独使用它。他允许开发人员使用不同的具体日志实现工具:Log4j、Jdk自带的日志 (JUL)
JCL有两个基本的抽象类:Log(基本记录器)LogFactory(负责创建Log实例)

简单使用

1)添加依赖

pom.xml

<dependencies><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version></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");}

4.4 日志发展历史

Java日志系统历史从入门到崩溃 - 个人文章 - SegmentFault 思否

5 Logback

​ Logback是一个功能强大的Java日志框架,是Log4j框架的继任者(Logback是由Log4j创始人编写的)。它提供了灵活的配置选项和高性能的日志记录功能,被广泛用于Java应用程序的日志管理。

以下是一些Logback的特点和功能:

  1. 灵活的配置:Logback使用XML或Groovy配置文件来定义日志记录器、日志级别、输出格式等。它支持继承和覆盖配置,可以根据不同的环境和需求进行灵活的配置。
  2. 多种日志级别:Logback支持多种日志级别,包括TRACE、DEBUG、INFO、WARN、ERROR等。开发人员可以根据需要选择适当的日志级别来记录不同类型的信息。
  3. 多种输出方式:Logback支持多种输出方式,包括控制台输出、文件输出、远程日志服务器等。开发人员可以根据需求配置多个输出目标,以便在不同环境下进行日志记录和分析。
  4. 异步日志记录:Logback支持异步日志记录,可以提高日志记录的性能。它使用多线程机制将日志事件异步写入目标输出,避免了阻塞应用程序的性能问题。
  5. 运行时配置更新:Logback支持在运行时动态更新配置,无需重启应用程序。这使得开发人员可以在不停止应用程序的情况下修改日志配置,方便调试和故障排查。
  6. 插件支持:Logback提供了丰富的插件支持,可以扩展其功能。例如,可以使用Logstash插件将日志数据发送到Elasticsearch进行分析和可视化。

5.1 三大模块

Logback框架包含三个主要的模块:logback-core、logback-classic 和 logback-access。

  • logback-core:logback-core是Logback框架的核心模块,提供了日志记录和输出的基本功能。

    • 它定义了Logger、Appender、Layout等核心组件的接口和实现,并提供了日志事件的处理和传递机制。
    • logback-core模块是其他两个模块的基础,它不依赖于任何特定的日志实现。
  • logback-classic:logback-classic是Logback框架的经典模块,是logback-core模块的扩展。

    • 它是Log4j的改良版,实现了完整的SLF4J(Simple Logging Facade for Java)的API,并提供了与SLF4J的适配器,使得应用程序可以无缝地迁移和使用Logback作为日志实现。
    • logback-classic模块还提供了一些额外的功能,如MDC(Mapped Diagnostic Context)和LoggerContext等。
  • logback-access:logback-access是Logback框架的访问模块,用于记录和访问HTTP请求的日志。

    • 它提供了与Servlet容器集成的功能,可以记录HTTP请求的详细信息,如请求URL、请求参数、响应状态等。logback-access模块可以与logback-classic模块一起使用,实现全面的日志记录和访问功能。

这三个模块共同构成了Logback框架的核心功能,开发人员可以根据需要选择和配置相应的模块,以满足应用程序的日志管理需求。

5.2 Logbook的使用

1)要想使用Logbook框架,至少需要在pom中导入如下三个依赖:

  • slf4j-api:日志门面
  • logbook-core
  • logback-classic

pom.xml

<dependencies><!-- SLF4J API --><!-- SLF4J是一个抽象层,它提供了统一的日志接口,可以与不同的日志实现框架(如Logback、Log4j等)进行交互 --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.32</version></dependency><!-- Logback Core --><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-core</artifactId><version>1.2.3</version></dependency><!-- Logback Classic --><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version></dependency>
</dependencies>

2)在模块的src目录下创建核心配置文件 logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration><!--CONSOLE :表示当前的日志信息是可以输出到控制台的。--><appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><!--输出流对象 默认 System.out 改为 System.err--><target>System.out</target><encoder><!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level]  %c [%thread] : %msg%n</pattern></encoder></appender><!-- File是输出的方向通向文件的 --><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%n</pattern><charset>utf-8</charset></encoder><!--日志输出路径--><file>C:/code/data.log</file><!--该目录需要提前存在-><!-指定日志文件拆分和压缩规则--><rollingPolicyclass="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><!--通过指定压缩文件名称,来确定分割文件方式--><fileNamePattern>C:/code/data-%d{yyyy-MMdd}.log%i.gz</fileNamePattern><!--文件拆分大小--><maxFileSize>1MB</maxFileSize></rollingPolicy></appender><!--level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF, 默认debug<root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。--><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方法执行失败!!!

5.3 核心配置文件logback.xml

  • 对LogBack日志框架进行控制

日志的输出位置、输出格式的设置

  • 通常可以设置2个输出日志的位置:一个是控制台、一个是系统文件中
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
  • 开启(ALL)、关闭日志(OFF)
<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.log</file><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${LOG_DIR}/application.%d{yyyy-MM-dd}.%i.log</fileNamePattern><maxHistory>30</maxHistory></rollingPolicy><encoder><pattern>${LOG_PATTERN}</pattern></encoder></appender></configuration>
  • 定义日志输出的格式:通过<property>元素定义一个名为LOG_PATTERN的属性,可以在后面的配置中引用该属性。
  • 定义日志输出的目录:通过<property>元素定义一个名为LOG_DIR的属性,可以在后面的配置中引用该属性。
  • 定义根日志级别:通过<root>元素定义根日志的级别,以及要使用的日志输出器(appender)。
  • 定义控制台输出的日志:通过<appender>元素定义一个名为CONSOLE的控制台输出器,使用ConsoleAppender类,并配置日志格式。
  • 定义文件输出的日志:通过<appender>元素定义一个名为FILE的文件输出器,使用RollingFileAppender类,并配置日志文件路径、滚动策略、日志格式等。

打印日志到控制台

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><!--输出流对象 默认 System.out 改为 System.err--><target>System.out</target><encoder><!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level]  %c [%thread] : %msg%n</pattern></encoder>
</appender>
  • ConsoleAppender类表示日志信息输出到控制台

  • <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %c [%thread] : %msg%n中:

    • 表示输出格式
    • %d{yyyy-MM-dd HH:mm:ss.SSS} 表示日期年月日时分秒
    • %-5level 表示日志输出的级别
    • %c 表示当前所在类(打印日志的类)
    • %thread 表示当前线程
    • %msg 表示日志信息
    • %n 表示换行

打印日志到文件

<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%n</pattern><charset>utf-8</charset></encoder><!--日志输出路径--><file>C:/code/data.log</file><!--指定日志文件拆分和压缩规则--><rollingPolicyclass="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><!--通过指定压缩文件名称,来确定分割文件方式--><fileNamePattern>C:/code/data-%d{yyyy-MMdd}.log%i.gz</fileNamePattern><!--文件拆分大小--><maxFileSize>1MB</maxFileSize></rollingPolicy>
</appender>
  • RollingFileAppender类表示日志信息输出到文件

  • <charset>utf-8</charset> 记录写入到文件中的字符集编码为UTF-8

  • <file>C:/code/data.log</file> 控制文件输出的路径

5.4 Logback日志级别的设置

以下是Logback中定义的日志级别:(从小到大)

  • TRACE:最低的日志级别,用于输出最详细的日志信息,通常用于调试目的。(追踪,指明程序运行轨迹)
  • DEBUG:用于输出调试信息,比TRACE级别更高,通常用于开发和调试阶段。
  • INFO:用于输出一般的信息性消息,表示程序正常运行的状态。
  • WARN:用于输出警告信息,表示潜在的问题或不符合预期的情况,但不会影响程序的正常运行。
  • ERROR:用于输出错误信息,表示严重的问题或错误,可能会导致程序的异常终止或不可恢复的错误。

除了以上五个标准的日志级别,Logback还支持自定义的日志级别。可以根据具体的需求定义自己的日志级别,并在配置文件中进行配置。

​ 在Logback中,可以通过配置文件(通常是logback.xmllogback.groovy)来设置日志级别。可以为不同的日志记录器(Logger)设置不同的日志级别,以控制不同部分的日志输出。

例如,可以使用以下配置将日志级别设置为DEBUG

  • 只有日志级别是大于或等于核心配置文件配置的日志级别,才会别记录,否则不会被记录
<root level="DEBUG"><appender-ref ref="CONSOLE"/><appender-ref ref="FILE" />
</root>

5.5 日志拆分

在 Logback 中,可以通过配置来实现日志的拆分。拆分日志可以按照时间、文件大小或者其他条件进行

  • 时间拆分:使用 TimeBasedRollingPolicy 可以按照时间来拆分日志文件。可以通过 <fileNamePattern> 指定拆分后的日志文件名格式,例如 logfile-%d{yyyy-MM-dd}.%i.log,其中 %d{yyyy-MM-dd} 表示按照日期进行拆分,%i 表示拆分后的文件索引。可以通过 <maxHistory> 指定保留的历史日志文件的数量。
  • 大小拆分:使用 SizeBasedTriggeringPolicy 可以按照文件大小来拆分日志文件。可以通过 <maxFileSize> 指定每个日志文件的最大大小,例如 10MB。当日志文件达到指定大小时,会自动拆分为新的日志文件。
  • 条件拆分:使用 OnMarkerEvaluator 可以根据自定义的条件来拆分日志文件。可以通过 <evaluator> 配置一个自定义的 OnMarkerEvaluator,并在代码中使用 Marker 来触发日志拆分。

演示:

将日志按照时间和文件大小进行拆分

<configuration><appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>/path/to/logfile.log</file><!-- 按照时间来拆分日志文件 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!-- 指定拆分后的日志文件名格式 --><fileNamePattern>/path/to/logfile-%d{yyyy-MM-dd}.%i.log</fileNamePattern><!-- 指定保留的历史日志文件的数量 --><maxHistory>30</maxHistory></rollingPolicy><!-- 按照文件大小来拆分日志文件 --><triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"><!-- 指定每个日志文件的最大大小 --><maxFileSize>10MB</maxFileSize></triggeringPolicy><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern></encoder></appender><root level="INFO"><appender-ref ref="FILE" /></root>
</configuration>

上面配置中,日志文件将按照日期进行拆分,每天生成一个新的日志文件。同时,当日志文件达到 10MB 时,也会触发拆分为新的日志文件

5.6 Logback过滤器

在 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%n</pattern></encoder></appender><root level="DEBUG"><appender-ref ref="CONSOLE" /></root><logger name="com.clear" level="DEBUG" /><filter class="ch.qos.logback.classic.filter.LevelFilter"><!-- 只输出 INFO 级别的日志 --><level>INFO</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter>
</configuration>

在上述配置中,所有的日志都会输出到控制台。但是通过 LevelFilter 过滤器,只有 INFO 级别的日志才会被接受(输出),其他级别的日志会被拒绝(不输出)

5.7 Logback异步日志

打印日志到文件中需要涉及到大量的文件IO,性能比较低,Logback之所以高效就是因为它支持异步日志

在 Logback 中,可以使用异步日志来提高日志的性能和吞吐量。异步日志将日志的写入操作放在一个独立的线程中进行,不会阻塞主线程的执行。

使用方式:

​ 要启用异步日志,可以使用 Logback 提供的 AsyncAppenderAsyncAppender 是一个包装器,可以将其他的 Appender 转换为异步的。

演示:

<configuration><appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender"><!-- 使用 AsyncAppender 包装了这个 FileAppender,将其转换为异步的 --><appender-ref ref="FILE" /><!-- 指定了异步队列的大小 --><queueSize>512</queueSize><!-- 指定了当队列满时是否丢弃日志 --><!-- 0,表示不丢弃任何日志事件。新的日志事件将被阻塞,直到队列中有空闲位置--><!-- 大于0,表示当队列已满时,丢弃最早的<discardingThreshold>个日志事件,然后将新的日志事件添加到队列中 --><!-- 如果对日志的实时性要求较高,可以将 <discardingThreshold> 的值设置为一个较小的数值,以避免队列过大导致的性能问题--><discardingThreshold>0</discardingThreshold></appender><!-- FileAppender,将日志输出到文件 logs/myapp.log --><appender name="FILE" class="ch.qos.logback.core.FileAppender"><file>logs/myapp.log</file><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern></encoder></appender><root level="DEBUG"><!-- 用于将一个或多个 appender 引用到根日志中。在这里,根日志引用了名为 ASYNC_FILE 的 appender,表示根日志的输出目标是 ASYNC_FILE appender--><appender-ref ref="ASYNC_FILE" /></root>
</configuration>

6 LOG4J2

​ Log4j 2 是一个用于 Java 应用程序的日志框架,它提供了灵活的配置和强大的日志功能。与 Logback 类似,Log4j 2 也支持异步日志,可以提高日志的性能和吞吐量(其实就是参考了Logback而设计出来的)

6.1 与Log4J的关系

LOG4J和LOG4J2都是Java的日志框架,它们都提供了强大的日志记录和管理功能。然而,它们在设计和实现上有一些区别。

  • 设计理念:LOG4J是LOG4J2的前身,它的设计目标是提供一个简单、灵活的日志框架。LOG4J2则是LOG4J的升级版,它在LOG4J的基础上进行了重构和改进,旨在提供更高的性能和更丰富的功能。
  • 性能:LOG4J2相对于LOG4J在性能上有所提升。LOG4J2使用了异步日志记录机制,可以在不阻塞应用程序的情况下进行日志记录,从而提高了应用程序的性能。
  • 配置方式:LOG4J使用基于属性的配置文件(如log4j.properties)或基于XML的配置文件(如log4j.xml)来配置日志记录器和输出目标。LOG4J2则引入了新的配置方式,支持使用XML、JSON、YAML等格式的配置文件,同时也支持使用编程API进行配置。
  • 插件支持:LOG4J2引入了插件机制,可以通过插件扩展日志框架的功能。LOG4J2提供了一些内置的插件,如异步日志记录器、日志事件过滤器等,同时也支持自定义插件。
  • 日志框架迁移:由于LOG4J2在设计和实现上与LOG4J有所不同,因此在迁移项目中使用LOG4J2时,可能需要进行一些代码和配置的调整。

总的来说,LOG4J2是LOG4J的升级版,它在性能、配置方式和功能扩展方面有所改进。如果你正在开始一个新的项目,或者希望提升现有项目的日志性能和功能,那么LOG4J2可能是一个更好的选择。但如果你已经在使用LOG4J,并且没有特别的需求,那么继续使用LOG4J也是可以的。

6.2 默认配置

DefaultConfiguration类提供的默认配置

private volatile Configuration configuration = new DefaultConfiguration();

6.3 Log4j2的使用

6.3.1 使用log4j-api做门面

要使用 Log4j 2,需要在项目中添加 Log4j 2 的依赖,并配置 Log4j 2 的配置文件

1)pom.xml

<dependencies><!-- Log4j2门面API--><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>2.14.1</version></dependency><!-- Log4j2日志实现--><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.14.1</version></dependency>
</dependencies>

2)配置 Log4j 2 的配置文件,位于类路径的根目录下。

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO"><Appenders><!-- 定义一个名为Console的Appender,将日志输出到控制台 --><Console name="Console" target="SYSTEM_OUT"><PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" /></Console></Appenders><Loggers><!-- 根日志的级别被设置为 DEBUG--><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);@Testpublic 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

6.3.2 使用slf4j做门面

使用slf4j作为日志的门面,使用log4j2作为日志的实现

1)pom.xml

<dependencies><!-- Log4j2门面API--><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>2.14.1</version></dependency><!-- Log4j2日志实现--><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.14.1</version></dependency><!-- 使用slf4j作为日志的门面,使用log4j2来记录日志--><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.32</version></dependency><!-- 为slf4j绑定日志实现 log4j2的适配器--><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j-impl</artifactId><version>2.12.1</version></dependency>
</dependencies>

6.4 log4j2异步日志

要使用 Log4j 2 的异步日志功能,你需要进行以下配置:

1)添加 Log4j 2 的异步日志依赖

pom.xml

<dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-async</artifactId><version>2.14.1</version>
</dependency>

2)创建 Log4j 2 的配置文件 log4j2.xml,并将其放置在类路径下

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<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);@Testpublic void test(){LOGGER.fatal("fatal");LOGGER.error("error");LOGGER.warn("warn");LOGGER.info("info");LOGGER.debug("debug");LOGGER.trace("trace");}
}

注意:

​ 异步日志可以提高性能,但也可能会导致日志消息的顺序不一致。如果你需要确保日志消息的顺序,请使用同步日志。

7 日志的打印

基本格式

必须使用参数化信息的方式:

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方法中对于系统/业务状态的变更
  • 主要逻辑的分步骤:1.初始化什么 2.加载什么
  • 外部接口的部分
  • 客户端请求参数(REST/WS)
  • 调用第三方时的调用参数和调用结果
  • 对于复杂的业务逻辑,需要进入日志打点,以及埋点记录
  • 调用其它第三方服务时,所有的出参和入参是必须要记录的(因为我们很难追溯第三方的错误)

说明

​ 并不是所有的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代替)

相关文章:

01-Java-日志框架

1 日志技术概述 1.1 什么是日志技术 ​ 日志技术是一种记录和存储应用程序运行时信息的技术。它可以捕获应用程序的状态、事件、错误和警告等信息&#xff0c;并将其保存到日志文件或其他存储介质中。日志技术可以帮助开发人员和运维团队了解应用程序的运行情况&#xff0c;进…...

【js】map、filter、reduce、fill(待补充...)

const arr [{ id: 1, flag: true },{ id: 2, flag: true },{ id: 3, flag: false },{ id: 4, flag: true }, ]map&#xff1a;返回的是对每个元素进行操作后的结果数组&#xff0c;这个数组的长度和原数组相同 const result arr.map((item: any) > {return item.flag fa…...

【JPC出版】第二届能源与电力系统国际学术会议 (ICEEPS 2023)

第二届能源与电力系统国际学术会议 (ICEEPS 2023) 2023 2nd International Conference on Energy and Electrical Power Systems 第二届能源与电力系统国际学术会议 (ICEEPS 2023)将于2023年10月27日至29日在中国厦门举行。ICEEPS 将汇集能源科学、电气工程和电力系统领域的…...

51单片机的简易篮球计分器倒计时仿真设计( proteus仿真+程序+原理图+报告+讲解视频)

51单片机的简易篮球计分器倒计时仿真设计( proteus仿真程序原理图报告讲解视频&#xff09; 1.主要功能&#xff1a;2.仿真3. 程序代码4. 原理图5. 设计报告6. 设计资料内容清单&&下载链接 51单片机的简易篮球计分器倒计时仿真设计( proteus仿真程序原理图报告讲解视频…...

医院安全不良事件报告系统源码 PHP+ vue2+element+ laravel8+ mysql5.7+ vscode开发

不良事件上报系统通过 “事前的人员知识培训管理和制度落地促进”、“事中的事件上报和跟进处理”、 以及 “事后的原因分析和工作持续优化”&#xff0c;结合预存上百套已正在使用的模板&#xff0c;帮助医院从对护理事件、药品事件、医疗器械事件、医院感染事件、输血事件、意…...

Mybatis -- 读取 DATE 类型字段时可能遇到的问题(夏令时问题)

在使用 MYBATIS 读取数据库字段的时候&#xff0c;我们一般需要为查询字段指定数据类型。特别是当我们使用 mybatis generator 去生成对应的接口代码时&#xff0c;会自动按照数据库字段类型生成响应映射规则的代码。   如下&#xff0c;左侧是 date 类型生成的字段映射规则&…...

第10节-PhotoShop基础课程-选区工具

文章目录 前言1.矩形选区 shift 是正方形1.任意比例2.等比绘制 先点击再按shift3.中心绘制1.任意比例 先点击再按Alt2.等比绘制 先点击再按Alt Shift 4.移动选区 按住空格 2.椭圆选区1.椭圆选区2.正圆选区 shift键3.中心圆选区 Alt Shift 3.选取选项1. 选区1.建立选区2.加选 s…...

【C++】C++动态内存管理

&#x1f3f3;️‍&#x1f308;C/C内存分布 说明&#xff1a; 1.我们的代码并非放在代码段里的&#xff0c;而是以文件的形式存在磁盘上的。 代码经过编译链接形成的二进制指令&#xff0c;才是放进代码段里的。&#xff08;即可执行代码&#xff09; 2.“abcd”如果没有被c…...

Java中使用JTS实现WKT字符串读取转换线、查找LineString的list中距离最近的线、LineString做缓冲区扩展并计算点在缓冲区内的方位角

场景 Java中使用JTS对空间几何计算(读取WKT、距离、点在面内、长度、面积、相交等)&#xff1a; Java中使用JTS对空间几何计算(读取WKT、距离、点在面内、长度、面积、相交等)_jts-core_霸道流氓气质的博客-CSDN博客 JavaGeoTools实现WKT数据根据EPSG编码进行坐标系转换&…...

【异步VS多线程】异步VS多线程区别

异步VS多线程区别 1、异步 异步概念&#xff1a;异步是并发编程的一种形式&#xff0c;在同一时刻可以独立于主程序外&#xff0c;可以并发执行另外一些任务。异步的实现方式有两种&#xff1a; 第一种&#xff1a;通过 async TASK来实现异步&#xff0c;第二种&#xff1a;通…...

【nosql】redis之高可用(主从复制、哨兵、集群)搭建

redis群集有三种模式 redis群集有三种模式&#xff0c;分别是主从同步/复制、哨兵模式、Cluster集群&#xff0c;下面会讲解一下三种模式的工作方式&#xff0c;以及如何搭建cluster群集 ●主从复制&#xff1a;主从复制是高可用Redis的基础&#xff0c;哨兵和集群都是在主从…...

js如何实现数组去重的常用方法

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 使用 Set&#xff08;ES6&#xff09;⭐ 使用 filter 和 indexOf⭐ 使用 reduce⭐ 使用对象属性⭐ 使用 includes 方法&#xff08;ES6&#xff09;⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方…...

XREAL Air 2 Pro发布,加入电致变色技术,拓展AR眼镜使用场景

【2023年9月6日 中国北京】继刚刚宣布XREAL Air在全球销量突破20万台后&#xff0c;全球领先的消费级AR眼镜品牌XREAL今日于中国市场正式推出XREAL Air 2系列新品。全新Air 2系列包含两款AR眼镜产品&#xff1a;在显示、佩戴舒适性、音频等核心维度全面升级&#xff0c;体验全面…...

Go基础11-理解Go语言的包导入

Go语言是使用包&#xff08;package&#xff09;作为基本单元来组织源码的&#xff0c;可以说一个Go程序就是由一些包链接在一起构建而成的。虽然与Java、Python等语言相比这算不上什么创新&#xff0c;但与祖辈C语言的头文件包含机制相比则是“先进”了许多。 编译速度快是这种…...

【MySQL数据库原理】在MySQL Workbench界面运行SQL代码——学生管理系统

在 MySQL Workbench 8.0 中&#xff0c;你可以使用以下步骤新建内容并运行 MySQL 语言代码&#xff1a; 1、打开 MySQL Workbench 并连接到你的 MySQL 数据库服务器。 2、在左侧的导航栏中&#xff0c;展开你的连接以查看数据库。选择你要在其中运行 SQL 代码的数据库。 3…...

高分三号1米分辨率飞机检测识别数据集

二、背景介绍 合成孔径雷达(Synthetic Aperture Radar, SAR) 是一种主动式的微波成像系统&#xff0c;它不受光照、云雾 和气候等自然条件影响&#xff0c;具备全天时、全天候对地 观测的能力&#xff0c;已成为遥感领域重要的信息获取平 台。近年来&#xff0c;随着遥感成像技…...

Unity 之Material 类型和 MeshRenderer 组件中的 Materials 之间有一些重要的区别

文章目录 区别代码例子 区别 在Unity中&#xff0c;Material 类型和 MeshRenderer 组件中的 Materials 之间有一些重要的区别。 Material 类型&#xff1a; Material 是 Unity 中用来定义渲染属性的资源。它包含了一系列定义了如何绘制一个对象的属性&#xff0c;比如颜色、纹…...

【LeetCode-简单题】977. 有序数组的平方

文章目录 题目方法一&#xff1a;双指针方法二&#xff1a; 题目 方法一&#xff1a;双指针 class Solution { // 方法一 &#xff1a;双指针public int[] sortedSquares(int[] nums) {int left 0;int right nums.length -1 ;int[] res new int[nums.length];//结果集新数组…...

【笔试强训选择题】Day39.习题(错题)解析

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;笔试强训选择题 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01;&#xff…...

Prometheus-Alertmanager 警报管理器-部署和设置

文章目录 一、介绍二、核心概念1 Grouping 分组2 Inhibition 抑制3 Silences 静默&#xff08;静音&#xff09;5 High Availability 高可用性 三、部署1 二进制方式下载配置 systemd 2 docker-compose 方式 四、配置1 配置文件介绍1.1 全局配置1.2 receiver 接收器标准接收器相…...

恒运资本:小盘股的优点?投资小盘股要注意哪些方面?

股市是一个充溢时机和危险的当地&#xff0c;不同出资者有不同的偏好&#xff0c;有的人喜爱追逐大盘蓝筹股&#xff0c;有的人则钟情于小盘股。那么小盘股的长处&#xff1f;出资小盘股要注意哪些方面&#xff1f;恒运资本也为我们准备了相关内容&#xff0c;以供参考。 小盘股…...

LeetCode:2. 两数之和

这个解题思路来自代码随想录&#xff1a;代码随想录 (programmercarl.com) class Solution { public:vector<int> twoSum(vector<int>& nums, int target) {std::unordered_map <int,int> map;for(int i 0; i < nums.size(); i) {// 遍历当前元素&am…...

OpenCV(二十四):可分离滤波

目录 1.可分离滤波的原理 2.可分离滤波函数sepFilter2D() 3.示例代码 1.可分离滤波的原理 可分离滤波的原理基于滤波器的可分离性。对于一个二维滤波器&#xff0c;如果它可以表示为水平方向和垂直方向两个一维滤波器的卷积&#xff0c;那么它就是可分离的。也就是说&#x…...

【JS进阶】防抖与节流

防抖与节流 1.防抖 1.1 为什么要防抖&#xff1f; 在项目中&#xff0c;有的操作是高频触发的&#xff0c;但是其实触发一次就好了&#xff0c;比如我们短时间内多次缩放页面&#xff0c;那么我们不应该每次缩放都去执行操作&#xff0c;应该只做一次就好。再比如说监听输入…...

【css】linear-gradient()的用法

linear-gradient() CSS函数创建一个由两种或多种颜色沿一条直线进行线性过渡的图像,其结果是<gradient>数据类型的对象,此对象是一种特殊的<image> 数据类型。 语法 /* 渐变轴为 45 度&#xff0c;从蓝色渐变到红色 */ linear-gradient(45deg, blue, red);/* 从右…...

java: 读取snakeyaml-1.26.jar各种jar包时出错; error in opening zip file

可能的问题 jar有问题idea没有权限等等其他问题。但执行后报错就是读取不了&#xff0c;还报error in opening zip file这个错。 解决问题 我的错就是jar包有问题。我先后进行了很多次把jar包位置里的东西全部删除&#xff0c;然后重新maven下载但是不管用。最后从网站上下载…...

医疗知识图谱 neo4j

开源项目&#xff1a; https://github.com/liuhuanyong/QASystemOnMedicalKG 一.效果 二.需要安装&#xff1a; pip install pyahocorasick pip install py2neo 三.需要修改&#xff1a; 需要改的点&#xff1a; 1.改连接的方式 2.改读文件的方式 MedicalGraph 运行&am…...

【LeetCode-简单题】367. 有效的完全平方数

文章目录 题目方法一&#xff1a;二分查找 题目 方法一&#xff1a;二分查找 找 1 - num 之间的 mid&#xff0c; 开方是整数 就找得到 mid&#xff0c; 不是整数自然找不到mid class Solution { // 二分查找 &#xff1b;找 1 - num 之间的mid 开方是整数 就找得到 不是…...

vben-admin中渲染table表格时怎么处理不同的数据结构

最近在用vben admin开发后台管理系统&#xff0c;vben admin这个后管端框架封装的非常细&#xff0c;颗粒度非常细&#xff0c;如果了解里面的组件或者api用法&#xff0c;那开发起来非常快。如果不了解&#xff0c;那就非常痛苦了&#xff0c;目前关于vben admin这块的开发问题…...

从零开始在树莓派上搭建WordPress博客网站并实现公网访问

文章目录 序幕概述1. 安装 PHP2. 安装MySQL数据库3. 安装 Wordpress4. 设置您的 WordPress 数据库设置 MySQL/MariaDB创建 WordPress 数据库 5. WordPress configuration6. 将WordPress站点发布到公网安装相对URL插件修改config.php配置 7. 支持好友链接样式8. 定制主题 序幕 …...

有做网站代理运营的吗/品牌咨询

我们的疑问是&#xff1a;iPad充电器可以给iPhone充电吗&#xff0c;iPad充电器不能为iPhone充电吗&#xff1f;使用iPad给iPhone充电的危害&#xff1f;用iPhone充电器会损害iPad吗&#xff1f;iPad充电器可以给iPhone快充吗&#xff1f;iPhone用iPad充电器是不是可以的呢&…...

微信端微网站怎么做/seo网站管理

2.1 问题 沿用练习一&#xff0c;通过调整Nginx服务端配置&#xff0c;实现以下目标&#xff1a; 访问Web页面需要进行用户认证 用户名为&#xff1a;tom&#xff0c;密码为&#xff1a;1234562.2 方案 通过Nginx实现Web页面的认证&#xff0c;需要修改Nginx配置文件&#x…...

旅游网站只做/seo数据分析

介绍 Python代码审计方法多种多样&#xff0c;但是总而言之是根据前人思路的迁移融合扩展而形成。目前Python代码审计思路&#xff0c;呈现分散和多样的趋势。Python微薄研发经验以及结合实际遇到的思路和技巧进行总结&#xff0c;以便于朋友们的学习和参考。 SQL注入和ORM注入…...

河南省工程建设信息网官网入口/开封网站快速排名优化

地址&#xff1a;http://poj.org/problem?id2486 题意&#xff1a;有一颗苹果树&#xff0c;每个节点上面有很多苹果&#xff0c;从一个节点到另外一个可以到达的节点花费1步&#xff0c;求k步最多能吃到多少苹果。 mark&#xff1a;这是典型的回溯型树状dp。dp[i][j][0]代表以…...

网站建设ftp上传是空目录/为什么不能去外包公司

安装使用 运行命令 yarn add reduxredux 原理图 主要流程 组件 通过 Action Creators 分发(dispatch) action 给 StoreStore 将 之前的状态(previousState)、action&#xff0c;传递给 ReducersReducers 对状态处理后&#xff0c;返回给 Store 新的状态组件通过 getState() 获…...

网站改版提交给百度/百度系优化

就目前大环境来看&#xff0c;跳槽成功的难度比往年高很多。总结一下2019面试的感受&#xff1a;无论一面还是二面&#xff0c;都很考验Java程序员的技术功底&#xff01;&#xff01;最近我整理了一份复习用的面试题及面试高频的考点题及技术点梳理成一份“Java程序员高频面试…...