简单的springboot log4j2日志配置
1.简介
Log4j2 是 Apache Software Foundation
开发的一个日志记录工具,它是 Log4j 的后续版本,并且在多个方面进行了改进。以下是 Log4j2 的一些关键特性:
-
性能提升:Log4j2 在设计上做了很多优化来提高日志记录的效率。例如,它使用了更高效的查找表来减少日志记录时的开销。
-
可靠性增强:Log4j2 支持异步日志记录,这有助于提高应用程序的整体性能。异步处理可以避免日志记录阻塞应用程序线程。
-
灵活性增加:Log4j2 提供了一个可插拔的架构,允许用户根据需要选择不同的布局、appender 和其他组件。这种模块化的设计使得配置更加灵活。
-
简化配置:Log4j2 使用 XML、JSON 或 YAML 文件进行配置,相较于 Log4j 的 XML 配置,提供了更多的灵活性和易用性。
丰富的功能集:Log4j2 包含了许多高级功能,如支持多种日志级别、过滤器、布局以及多种输出目的地(比如文件、控制台、数据库等)。 -
安全性改进:鉴于之前在 Log4j 中发现的安全问题,Log4j2 在设计时考虑到了安全性,尤其是在处理外部输入数据时更为谨慎。
Log4j2 是一个广泛使用的日志框架,在 Java 应用程序中非常受欢迎。然而,值得注意的是,Log4j2 也曾经历过一些严重的安全漏洞,比如著名的 Log4Shell 漏洞(CVE-2021-44228),这是一个远程代码执行漏洞,影响了大量的系统和服务。因此,在使用 Log4j2 时,确保使用最新版本并及时应用安全更新是非常重要的。
在 Java 日志领域还有其他的日志框架,Log4j2、Log4j、Logback 和 SLF4J 这几个框架扮演着不同的角色,相互之间还有些许关联,此处整理一下它们之间的关系如下:
- Log4j
Log4j 是 Apache 软件基金会开发的第一个日志框架,它为 Java 应用程序提供了强大的日志功能。由于其稳定性和广泛的使用,Log4j 成为了早期 Java 应用的标准日志解决方案之一。 - Log4j2
Log4j2 是 Log4j 的继任者,它在 Log4j 的基础上进行了大量的改进,包括性能优化、新的配置方式(XML、JSON 或 YAML)、异步日志处理等功能。虽然两者名称相似,但是 Log4j2 并不向后兼容 Log4j。 - Logback
Logback 是 Log4j 的另一个替代品,由 Log4j 的原始作者 Ceki Gülcü 创建。它旨在作为 Log4j 的一个改进版本,并且与 Log4j 具有较高的兼容性。Logback 同样支持异步日志记录,并且具有更好的性能。 - SLF4J (Simple Logging Facade for Java)
SLF4J 不是一个实际的日志实现,而是一个抽象层或门面(Facade)。它的目的是提供一个简单的 API,以便于开发者编写日志代码,同时允许在运行时动态地绑定到不同的日志框架(如 Logback、Log4j、java.util.logging 等)。这样可以在不影响应用代码的情况下更换底层的日志实现。
这些日志框架之间的关系可以总结为:
- SLF4J 是一个日志门面,它提供了一套统一的日志 API。
- Logback 默认实现了 SLF4J 接口,可以直接与 SLF4J 一起工作。
- Log4j 和 Log4j2 可以通过适配器(如 slf4j-log4j12 或 log4j-slf4j-impl)与 SLF4J 一起使用。
- Log4j 和 Log4j2 是独立的日志框架,直接提供日志功能,不需要通过 SLF4J。
在实际应用中,通常会选择一个具体的日志框架(如 Logback 或 Log4j2),并通过 SLF4J 来编写日志代码,以提高代码的可移植性和灵活性。我们项目使用的是log4j2日志框架进行配置,下面主要对log4j2日志框架配置进行梳理方便日后复习使用。
2. 配置简介
2.1 日志级别
log4j2有8个级别 从低到高为 ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF,我们作为web服务器,主要配置level为 INFO 级别
等级 | 描述 |
---|---|
ALL | 最低等级,用于打印所有日志记录信息 |
TRACE | 追踪程序运行到哪里 |
DEBUG | 消息颗粒度强调调试层面日志信息,展示详细执行信息 |
INFO | 消息颗粒度突出强调应用正常的执行逻辑日志信息 |
WARN | 输出警告 |
ERROR | 输出错误日志信息 |
FATAL | 输出每个严重错误时间,会导致应用程序退出日志 |
OFF | 最高等级,关闭所有日志记录 |
2.2 Appenders种类
常用的文件追加器 appenders 信息如下
Appenders名称 | 具体作用 |
---|---|
FlumeAppender | 将几个不同源的日志汇集、集中到一处 |
RewriteAppender | 对日志事件进行掩码或注入信息 |
RollingFileAppender | 对日志文件进行封存 |
RoutingAppender | 在输出地之间进行筛选路由 |
SMTPAppender | 将LogEvent发送到指定邮件列表 |
SocketAppender | 将LogEvent以普通格式发送到远程主机 |
SyslogAppender | 将LogEvent以RFC 5424格式发送到远程主机 |
AsynchAppender | 将一个LogEvent异步地写入多个不同输出地 |
ConsoleAppender | 将LogEvent输出到控制台 |
FailoverAppender | 维护一个队列,系统将尝试向队列中的Appender依次输出LogEvent,直到有一个成功为止 |
2.3 PatternLayout格式详解
PatternLayout 是最重要也是最常用的控制输出内容的节点,包括类名、时间、行号、日志级别、序号等都可以控制,同时还可以指定日志格式,可以使用正则表达式处理输出结果。
PatternLayout中包含的特殊字符包括\t,\n,\r,\f,用\输出单斜线,用%%输出%,下面是PatternLayout的参数
参数名称 | 类型 | 描述 |
---|---|---|
charset | String | 输出的字符集。如果没有指定,则使用系统默认的字符集输出 |
pattern | String | 详见后面的pattern的表格 |
replace | RegexReplacement | 替换部分输出中的String。这将会调用一个与RegexReplacement转换器相同的函数,不同的是这是针对整个消息的 |
alwaysWriteExceptions | boolean | 默认为true。总是输出异常,即使没有定义异常对应的pattern,也会被附在所有pattern的最后。设为false则不会输出异常 |
header | String | 可选项。包含在每个日志文件的顶部 |
footer | String | 可选项。包含在每个日志文件的尾部 |
noConsoleNoAnsi | boolean | 默认为false。如果为true,且System.console()是null,则不会输出ANSI转义码 |
RegexReplacement参数
参数名称 | 类型 | 具体作用描述 |
---|---|---|
regex | String | 将几个不同源的日志汇集、集中到一处 |
replacement | String | 任何匹配正则规则,被正则替换的后值 |
下面是pattern属性具体描述(这个表格仅包括了我能看懂的部分,还有很多看不懂并且试验也不成功的部分我都没写。另外用于控制输出结果颜色的highlight和style我也没有写,实在是感觉平时意义不大):
参数名 | 参数意义 | 详细描述 |
---|---|---|
%c{参数}或%logger{参数} | 输出logger的名称,即语句private static final Logger logger = LogManager.getLogger(App.class.getName())中App.class.getName()的值。也可以使用其他字符串 | 如果不带参数,则输出完整的logger名称;如果参数是整数n(只支持正整数),则先将logger名称依照小数点(.)分割成n段,然后取右侧的n段;如果参数不是整数,则除了最右侧的一段,其他整段字符都用这个字符代替,保留小数点;不管怎么写,最右侧的一段都保持不变。默认不带参数,并输出logger的完整名称。注意:上面的说明写得很烂,但是官方文档写得就这么烂,而且还不完整,看不懂是必然的,还请看官自己多试验,才能有所领会。如果看官自己懒得试验又看不懂,只能建议不要加参数,直接%c输出完整值。 |
%C{参数}或%class{参数} | 输出类名。注意,这个是大写C,上面是小写c | 参数规则与%c完全一样,请参见上面的说明。 |
%d{参数}{时区te{参数}{时区} | 输出时间。 | 第一个大括号数可以是保留关键字,也可以是text.SimpleDateFormat字符拼接而成。保留关键字有:DEFAULT,ABSOLUTE, COMPACT, DATE, ISO8601, ISO8601_BASIC。第二个大括号中的参数是java.util.TimeZone.getTimeZone的值,可以设定时区 |
无 | 输出特殊字符 | &, <, >, ”, ’全都要使用实体名称或实体编号替代,即官方文档说pattern删除了\r和\n,但是经我测试可以使用,明显是官方文档的错误 |
%F | %file | 输出文件名 |
highlight{pattern}{style} | 高亮显示结果 | |
%l | 输出完整的错误位置,如com.future.ourfuture.test.test.App.tt(App.java:13) | 注意1:这个是小写的L。注意2:使用该参数会影影响日志输出的性能 |
%L | 输出错误行号,如“13” | 注意:使用该参数会影响日志输出的性能 |
%m或%msg或%message | 输出错误信息,即logger.error(String msg)中的msg | |
%M或%method | 输出方法名,如“main”,“getMsg”等字符串 | |
%n | 换行符 | 根据系统自行决定,如Windows是”\r\n”,Linux是”\n” |
%level{参数1}{参数2}{参数3} | 参数1用来替换日志信息的级别,格式为:{level=label, level=label, …},即使用label代替的字符串替换level。其中level为日志的级别,如WARN/DEBUG/ERROR/TRACE/INFO参数2表示只保留前n个字符。格式为length=n,n为整型。但参数1中指定了label的字符串不受此参数限制参数3表示结果是大写还是小写。参数1指定了label的字符串不受此参数限制 | |
%r或%relative | 输出自JVM启动以来到log事件开始时的毫秒数 | |
replace{pattern}{regex}{substitution} | 将pattern的输出结果,按照正则表达式regex匹配后,使用substitution字符串替换 | 例如:"%replace{%logger }{.}{/}就是将所有%logger中的小数点(.)全部替换为斜杠,如果%logger是com.future.ourfuture.test.test.App则输出为com/future/ourfuture/test/test/App。pattern中可以写多个表达式,如%replace{%logger%msg%C}{.}{/}%n |
%sn或%sequenceNumber | 自增序号,每执行一次log事件,序号+1,是一个static变量。 | |
%t或%thread | 创建logging事件的线程名 | |
%u{RANDOM | TIME}或%uuid{RANDOM | TIME} |
patten表达式
pattern表达式 | logger名称 | 响应结果 |
---|---|---|
%c{1} | org.apache.com.te.Foo | Foo |
%c{2} | org.apache.com.te.Foo | te.Foo |
%c{1.} | org.apache.com.te.Foo | o.a.c.t.Foo |
%c{1.1.!} | org.apache.com.te.Foo | o.a.!.!.Foo |
%c{.} | org.apache.com.te.Foo | ….Foo |
pattern的对齐修饰
对齐修饰,可以指定信息的输出格式,如是否左对齐,是否留空格等。
编写格式为在任何pattern和%之间加入一个小数,可以是正数,也可以是负数。如%10.20c表示对logger的信息进行处理。%-10.20m表示对message进行处理。
整数表示右对齐,负数表示左对齐;整数位表示输出信息的最小10个字符,如果输出信息不够10个字符,将用空格补齐;小数位表示输出信息的最大字符数,如果超过20个字符,则只保留最后20个字符的信息(注意:保留的是后20个字符,而不是前20个字符)。下面是一些示例。
格式 | 是否左对齐 | 最小宽度 | 最大宽度 | 说明 |
---|---|---|---|---|
%20 | 右对齐 | 20 | 无 | 右对齐,不足20个字符则在信息前面用空格补足,超过20个字符则保留原信息 |
%-20 | 左对齐 | 20 | 无 | 左对齐,不足20个字符则在信息后面用空格补足,超过20个字符则保留原信息 |
%.30 | 不对齐 | 无 | 30 | 如果信息超过30个字符,则只保留最后30个字符 |
%20.30 | 右对齐 | 20 | 30 | 右对齐,不足20个字符则在信息前面用空格补足,超过30个字符则只保留最后30个字符 |
%-20.30 | 左对齐 | 20 | 30 | 左对齐,不足20个字符则在信息后面用空格补足,超过30个字符则只保留最后30个字符 |
我们项目中的log4j2.xml日志格式具体配置日志格式附带格式注释信息如下:
<!-- elk日志格式 -->
<property name="patternLayout">[%d{yyyy-MM-dd'T'HH:mm:ss.SSSZZ}] [%level{length=5}] [%traceId] [%logger] [${sys:hostName}] [${sys:ip}] [${sys:applicationName}] [%F,%L,%C,%M] [%m] ## '%ex'%n</property>
- [%d{yyyy-MM-dd’T’HH:mm:ss.SSSZZ}] 日期 美国时间
- [%level{length=5}] 日志级别
- [%traceId] 链路追踪id,skyWalking使用
- [%logger] 记录日志的类或包的全限定名。这有助于在日志输出中明确标识日志来源
- [${sys:hostName}] 自定义主机名称,System.setProperty(“hostName”, NetUtil.getLocalHostName());
- [${sys:ip}] 自定义系统ip信息 ,System.setProperty(“ip”, NetUtil.getLocalIp());
- [${sys:applicationName}] 应用名称
- [%F,%L,%C,%M] / [当前执行类, 行号, 全类名, 方法名称]
- [%m] 日志输出内容
##
自己特殊约定'%ex'%n
两个引号将异常包裹,打出异常时候方便解析 如何抛异常 和 换行
log4j2 patternLayOut参考此博文
3.项目中使用
我们整体的使用需要先引入pom文件,将服务ip信息存入到系统变量中,供log4j2配置文件使用, 再进行log4j2配置文件配置,最后在代码中可以使用slf4j日志门脸进行日志调用,添加日志后通过elk
我们可以快速定为到线上的问题,哪个服务在哪个机器上,具体发生了哪些问题,提高生产问题排错效率。
3.1 pom配置
在spring boot项目中,默认使用的日志框架是Logback,所以我们需要排除掉其自身引用的日志框架再引入log4j2日志jar包。引入pom内容如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.4</version>
</dependency>
3.2 ip信息初始化到系统变量中
通过代码获取ip信息,具体工具类NetUtil实现如下
package cn.git.elk.util;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketAddress;
import java.net.SocketChannel;
import java.net.UnknownHostException;
import java.nio.channels.SocketChannel;
import java.util.Enumeration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @program: bank-credit-sy
* @description: $NetUtil 获取ip地址hostname工具类
* @author: lixuchun
* @create: 2021-02-04 14:52
*/
public class NetUtil {
// 正则表达式模式,用于匹配 IP 地址
private static Pattern pattern;
// IP 地址分隔符限制长度
private static Integer BLOCKS_LIMIT_LENGTH_2 = 2;
// 默认主机名
private static String HOST = "0.0.0.0";
/**
* 格式化输入的地址字符串,确保其包含主机名和端口号,并默认设置端口为 80。
* @param address 地址字符串
* @return 格式化后的地址字符串
*/
public static String normalizeAddress(String address){
// 将地址字符串按冒号分割成数组
String[] blocks = address.split(":");
// 检查地址是否有效
if(blocks.length > BLOCKS_LIMIT_LENGTH_2){
throw new IllegalArgumentException(address + " is invalid");
}
// 获取主机名
String host = blocks[0];
// 默认端口号为 80
int port = 80;
// 如果地址包含端口号,则提取端口号
if(blocks.length > 1){
port = Integer.valueOf(blocks[1]);
} else {
// 使用默认端口 80
address += ":" + port;
}
// 格式化并返回地址
String serverAddr = String.format("%s:%d", host, port);
return serverAddr;
}
/**
* 如果输入地址中的主机名为“0.0.0.0”,则用本地 IP 地址替换后返回格式化的地址。
* @param address 地址字符串
* @return 格式化后的地址字符串
*/
public static String getLocalAddress(String address){
// 将地址字符串按冒号分割成数组
String[] blocks = address.split(":");
// 检查地址是否有效
if(blocks.length != BLOCKS_LIMIT_LENGTH_2){
throw new IllegalArgumentException(address + " is invalid address");
}
// 获取主机名
String host = blocks[0];
// 获取端口号
int port = Integer.valueOf(blocks[1]);
// 如果主机名为“0.0.0.0”,则替换为本地 IP 地址
if(HOST.equals(host)){
return String.format("%s:%d", NetUtil.getLocalIp(), port);
}
// 否则直接返回原地址
return address;
}
/**
* 检查给定的 IP 是否匹配优先级列表中的前缀,并返回匹配的索引。
* @param ip IP 地址
* @param prefix 优先级列表
* @return 匹配的索引
*/
private static int matchedIndex(String ip, String[] prefix){
// 遍历优先级列表
for(int i=0; i<prefix.length; i++){
String p = prefix[i];
// 如果前缀为“*”,则检查 IP 是否为内网地址
if("*".equals(p)){
if(ip.startsWith("127.") ||
ip.startsWith("10.") ||
ip.startsWith("172.") ||
ip.startsWith("192.")){
continue;
}
return i;
} else {
// 检查 IP 是否以指定前缀开头
if(ip.startsWith(p)){
return i;
}
}
}
// 如果没有匹配,则返回 -1
return -1;
}
/**
* 获取本地 IP 地址,根据优先级选择最优 IP 地址;如果没有指定优先级,则使用默认优先级顺序。
* @param ipPreference IP 优先级字符串
* @return 本地 IP 地址
*/
public static String getLocalIp(String ipPreference) {
// 如果未指定优先级,则使用默认优先级
if(ipPreference == null){
ipPreference = "*>10>172>192>127";
}
// 分割优先级字符串
String[] prefix = ipPreference.split("[> ]+");
try {
// 编译正则表达式模式
pattern = Pattern.compile(PATTEN_COMPARE_RULES);
// 获取所有网络接口
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
// 初始化最佳匹配 IP 和索引
String matchedIp = null;
int matchedIdx = -1;
// 遍历所有网络接口
while (interfaces.hasMoreElements()) {
NetworkInterface ni = interfaces.nextElement();
// 获取每个网络接口的所有 IP 地址
Enumeration<InetAddress> en = ni.getInetAddresses();
// 遍历所有 IP 地址
while (en.hasMoreElements()) {
InetAddress addr = en.nextElement();
String ip = addr.getHostAddress();
// 匹配 IP 地址
Matcher matcher = pattern.matcher(ip);
if (matcher.matches()) {
// 获取匹配的索引
int idx = matchedIndex(ip, prefix);
if(idx == -1) {
continue;
}
// 更新最佳匹配 IP 和索引
if(matchedIdx == -1){
matchedIdx = idx;
matchedIp = ip;
} else {
if(matchedIdx > idx){
matchedIdx = idx;
matchedIp = ip;
}
}
}
}
}
// 如果找到最佳匹配 IP,则返回;否则返回“127.0.0.1”
if(matchedIp != null) {
return matchedIp;
}
return "127.0.0.1";
} catch (Exception e) {
return "127.0.0.1";
}
}
/**
* 获取本地 IP 地址,默认使用优先级顺序。
* @return 本地 IP 地址
*/
public static String getLocalIp() {
return getLocalIp("*>10>172>192>127");
}
/**
* 返回给定 SocketChannel 对象的远程地址信息。
* @param channel SocketChannel 对象
* @return 远程地址信息
*/
public static String remoteAddress(SocketChannel channel){
// 获取远程地址
SocketAddress addr = channel.socket().getRemoteSocketAddress();
// 格式化并返回地址信息
String res = String.format("%s", addr);
return res;
}
/**
* 返回给定 SocketChannel 对象的本地地址信息,去掉可能存在的冒号前缀。
* @param channel SocketChannel 对象
* @return 本地地址信息
*/
public static String localAddress(SocketChannel channel){
// 获取本地地址
SocketAddress addr = channel.socket().getLocalSocketAddress();
// 格式化并返回地址信息
String res = String.format("%s", addr);
// 如果地址不为空,则去掉第一个字符(通常是“/”)
return addr == null ? res : res.substring(1);
}
/**
* 获取当前 Java 进程 ID。
* @return 进程 ID
*/
public static String getPid(){
// 获取运行时管理对象
RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
// 获取名称
String name = runtime.getName();
// 查找“@”符号的位置
int index = name.indexOf("@");
if (index != -1) {
// 提取进程 ID
return name.substring(0, index);
}
// 如果未找到,则返回 null
return null;
}
/**
* 获取本地主机名。
* @return 本地主机名
*/
public static String getLocalHostName() {
try {
// 获取本地 IP 地址并提取主机名
return (InetAddress.getLocalHost()).getHostName();
} catch (UnknownHostException uhe) {
// 处理异常情况
String host = uhe.getMessage();
if (host != null) {
int colon = host.indexOf(':');
if (colon > 0) {
// 提取主机名部分
return host.substring(0, colon);
}
}
// 如果无法获取主机名,则返回“UnknownHost”
return "UnknownHost";
}
}
}
初始化ip信息到系统变量中代码部分如下
package cn.git.init;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @description: 初始化设置ip hostname等信息通用类
* @program: bank-credit-sy
* @author: lixuchun
* @create: 2024-01-03
*/
@Component
public class InitLogIpHost implements EnvironmentAware {
private static Environment environment;
@PostConstruct
public void initIpHostEnvInfo() {
// 设置 applicationName
System.setProperty("applicationName", environment.getProperty("spring.application.name"));
// 设置 ip
System.setProperty("ip", NetUtil.getLocalIp());
// 设置 hostname
System.setProperty("hostName", NetUtil.getLocalHostName());
}
/**
* Set the {@code Environment} that this component runs in.
*
* @param environment
*/
@Override
public void setEnvironment(Environment environment) {
InitLogIpHost.environment = environment;
}
}
3.3 log4j2.xml日志文件配置
<?xml version="1.0" encoding="UTF-8"?>
<Configuration schema="Log4J-V2.0.xsd" monitorInterval="600">
<!-- 配置全局属性 -->
<Properties>
<!-- 日志文件保存的基本路径 -->
<Property name="LOG_HOME">logs</Property>
<!-- 日志文件的基础名称 -->
<property name="FILE_NAME">docker-server</property>
<!-- 日志输出格式 -->
<property name="patternLayout">[%d{yyyy-MM-dd'T'HH:mm:ss.SSSZZ}] [%level{length=5}] [%traceId] [%logger] [${sys:hostName}] [${sys:ip}] [${sys:applicationName}] [%F,%L,%C,%M] [%m] ## '%ex'%n</property>
</Properties>
<!-- 定义不同的日志输出目的地 -->
<Appenders>
<!-- 控制台输出 -->
<Console name="CONSOLE" target="SYSTEM_OUT">
<!-- 使用定义的日志格式 -->
<PatternLayout pattern="${patternLayout}"/>
<!-- 只允许 info 级别及以上的日志输出到控制台 -->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
</Console>
<!-- 应用程序日志滚动文件 -->
<RollingRandomAccessFile name="appAppender" fileName="${LOG_HOME}/app-${FILE_NAME}.log" filePattern="${LOG_HOME}/app-${FILE_NAME}-%d{yyyy-MM-dd}-%i.log" >
<!-- 使用定义的日志格式 -->
<PatternLayout pattern="${patternLayout}" />
<!-- 滚动策略 -->
<Policies>
<!--
根据当前filePattern配置"%d{yyyy-MM-dd}",每interval天滚动一次
"%d{yyyy-MM-dd HH-mm}" 则为每interval分钟滚动一次
-->
<TimeBasedTriggeringPolicy interval="1"/>
<!-- 文件大小超过 500MB 时滚动 -->
<SizeBasedTriggeringPolicy size="500MB"/>
</Policies>
<!-- DefaultRolloverStrategy 属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖 -->
<DefaultRolloverStrategy max="20"/>
</RollingRandomAccessFile>
<!-- Druid SQL 日志滚动文件 -->
<RollingRandomAccessFile name="druidSqlRollingFile" fileName="${LOG_HOME}/druid/app-${FILE_NAME}-druid.log" filePattern="${LOG_HOME}/app-${FILE_NAME}-druid-%d{yyyy-MM-dd}-%i.log" >
<!-- 使用定义的日志格式 -->
<PatternLayout pattern="${patternLayout}" />
<!-- 滚动策略 -->
<Policies>
<!-- 每天滚动一次 -->
<TimeBasedTriggeringPolicy interval="1"/>
<!-- 文件大小超过 500MB 时滚动 -->
<SizeBasedTriggeringPolicy size="500MB"/>
</Policies>
<!-- 最多保留 20 个旧日志文件 -->
<DefaultRolloverStrategy max="20"/>
</RollingRandomAccessFile>
<!-- skywalking GRPC 日志客户端 Appender -->
<GRPCLogClientAppender name="grpc-log">
<!-- 使用简单的日志格式 -->
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</GRPCLogClientAppender>
</Appenders>
<!-- 定义不同日志记录器 -->
<Loggers>
<!-- 关闭 org.apache.kafka 包下的所有日志输出 -->
<logger name="org.apache.kafka" level="off"/>
<!-- 设置 druid 包的日志级别为 error,并关闭继承父 Logger 的行为 -->
<logger name="druid" level="error" additivity="false">
<appender-ref ref="druidSqlRollingFile"/>
</logger>
<!-- 设置 cn.git.* 包的日志级别为 info,并关闭继承父 Logger 的行为 -->
<logger name="cn.git.*" level="info" additivity="false">
<AppenderRef ref="grpc-log"/>
</logger>
<!-- 创建一个异步 Logger,用于处理 cn.git.* 包的日志,并指定日志输出到 appAppender -->
<AsyncLogger name="cn.git.*" level="info" includeLocation="true">
<AppenderRef ref="appAppender"/>
</AsyncLogger>
<!-- 设置根 Logger 的日志级别为 info,并指定日志输出到控制台、appAppender 和 grpc-log -->
<root level="info">
<AppenderRef ref="CONSOLE"/>
<Appender-Ref ref="appAppender"/>
<AppenderRef ref="grpc-log"/>
</root>
</Loggers>
</Configuration>
4. 测试
我们启动服务,然后在定时任务中打印一个简单的日志信息,并且运行时候可能会报错,task代码如下
package cn.git.task;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* @description: 简单定时任务
* @program: bank-credit-sy
* @author: lixuchun
* @create: 2024-07-10
*/
@Slf4j
@Component
@EnableScheduling
public class TimerTask {
/**
* 每5秒执行一次
*/
@Scheduled(cron = "0/5 * * * * ?")
public void timer() {
log.info("定时任务执行 : " + System.currentTimeMillis());
if (System.currentTimeMillis() % 2 == 0) {
throw new RuntimeException("异常啦!");
}
}
}
我们观察运行结果日志信息如下
"C:\Program Files\Java\jdk1.8.0_131\bin\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:61667,suspend=y,server=n -Dvisualvm.id=6066284867800 -javaagent:C:\Users\Administrator.DESKTOP-40G9I84\AppData\Local\JetBrains\IdeaIC2020.3\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_131\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar;D:\idea_workspace_activiti_change\docker-hello\target\classes;D:\apache-maven-3.6.3\repos\org\projectlombok\lombok\1.18.6\lombok-1.18.6.jar;D:\apache-maven-3.6.3\repos\cn\hutool\hutool-all\5.5.7\hutool-all-5.5.7.jar;D:\apache-maven-3.6.3\repos\com\alibaba\fastjson\1.2.83\fastjson-1.2.83.jar;D:\apache-maven-3.6.3\repos\org\springframework\boot\spring-boot-starter-web\2.3.8.RELEASE\spring-boot-starter-web-2.3.8.RELEASE.jar;D:\apache-maven-3.6.3\repos\org\springframework\boot\spring-boot-starter\2.3.8.RELEASE\spring-boot-starter-2.3.8.RELEASE.jar;D:\apache-maven-3.6.3\repos\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;D:\apache-maven-3.6.3\repos\org\yaml\snakeyaml\1.26\snakeyaml-1.26.jar;D:\apache-maven-3.6.3\repos\org\springframework\boot\spring-boot-starter-json\2.3.8.RELEASE\spring-boot-starter-json-2.3.8.RELEASE.jar;D:\apache-maven-3.6.3\repos\com\fasterxml\jackson\core\jackson-databind\2.11.4\jackson-databind-2.11.4.jar;D:\apache-maven-3.6.3\repos\com\fasterxml\jackson\core\jackson-annotations\2.11.4\jackson-annotations-2.11.4.jar;D:\apache-maven-3.6.3\repos\com\fasterxml\jackson\core\jackson-core\2.11.4\jackson-core-2.11.4.jar;D:\apache-maven-3.6.3\repos\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.11.4\jackson-datatype-jdk8-2.11.4.jar;D:\apache-maven-3.6.3\repos\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.11.4\jackson-datatype-jsr310-2.11.4.jar;D:\apache-maven-3.6.3\repos\com\fasterxml\jackson\module\jackson-module-parameter-names\2.11.4\jackson-module-parameter-names-2.11.4.jar;D:\apache-maven-3.6.3\repos\org\springframework\boot\spring-boot-starter-tomcat\2.3.8.RELEASE\spring-boot-starter-tomcat-2.3.8.RELEASE.jar;D:\apache-maven-3.6.3\repos\org\apache\tomcat\embed\tomcat-embed-core\9.0.41\tomcat-embed-core-9.0.41.jar;D:\apache-maven-3.6.3\repos\org\glassfish\jakarta.el\3.0.3\jakarta.el-3.0.3.jar;D:\apache-maven-3.6.3\repos\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.41\tomcat-embed-websocket-9.0.41.jar;D:\apache-maven-3.6.3\repos\org\springframework\spring-web\5.2.12.RELEASE\spring-web-5.2.12.RELEASE.jar;D:\apache-maven-3.6.3\repos\org\springframework\spring-beans\5.2.12.RELEASE\spring-beans-5.2.12.RELEASE.jar;D:\apache-maven-3.6.3\repos\org\springframework\spring-webmvc\5.2.12.RELEASE\spring-webmvc-5.2.12.RELEASE.jar;D:\apache-maven-3.6.3\repos\org\springframework\spring-aop\5.2.12.RELEASE\spring-aop-5.2.12.RELEASE.jar;D:\apache-maven-3.6.3\repos\org\springframework\spring-context\5.2.12.RELEASE\spring-context-5.2.12.RELEASE.jar;D:\apache-maven-3.6.3\repos\org\springframework\spring-expression\5.2.12.RELEASE\spring-expression-5.2.12.RELEASE.jar;D:\apache-maven-3.6.3\repos\org\springframework\boot\spring-boot-starter-log4j2\2.3.8.RELEASE\spring-boot-starter-log4j2-2.3.8.RELEASE.jar;D:\apache-maven-3.6.3\repos\org\apache\logging\log4j\log4j-slf4j-impl\2.13.3\log4j-slf4j-impl-2.13.3.jar;D:\apache-maven-3.6.3\repos\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;D:\apache-maven-3.6.3\repos\org\apache\logging\log4j\log4j-api\2.13.3\log4j-api-2.13.3.jar;D:\apache-maven-3.6.3\repos\org\apache\logging\log4j\log4j-core\2.13.3\log4j-core-2.13.3.jar;D:\apache-maven-3.6.3\repos\org\apache\logging\log4j\log4j-jul\2.13.3\log4j-jul-2.13.3.jar;D:\apache-maven-3.6.3\repos\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;D:\apache-maven-3.6.3\repos\com\lmax\disruptor\3.3.4\disruptor-3.3.4.jar;D:\apache-maven-3.6.3\repos\org\springframework\spring-core\5.2.12.RELEASE\spring-core-5.2.12.RELEASE.jar;D:\apache-maven-3.6.3\repos\org\springframework\spring-jcl\5.2.12.RELEASE\spring-jcl-5.2.12.RELEASE.jar;D:\apache-maven-3.6.3\repos\org\springframework\boot\spring-boot-autoconfigure\2.3.8.RELEASE\spring-boot-autoconfigure-2.3.8.RELEASE.jar;D:\apache-maven-3.6.3\repos\org\springframework\boot\spring-boot\2.3.8.RELEASE\spring-boot-2.3.8.RELEASE.jar;D:\apache-maven-3.6.3\repos\com\huaban\jieba-analysis\1.0.2\jieba-analysis-1.0.2.jar;D:\apache-maven-3.6.3\repos\org\apache\commons\commons-lang3\3.10\commons-lang3-3.10.jar;D:\apache-maven-3.6.3\repos\com\github\whvcse\easy-captcha\1.6.2\easy-captcha-1.6.2.jar;D:\apache-maven-3.6.3\repos\com\jcraft\jsch\0.1.55\jsch-0.1.55.jar;D:\apache-maven-3.6.3\repos\commons-net\commons-net\3.7\commons-net-3.7.jar;D:\IntelliJ IDEA Community Edition 2020.3.1\lib\idea_rt.jar" cn.git.helloApplication
Connected to the target VM, address: '127.0.0.1:61667', transport: 'socket'
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.8.RELEASE)
[2024-09-09T09:41:36.395+08:00] [INFO] [mainraceId] [cn.git.helloApplication] [smallBigPower] [${sys:ip}] [${sys:applicationName}] [StartupInfoLogger.java,55,org.springframework.boot.StartupInfoLogger,logStarting] [Starting helloApplication on smallBigPower with PID 18696 (D:\idea_workspace_activiti_change\docker-hello\target\classes started by Administrator in D:\idea_workspace_activiti_change\docker-hello)] ## ''
[2024-09-09T09:41:36.401+08:00] [INFO] [mainraceId] [cn.git.helloApplication] [smallBigPower] [${sys:ip}] [${sys:applicationName}] [SpringApplication.java,651,org.springframework.boot.SpringApplication,logStartupProfileInfo] [No active profile set, falling back to default profiles: default] ## ''
[2024-09-09T09:41:37.169+08:00] [INFO] [mainraceId] [org.springframework.boot.web.embedded.tomcat.TomcatWebServer] [smallBigPower] [${sys:ip}] [${sys:applicationName}] [TomcatWebServer.java,108,org.springframework.boot.web.embedded.tomcat.TomcatWebServer,initialize] [Tomcat initialized with port(s): 8088 (http)] ## ''
[2024-09-09T09:41:37.179+08:00] [INFO] [mainraceId] [org.apache.coyote.http11.Http11NioProtocol] [smallBigPower] [${sys:ip}] [${sys:applicationName}] [DirectJDKLog.java,173,org.apache.juli.logging.DirectJDKLog,log] [Initializing ProtocolHandler ["http-nio-8088"]] ## ''
[2024-09-09T09:41:37.180+08:00] [INFO] [mainraceId] [org.apache.catalina.core.StandardService] [smallBigPower] [${sys:ip}] [${sys:applicationName}] [DirectJDKLog.java,173,org.apache.juli.logging.DirectJDKLog,log] [Starting service [Tomcat]] ## ''
[2024-09-09T09:41:37.180+08:00] [INFO] [mainraceId] [org.apache.catalina.core.StandardEngine] [smallBigPower] [${sys:ip}] [${sys:applicationName}] [DirectJDKLog.java,173,org.apache.juli.logging.DirectJDKLog,log] [Starting Servlet engine: [Apache Tomcat/9.0.41]] ## ''
[2024-09-09T09:41:37.229+08:00] [INFO] [mainraceId] [org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/]] [smallBigPower] [${sys:ip}] [${sys:applicationName}] [DirectJDKLog.java,173,org.apache.juli.logging.DirectJDKLog,log] [Initializing Spring embedded WebApplicationContext] ## ''
[2024-09-09T09:41:37.229+08:00] [INFO] [mainraceId] [org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext] [smallBigPower] [${sys:ip}] [${sys:applicationName}] [ServletWebServerApplicationContext.java,285,org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext,prepareWebApplicationContext] [Root WebApplicationContext: initialization completed in 796 ms] ## ''
[2024-09-09T09:41:37.263+08:00] [INFO] [mainraceId] [cn.git.init.AnalyzerInit] [smallBigPower] [${sys:ip}] [${sys:applicationName}] [AnalyzerInit.java,44,cn.git.init.AnalyzerInit,analyzerInit] [开始加载分词词典信息,获取自定义词典路径[/D:/idea_workspace_activiti_change/docker-hello/target/classes/dict/custom.dict]] ## ''
main dict load finished, time elapsed 373 ms
user dict D:\idea_workspace_activiti_change\docker-hello\target\classes\dict\custom.dict load finished, tot words:7839, time elapsed:9ms
[2024-09-09T09:41:37.647+08:00] [INFO] [mainraceId] [cn.git.init.AnalyzerInit] [smallBigPower] [${sys:ip}] [${sys:applicationName}] [AnalyzerInit.java,48,cn.git.init.AnalyzerInit,analyzerInit] [加载自定义词典信息完毕] ## ''
[2024-09-09T09:41:37.785+08:00] [INFO] [mainraceId] [cn.git.init.AnalyzerInit] [smallBigPower] [${sys:ip}] [${sys:applicationName}] [AnalyzerInit.java,61,cn.git.init.AnalyzerInit,analyzerInit] [数据库中敏感分词加载完毕!] ## ''
[2024-09-09T09:41:38.093+08:00] [INFO] [mainraceId] [org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor] [smallBigPower] [192.168.220.1] [docker-hello] [ExecutorConfigurationSupport.java,181,org.springframework.scheduling.concurrent.ExecutorConfigurationSupport,initialize] [Initializing ExecutorService 'applicationTaskExecutor'] ## ''
[2024-09-09T09:41:38.770+08:00] [INFO] [mainraceId] [org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler] [smallBigPower] [192.168.220.1] [docker-hello] [ExecutorConfigurationSupport.java,181,org.springframework.scheduling.concurrent.ExecutorConfigurationSupport,initialize] [Initializing ExecutorService 'taskScheduler'] ## ''
[2024-09-09T09:41:38.778+08:00] [INFO] [mainraceId] [org.apache.coyote.http11.Http11NioProtocol] [smallBigPower] [192.168.220.1] [docker-hello] [DirectJDKLog.java,173,org.apache.juli.logging.DirectJDKLog,log] [Starting ProtocolHandler ["http-nio-8088"]] ## ''
[2024-09-09T09:41:38.795+08:00] [INFO] [mainraceId] [org.springframework.boot.web.embedded.tomcat.TomcatWebServer] [smallBigPower] [192.168.220.1] [docker-hello] [TomcatWebServer.java,220,org.springframework.boot.web.embedded.tomcat.TomcatWebServer,start] [Tomcat started on port(s): 8088 (http) with context path ''] ## ''
[2024-09-09T09:41:38.804+08:00] [INFO] [mainraceId] [cn.git.helloApplication] [smallBigPower] [192.168.220.1] [docker-hello] [StartupInfoLogger.java,61,org.springframework.boot.StartupInfoLogger,logStarted] [Started helloApplication in 2.72 seconds (JVM running for 3.189)] ## ''
[2024-09-09T09:41:40.008+08:00] [INFO] [scheduling-1raceId] [cn.git.task.TimerTask] [smallBigPower] [192.168.220.1] [docker-hello] [TimerTask.java,24,cn.git.task.TimerTask,timer] [定时任务执行 : 1725846100008] ## ''
[2024-09-09T09:41:40.009+08:00] [ERROR] [scheduling-1raceId] [org.springframework.scheduling.support.TaskUtils$LoggingErrorHandler] [smallBigPower] [192.168.220.1] [docker-hello] [TaskUtils.java,95,org.springframework.scheduling.support.TaskUtils$LoggingErrorHandler,handleError] [Unexpected error occurred in scheduled task] ## ' java.lang.RuntimeException: 异常啦!
at cn.git.task.TimerTask.timer(TimerTask.java:26)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84)
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:93)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
at java.util.concurrent.FutureTask.run(FutureTask.java)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:748)
'
Disconnected from the target VM, address: '127.0.0.1:61667', transport: 'socket'
Process finished with exit code -1