这篇博客是我参考了很多资料总结出来的,内容很多,希望能帮助到你^_^
可以先看下目录了解下大体内容:
一、日志框架发展史
二、日志规范
三、日志级别
四、Logback
4.1 Maven引入
4.2 logback-spring.xml是在哪里加载的?
4.3 logback.xml和logback-spring.xml区别
五、logback-spring.xml格式详解
5.1 完整格式
5.2 configuration
5.2.1 定时器是如何生效的?
5.3 contextName
5.4 property和springProperty
5.5 timestamp
5.6.logger和root
5.6.1 说明
5.6.2.appender-ref
5.7 appender
5.7.1 ConsoleAppender
5.7.2 FileAppender
5.7.3 RollingFileAppender
5.7.3.1 TimeBasedRollingPolicy
5.7.3.2 SizeBasedTriggeringPolicy
5.7.3.3 FixedWindowRollingPolicy
5.7.3.4 SizeAndTimeBasedRollingPolicy
5.8 encoder说明
5.8.1 %n
5.8.2 %c %lo %logger
5.8.3 %C %class
5.8.4.%d %date
5.8.5.%caller
5.8.6.%L %line
5.8.7 %m %msg %message
5.8.8.%M %method
5.8.9 %p %le %level
5.8.10 %r %relative
5.8.11.%t %thread
5.8.12 %replace
6.8.13 宽度设置
5.8.14.显示设置
5.8.14.1.%clr和defaults.xml
5.8.14.2.支持的颜色
5.9 filter过滤规则
5.9.1 LevelFilter
5.9.2 ThresholdFilter
5.9.3 EvaluatorFilter
5.9.4 自定义过滤器
六、优化异常堆栈stack日志打印
6.1 未优化前
6.2.优化后
参考博客:
【精选】Java日志框架的发展历史,你不想了解一下吗_java日志历史-CSDN博客 [Java日志框架的发展历史,你不想了解一下吗]
第1阶段:只有System.out 与System.error
第2阶段:Apache大佬Ceki Gülcü 搞了个 Log4j,爆火
第3阶段:Sun眼红Log4j, 自己制定一套标准JUL (Java Util Logging),没啥人用
第4阶段:Apache不服,也自己制定一套标准JCL (Jakarta Commons Logging), 可以在Log4j和JUL之间切换,但存在问题。
第5阶段,大佬Ceki Gülcü离开Apache公司,觉得JCL不好用,自己搞了个Slf4j (Simple Logging Facade For Java 简单日志门面)。但想要Apache和Sun来对接Slf4j太难,于是弄了桥接包,通过桥接包来帮助Slf4j接口与其他日志库建立关系,这种方式称桥接设计模式。
但仍然存在一个问题:日志配置文件未做统一,假如你的系统使用了Slf4j作为日志接口,使用Log4j作为日志产品,则配置文件需要配2份。
Ceki Gülcü发话: 没事,大家都选择用Slf4j统一吧,我来帮大家统一,没有事是桥接包解决不了的,有的话,那就再来个。
第6阶段:Ceki Gülcü 巨佬觉得市场上的日志标准库都是间接实现Slf4j接口,也就是说每次都需要配合桥接包,也就是之前的日志产品都不是正统的Slf4j的实现。
因此在2006年,Ceki Gülcü 基于Slf4j接口写出了Logback日志标准库
第7阶段:Slf4j+Logback的模式,显然很冲击JCL+Log4j,并且本身Logback确实比Log4j性能更优,设计更为合理,所以,老东家Apache可就坐不住了。
在2012年,Apache直接推出新项目,不是Log4j1.x升级,而是新项目Log4j2,因为Log4j2是完全不兼容Log4j1.x的。
并且很微妙的,Log4j2几乎涵盖Logback所有的特性(这不是对着干是啥~而且还有抄袭的嫌疑哈哈哈),更甚者的Log4j2也搞了分离的设计,分化成log4j-api和log4j-core,这个log4j-api也是日志接口,log4j-core才是日志产品。
参考博客
https://www.cnblogs.com/yangyongjie/p/16230247.html [Java日志规范]
1、【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架 (SLF4J)中的 API,使用门面模式的日志框架。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component
public class SpringUtil implements ApplicationContextAware {
private static final Logger log = LoggerFactory.getLogger(SpringUtil.class);
}
或者使用lombok注解:
import lombok.extern.slf4j.Slf4j;
@Component
@Slf4j
public class SpringUtil implements ApplicationContextAware {
}
2、【强制】所有日志文件至少保存15天,因为有些异常具备以“周”为频次发生的特点。日志文件格式为: {logname}.log.{保存日期},日期格式:yyyy-MM-dd,例如info.log.2023-04-21
3、【强制】对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式。
错误示例:(如果日志级别是 warn,该日志不会打印,但是会执行字符串拼接操作,如果 symbol 是对象,会执行 toString()方法,浪费了系统资源,但最终日志却没有打印)
log.debug("Processing trade with id: " + id + " and symbol: " + symbol);
解决方法一:打印日志前加上判断:
if (log.isInfoEnabled()) {
log.info("Processing trade with id: " + id + " and symbol: " + symbol);
}
if (log.isWarnEnabled()) {
log.warn("Processing trade with id: " + id + " and symbol: " + symbol);
}
if (log.isDebugEnabled()) {
log.debug("Processing trade with id: " + id + " and symbol: " + symbol);
}
解决方法二(推荐使用):使用参数化形式{}占位,[] 进行参数隔离。因为 String 字符串的拼接会使用 StringBuilder 的 append()方式,有一定的性能损耗。使用占位符仅是替换动作,可以有效提升性能。
log.debug("Processing trade with id: [{}] and symbol: [{}]", id, symbol);
4、【强制】生产环境禁止直接使用 System.out 或 System.err 输出日志或使用 e.printStackTrace()打印异常堆栈。
5、【强制】生产环境禁止打印debug日志
6、【建议】打印日志不要使用JSON.toJSONString()写法,而是通过重写对象的toString()方法(可以使用lombok的 @ToString(callSuper=true))。
错误示例:
log.info("基础数据拉取sc:{},mqJson:{}", dockDTO.getNeighNo(), JSON.toJSONString(amcBaseInfoPo));
正确示例:
log.info("基础数据拉取sc:{},mqJson:{}", dockDTO.getNeighNo(), amcBaseInfoPo);
参考博客
日志打印的8种级别(很详细)_日志级别-CSDN博客[日志打印的8种级别(很详细)]
log4j定义了8个级别的log,优先级从高到低依次为:
OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL
如果日志设置为L,一个级别为P的输出日志只有当P >= L时日志才会输出。
Log4j建议只使用四个级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG。
参考博客:
logback 日志 介绍和配置详解_logback日志配置-CSDN博客 [logback 日志 介绍和配置详解]
万字详解logback日志框架,再没这么全的了!-腾讯云开发者社区-腾讯云 [万字详解logback日志框架,再没这么全的了!]
logback详解 - 知乎[logback详解]
(三)Logback中的Appender - 程序员大本营[(三)Logback中的Appender]
Java日志框架:logback详解 - 知乎[Java日志框架:logback详解]
logback -- 配置详解 -- 三 --
logback 配置颜色高亮_黑帽子技术的博客-CSDN博客[logback 配置颜色高亮]
LogBack 基本使用_凯凯JAVA的博客-CSDN博客[LogBack 基本使用]
(六)Logback中的Filter_logback filter-CSDN博客[(六)Logback中的Filter]
logback logback.xml常用配置详解(三)
RollingFileAppender详解[RollingFileAppender详解]
logback解析——Appender - 简书[logback解析——Appender]
Spring Boot 默认使用 SLF4J+Logback 记录日志。
1、springBoot已经帮我们引入了logback包,所以我们不需要再去引用。
2、在项目资源文件夹 resources 下 创建 logback-spring.xml 文件。logback 将会自动读取该配置文件。
logback是springboot自带的日志框架.该框架主要有3个模块:
logback-core:核心代码块
logback-classic:实现了slf4j的api,加入该依赖可以实现log4j的api。
logback-access:访问模块与servlet容器集成提供通过http来访问日志的功能(也就是说不需要访问服务器,直接在网页上就可以访问日志文件,实现HTTP访问日志的功能)。
maven已经帮我们集成好了,只需引用:
org.springframework.boot
spring-boot-starter-parent
2.2.5.RELEASE
org.springframework.boot
spring-boot-starter-web
点击进入spring-boot-starter-web,可以看到:
org.springframework.boot
spring-boot-starter
2.2.5.RELEASE
compile
点击进入spring-boot-starter包,可以看到:
org.springframework.boot
spring-boot-starter-logging
2.2.5.RELEASE
compile
点击进入spring-boot-starter-logging,可以看到:
ch.qos.logback
logback-classic
1.2.3
compile
参考博客
springboot 源码阅读之 logback-spring.xml 是在哪里加载的?_logback-spring加载流程-CSDN博客 [springboot 源码阅读之 logback-spring.xml 是在哪里加载的?]
随便在logback-spring.xml写个错误:
启动报错:
Exception in thread "main" java.lang.IllegalStateException: Logback configuration error detected:
ERROR in ch.qos.logback.core.joran.spi.Interpreter@2:8 - no applicable action for [a], current ElementPath is [[configuration][a]]
at org.springframework.boot.logging.logback.LogbackLoggingSystem.loadConfiguration(LogbackLoggingSystem.java:169)
at org.springframework.boot.logging.AbstractLoggingSystem.initializeWithConventions(AbstractLoggingSystem.java:80)
at org.springframework.boot.logging.AbstractLoggingSystem.initialize(AbstractLoggingSystem.java:60)
at org.springframework.boot.logging.logback.LogbackLoggingSystem.initialize(LogbackLoggingSystem.java:118)
at org.springframework.boot.context.logging.LoggingApplicationListener.initializeSystem(LoggingApplicationListener.java:313)
at org.springframework.boot.context.logging.LoggingApplicationListener.initialize(LoggingApplicationListener.java:288)
at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEnvironmentPreparedEvent(LoggingApplicationListener.java:246)
at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEvent(LoggingApplicationListener.java:223)
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:127)
at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:76)
at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:53)
at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:345)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:308)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215)
at com.top.whysu.web.system.SystemWebApplication.main(SystemWebApplication.java:17)
进入LogbackLoggingSystem.java第169行,不断debug发现
1、org.springframework.boot.logging.logback.LogbackLoggingSystem定了4个默认配置文件:
@Override
protected String[] getStandardConfigLocations() {
return new String[] { "logback-test.groovy", "logback-test.xml", "logback.groovy", "logback.xml" };
}
从这里可以看出来读取顺序是:logback-test.groovy, logback-test.xml, logback.groovy, logback.xml
2、org.springframework.boot.logging.AbstractLoggingSystem这边先判断4个默认配置文件是否存在,如果不存的话,则加上-spring后缀再查询。
参考博客
logback.xml和logback-spring.xml的区别_logback-spring和logback.xml-CSDN博客 [logback.xml和logback-spring.xml的区别]
https://www.cnblogs.com/huangdh/p/16778065.html [logback.xml和logback-spring.xml的区别]
【精选】附加:logback日志组件中,logback.xml和logback-spring.xml的区别;(本篇博客并没有得出明确的结论……)_logback.xml和配置文件的区别’-CSDN博客 [附加:logback日志组件中,logback.xml和logback-spring.xml的区别;]
我们使用SLF4J框架记录日志时,会用到 logback.xml 和 logback-spring.xml 两个不同的配置文件。
1、logback-spring.xml 只有在Spring应用程序运行的时候才生效,即带有@SpringBootApplication注解的类启动的时候才会生效。
如果不是Spring应用程序,而是一个main方法或者一个JUnit的测试方法,要用 logback.xml 来配置。
2、存放的位置不同:
logback-spring.xml存放的位置是在SpringApplication主类所在的项目的resources目录,也就是application.yml或者application.properties所在的目录
logback.xml存放的位置是在你启动的那个类所在的项目的resources目录。
3、二者的加载顺序是:logback.xml—>application.properties—>logback-spring.xml。如果你在logback.xml定义了变量,而恰好这个变量又被写在了application.properties中,那么就有可能会获取不到,因为application.properties在logback.xml的后面执行。这也是为什么springboot推荐使用logback-spring.xml来替代logback.xml来配置。
被这张图吓住了?其实很简单的。
另外
property就是定义变量,
contextName就是你这棵树的名称。
logback的日志配置文件格式如下所示:
${glmapper-name}
//xxxx
//xxxx
//xxxx
根节点
scan: 默认值为true。当此属性设置为true时,配置文件如果发生改变,将会被重新加载。
scanPeriod: 设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。如可以设置为scanPeriod="30 seconds"表示每30秒检测一次。
debug: 默认值false。当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。
把debug设置为true
发现启动的时候打印了一句:
16:09:09,385 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - Setting ReconfigureOnChangeTask scanning period to 30 seconds
打开类 ch.qos.logback.classic.joran.action.ConfigurationAction;
发现的确有个定时器:
1、通过property元素可定义变量。它有name和value两个属性。
可以通过“${name}”来使用变量。
如果你是在windows上启动的话,然后你的项目在D盘,则日志会生成在D:\home\smart-community-docking\scd\logs目录下。
2、使用springProperty的话,值是可以从配置文件application.properties里面取值的。
例如现在有application.propertis定义了值:
spring.application.name=smart-community-docking
则可以通过springProperty获取到该值:
${APP_NAME}
用来获取时间戳字符串。这个属性很少使用。
有两个属性key和datePattern。
key: 标识此
datePattern: 设置将当前时间(解析配置文件的时间)转换为字符串的模式,遵循 java.txt.SimpleDateFormat的格式。
logger:
它的name就是ROOT。所以只能配置level属性
level属性的取值范围只能取 TRACE, DEBUG, INFO, WARN, ERROR, ALL, OFF。
level默认是DEBUG。
appender用来定义日志的输出格式,过滤规则,以及日志文件如何生成。
有2个属性name 和class。
name当然是appender的名称啦。
class对应的是实现类。目前有如下三种常用的类:ConsoleAppender,FileAppender,RollingFileAppender。你会发现loback-core.jar包下真的有这3个类。
ConsoleAppender:日志输出到控制台,类名ch.qos.logback.core.FileAppender。
属性名 |
类型 |
备注 |
encoder |
ch.qos.logback.core.encoder.Encoder |
对日志进行格式化。 使用 |
target |
String |
有效值为System.out或者System.err,默认为System.out。一般不写。 |
示例:
注意:禁止向除了ConsoleAppender之外的appender配置彩色日志。
debug
${CONSOLE_LOG_PATTERN}
UTF-8
FileAppender:日志输出到文件,类名ch.qos.logback.core.FileAppender。
属性名 |
类型 |
备注 |
file |
String |
被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值 |
append |
boolean |
如果是 true,日志被追加到文件结尾,如果是 false,清空现存文件,默认是true。 |
encoder |
ch.qos.logback.core.encoder.Encoder |
对日志进行格式化。 使用 |
prudent |
boolean |
如果是 true,日志会被安全的写入文件(即使其他的FileAppender也在向此文件做写入操作),效率低,默认是 false。 |
示例:
%-4relative [%thread] %-5level %logger{35} - %msg%n
${LOG_HOME}/logback.log
true
RollingFileAppender:滚动记录文件,FileAppender的子类。
先将日志文件指定到文件,当符合某个条件时,将日志记录到其他文件。
类名:ch.qos.logback.core.rolling.RollingFileAppender。
属性名 |
类型 |
备注 |
file |
String |
被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值 |
append |
boolean |
如果是 true,日志被追加到文件结尾,如果是 false,清空现存文件,默认是true。 |
rollingPolicy |
ch.qos.logback.core.rolling.RollingPolicy |
当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名。 属性class定义具体的滚动策略类 |
triggeringPolicy |
ch.qos.logback.core.rolling.TriggeringPolicy |
告知 RollingFileAppender 何时激活滚动。 这个要配合具体的rollingPolicy使用,不同的rollingPolicy会有所不同。目前有用到的就只有SizeBasedTriggeringPolicy。 |
encoder |
ch.qos.logback.core.encoder.Encoder |
对日志进行格式化。 使用 |
prudent |
boolean |
当为true时,不支持FixedWindowRollingPolicy。支持TimeBasedRollingPolicy,但是有两个限制,1不支持也不允许文件压缩,2不能设置file属性,必须留空。 |
这里有如下常用的4种滚动策略:
TimeBasedRollingPolicy,SizeBasedTriggeringPolicy,FixedWindowRollingPolicy,SizeAndTimeBasedRollingPolicy
时间滚动策略 ch.qos.logback.core.rolling.TimeBasedRollingPolicy
可以基于时间滚动按时间生成日志
属性名 |
类型 |
备注 |
fileNamePattern |
String |
定义了归档日志文件的名字。 1、它的值由文件名和%d的占位转换符组成,如果没有指定时间和日期格式,默认为yyyy-MM-dd。(由java.text.SimpleDateFormat进行格式化)。 2、轮转周期通过fileNamePattern推断出来的。可以指定多个 %d,但是只能有一个是主要的,用于推断轮转周期,其它的 %d 占位符必须通过 'aux' 标记为辅助的。 例如:/var/log/%d{yyyy/MM,aux}/myapplication.%d{yyyy-MM-dd}.log 3、也可以指定时区:%d{yyyy-MM-dd_HH-mm, UTC},如果指定的时区timezone不能被识别或者拼写错误的话,将会根据TimeZone.getTimeZone(String)方法指定为 GMT。 举例: /foo.%d 默认%d格式是yyyy-MM-dd,按天滚动。 /%d{yyyy/MM}/foo.log 按月滚动 /%d{yyyy-MM-dd_HH}.log 按小时滚动 /%d{yyyy-MM-dd_HH-mm, UTC}.log 按分钟滚动 /%d{yyyy-MM, aux}/%d.log 按天滚动 |
maxHistory |
int |
表示日志文件保存的最大数量。 例如:如果根据fileNamePattern判断出来是按天滚动,该值设为30,则日志文件最多保存30天。 |
totalSizeCap |
int |
来控制所有归档文件总的大小。当达到这个大小后,旧的归档文件将会被异步的删除。 使用这个属性时还需要设置 maxHistory 属性。而且,maxHistory 将会被作为第一条件,该属性作为第二条件。 |
cleanHistoryOnStart |
boolean |
如果设置为 true,那么在 appender 启动的时候,归档文件将会被删除。默认的值为false。 |
下面是一些例子:
文件命名格式 |
滚动计划 |
备注 |
/wombat/foo.%d |
按日滚动 |
%d默认是yyyy-MM-dd格式。 昨天:/wombat/foo.2023-04-22 今天:/wombat/foo.2023-04-23 如果RollingFileAppender设置了 昨天:/wombat/foo.2023-04-22 今天:/wombat/aaaa.txt |
TimeBasedRollingPolicy支持归档日志文件自动压缩,如果fileNamePattern的值以.gz或者.zip结尾则可利用该特性。
文件命名格式 |
滚动计划 |
备注 |
/wombat/foo.%d.gz |
按日滚动,归档日志文件会自动GZIP压缩 |
文件格式如下: 昨天:/wombat/foo.2019-05-05.gz 当天:/wombat/foo.2019-05-06 |
示例:
${LOG_HOME}/test.log
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %5level - %msg%n
${LOG_HOME}/test-%d{yyyy-MM-dd}.log
30
3GB
false
观察当前活动文件的大小,如果已经大于了指定的值,它会给 RollingFileAppender 发一个信号触发对当前活动文件的轮转
ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy
属性名 |
类型 |
备注 |
maxFileSize |
int |
可以为字节,千字节,兆字节,千兆字节,通过在数值后面指定一个后缀 KB,MB 或者 GB。例如,5000000,5000KB,5MB 以及 2GB 都是有效的,前三个是一样的。(默认值是 10 MB) |
官方的示例这个要放在
基于窗口大小的滚动策略。ch.qos.logback.core.rolling.FixedWindowRollingPolicy
这个听起来可能有点难理解,其实说白了就是将归档日志文件到最大了就写到下一个文件里,而窗口大小就是最多允许多少份日志文件。
属性名 |
类型 |
备注 |
fileNamePattern |
String |
必须包含“%i”。 假设最小值maxIndex和最大值maxIndex分别为1和2,命名模式为 mylog%i.log,会产生归档文件mylog1.log和mylog2.log。还可以指定文件压缩选项,例如,mylog%i.log.gz 或者log%i.log.zip |
minIndex |
int |
窗口下限。下限一般都是1啦 |
maxIndex |
int |
窗口上限。一般我们用上限就可以了。 |
示例:(官方示例FixedWindowRollingPolic和SizeBasedTriggeringPolicy一起使用:)
test.log
tests.%i.log.zip
1
3
5MB
%-4relative [%thread] %-5level %logger{35} - %msg%n
前面介绍的TimeBasedRollingPolicy是根据时间生成日志,SizeBasedTriggeringPolicy是根据大小来生成日志,但很尴尬的一点是这2个类冲突,没法同时使用。
故而官方提供了基于时间和文件大小的滚动策略:
ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy
属性名 |
类型 |
备注 |
fileNamePattern |
String |
必选参数。 具体介绍可以看TimeBasedRollingPolicy的介绍。 区别点是必须包含 %i |
maxFileSize |
int |
每个文件最大多少。可以指定KB,MB 或者 GB,默认值是 10 MB。 |
maxHistory |
int |
表示日志文件保存的最大数量。 例如:如果根据fileNamePattern判断出来是按天滚动,该值设为30,则日志文件最多保存30天。 |
totalSizeCap |
int |
可选参数,表示所有归档日志文件的的文件总大小。 假如设置每个日志文件到10mb的时候开始切分,最多保留30天,但最大到20GB,哪怕没到30天但容量达到20G了也要删除多余的日志。 |
cleanHistoryOnStart |
boolean |
可选参数,表示appender应用程序启动时是否应进行日志存档清理,默认为false。 |
示例:(每个文件最多5MB,保存60天的历史记录,但最多20GB。)
${LOG_HOME}/mylog-%d{yyyy-MM-dd}.%i.log
5MB
60
20GB
%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} - %msg%n
UTF-8
encoder表示对参数进行格式化。
1、把日志信息转换为字节数组
2、把字节数组写到输出流
目前使用的是ch.qos.logback.classic.encoder.PatternLayoutEncoder (是唯一有用的且默认的encoder ),它有一个
使用“%”加“转换符”方式。
如果要输出“%”,则必须用“\”对“\%”进行转义。
转换符 |
作用 |
是否避免使用 |
n |
日志换行。 根据使用平台输出\n或\r\n |
否 |
说明:
看到最后一列的标题是"是否避免使用",这是因为这些信息是无法直接拿到的(比如请求行号、调用方法名),logback必须通过一些特殊手段去获取这些数据(比如在日志打印出产生一个堆栈信息),这种操作会比较影响效率,因此除非必要,否则不建议打印这些数据。
转换符 |
作用 |
是否避免使用 |
c{length} lo{length } logger{length} |
输出日志的logger名,有一个形参,功能是缩短logger名,设置为0表示只输入logger最右边点符号之后的字符串。 {length} 限制了总输出长度,如果输出长度不够,尽可能显示类名,压缩包名。 |
否 |
示例:
例如我在com.top.scd.web.system.controller.module.xmga.XmgaController打印了一行日志
package com.toptop.scd.web.system.controller.module.xmga;
@RestController
@RequestMapping("/dockset/sc_xmga")
@Slf4j
public class XmgaController {
@GetMapping("/time")
@ResponseBody
public String time() {
log.warn("aaaaa");
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String timeStr = "厦门公安-当前时间:" + format.format(new Date());
return timeStr;
}
}
logback-spring.xml中配置:
%c-%lo-%logger
打印结果:
com.toptop.scd.web.system.controller.module.xmga.XmgaController-com.toptop.scd.web.system.controller.module.xmga.XmgaController-com.toptop.scd.web.system.controller.module.xmga.XmgaController
可以注意到点号也是有被算进来的
pattern格式 |
打印结果 |
打印长度 |
%logger |
com.toptop.scd.web.system.controller.module.xmga.XmgaController |
63 |
%logger{0} |
XmgaController |
14 |
%logger{1} |
c.l.s.w.s.c.m.x.XmgaController |
30 |
%logger{14} |
c.l.s.w.s.c.m.x.XmgaController |
30 |
%logger{30} |
c.l.s.w.s.c.m.x.XmgaController |
30 |
%logger{32} |
c.l.s.w.s.c.m.x.XmgaController |
30 |
%logger{33} |
c.l.s.w.s.c.m.xmga.XmgaController |
33 |
%logger{38} |
c.l.s.w.s.c.module.xmga.XmgaController |
38 |
%logger{47} |
c.l.s.w.s.controller.module.xmga.XmgaController |
47 |
%logger{52} |
c.l.s.w.system.controller.module.xmga.XmgaController |
52 |
%logger{54} |
c.l.s.web.system.controller.module.xmga.XmgaController |
54 |
%logger{56} |
c.l.scd.web.system.controller.module.xmga.XmgaController |
56 |
%logger{61} |
c.toptop.scd.web.system.controller.module.xmga.XmgaController |
61 |
%logger{63} |
com.toptop.scd.web.system.controller.module.xmga.XmgaController |
63 |
%logger{100} |
com.toptop.scd.web.system.controller.module.xmga.XmgaController |
63 |
转换符 |
作用 |
是否避免使用 |
C{length} class{length } |
输出日志调用所在类。 length与%logger(简写%c或%lo)的用法相同。 尽量避免使用,除非执行速度不造成任何问题。 |
是 (不被推荐使用) |
转换符 |
作用 |
是否避免使用 |
d{pattern} date{pattern} |
输出时间格式,模式语法同 java.text.SimpleDateFormat |
否 |
可以指定日期格式精确到毫秒:%d{yyyy-MM-dd HH:mm:ss.SSS}
转换符 |
作用 |
是否避免使用 |
caller{depth} |
输出日志的调用者的位置信息,整数选项表示输出信息深度。 |
否 |
%caller
打印:
Caller+0 at com.toptop.scd.web.system.controller.module.xmga.XmgaController.time(XmgaController.java:49)
Caller+1 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Caller+2 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
Caller+3 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
Caller+4 at java.lang.reflect.Method.invoke(Method.java:498)
%caller{1}
打印:
Caller+0 at com.toptop.scd.web.system.controller.module.xmga.XmgaController.time(XmgaController.java:49)
转换符 |
作用 |
是否避免使用 |
L line |
输出执行日志请求的行号。 尽量避免使用,除非执行速度不造成任何问题。 |
是 |
%L
转换符 |
作用 |
是否避免使用 |
m msg message |
输出应用程序提供的信息 |
否 |
log.warn("aaaaa");
%msg
打印:
aaaaa
转换符 |
作用 |
是否避免使用 |
M method |
数值执行日志请求的方法名。 尽量避免使用,除非执行速度不造成任何问题。 |
是 |
public String time() {
log.warn("aaaaa");
}
%method
打印:
time
转换符 |
作用 |
是否避免使用 |
p le level |
输出日志级别 |
否 |
log.warn("aaaaa");
%level
打印:
WARN
转换符 |
作用 |
是否避免使用 |
r relative |
输出从程序启动到创建日志记录的时间,单位是毫秒 |
否 |
%r%n
多执行几次,打印:
30675581
30676817
30677965
30679104
30680009
转换符 |
作用 |
是否避免使用 |
t thread |
输出产生日志的线程名 |
否 |
%thread
打印:
http-nio-8998-exec-1
转换符 |
作用 |
是否避免使用 |
replace(p){r,t} |
p为日志内容, r是正则表达式, 将p中符合r的内容替换为t |
否 |
log.warn("aaaaa");
%replace(%msg){"a", "b"}
打印结果:
bbbbb
%20logger:当字符数少于20个字符时,则左侧留空白;
%-20logger:当字符数少于20个字符时,则右侧留空白;
%.30logger:当字符数据大于30个时,则截断;
还可以合起来使用,例如:%-20.20logger %20.20class
{length}可指定长度,如%logger{36}
log.warn("123456789");
%80logger%n%-80logger%n%.20logger
%highligth(日志内容):突出显示
%green(日志内容):字体显示为指定颜色
%clr(日志内容){颜色} 设置颜色
%clr(%msg){faint}%n
这里启动会报错说找不到%clr对应的转换器。
Exception in thread "main" java.lang.IllegalStateException: Logback configuration error detected:
ERROR in ch.qos.logback.core.pattern.parser.Compiler@79145d5a - There is no conversion class registered for composite conversion word [clr]
ERROR in ch.qos.logback.core.pattern.parser.Compiler@79145d5a - Failed to create converter for [%clr] keyword
解决方法一:
引入springboot的默认日志配置:
%clr(%msg){faint}%n
解决方法二:或者手动引入需要的配置:
%clr(%msg){faint}%n
这个文件是怎么冒出来的呢?查看前面的那个default.xml文件就可以看到啦!
%black 黑色
%red 红色
%green 绿色
%yellow 黄色
%blue 蓝色
%magenta 洋红色
%cyan 青色
%white 白色
%gray 灰色
%faint 灰白色
以下为对应加粗的颜色代码
%boldRed
%boldGreen
%boldYellow
%boldBlue
%boldMagenta
%boldCyan
%boldWhite
%highlight 高亮色
例如ch.qos.logback.classic.filter.LevelFilter,表示根据日志级别进行过滤。
举例:
INFO
ACCEPT
DENY
%-4relative [%thread] %-5level %logger{30} - %msg%n
public String time() {
log.trace("====trace====");
log.debug("====debug====");
log.info("====info====");
log.warn("====warn====");
log.error("====error====");
}
打印结果:
186556 [http-nio-8998-exec-6] INFO c.l.s.w.s.c.m.x.XmgaController - ====info====
临界值过滤器,过滤掉低于指定临界值的日志。
对应的类是ch.qos.logback.classic.filter.ThresholdFilter。
当日志级别等于或高于临界值时,过滤器返回NEUTRAL;当日志级别低于临界值时,日志会被拒绝。
例如:过滤掉所有低于INFO级别的日志:
INFO
%-4relative [%thread] %-5level %logger{30} - %msg%n
public String time() {
log.trace("====trace====");
log.debug("====debug====");
log.info("====info====");
log.warn("====warn====");
log.error("====error====");
}
打印结果:
393494 [http-nio-8998-exec-9] INFO c.l.s.w.s.c.m.x.XmgaController - ====info====
393494 [http-nio-8998-exec-9] WARN c.l.s.w.s.c.m.x.XmgaController - ====warn====
393494 [http-nio-8998-exec-9] ERROR c.l.s.w.s.c.m.x.XmgaController - ====error====
求值过滤器,评估、鉴别日志是否符合指定条件。
对应类是ch.qos.logback.core.filter.EvaluatorFilter。
需要额外的两个JAR包,commons-compiler.jar和janino.jar。
有以下子节点:
,也是默认的鉴别器。它以任意的java布尔值表达式作为求值条件,求值条件在配置文件解释过成功被动态编译,布尔值表达式返回true就表示符合过滤条件。
求值表达式作用于当前日志,logback向求值表达式暴露日志的各种字段:
Name |
Type |
Description |
event |
LoggingEvent |
与记录请求相关联的原始记录事件,下面所有变量都来自event,例如,event.getMessage()返回下面"message"相同的字符串 |
message |
String |
日志的原始消息,例如,设有logger mylogger,"name"的值是"AUB",对于 mylogger.info("Hello {}",name); "Hello {}"就是原始消息。 |
formatedMessage |
String |
日志被各式话的消息,例如,设有logger mylogger,"name"的值是"AUB",对于 mylogger.info("Hello {}",name); "Hello Aub"就是格式化后的消息。 |
logger |
String |
logger 名。 |
loggerContext |
LoggerContextVO |
日志所属的logger上下文。 |
level |
int |
级别对应的整数值,所以 level > INFO 是正确的表达式。 |
timeStamp |
long |
创建日志的时间戳。 |
marker |
Marker |
与日志请求相关联的Marker对象,注意“Marker”有可能为null,所以你要确保它不能是null。 |
mdc |
Map |
包含创建日志期间的MDC所有值得map。访问方法是: mdc.get("myKey") 。mdc.get()返回的是Object不是String,要想调用String的方法就要强转,例如, ((String) mdc.get("k")).contains("val") .MDC可能为null,调用时注意。 |
throwable |
java.lang.Throwable |
如果没有异常与日志关联"throwable" 变量为 null. 不幸的是, "throwable" 不能被序列化。在远程系统上永远为null,对于与位置无关的表达式请使用下面的变量throwableProxy |
throwableProxy |
IThrowableProxy |
与日志事件关联的异常代理。如果没有异常与日志事件关联,则变量"throwableProxy" 为 null. 当异常被关联到日志事件时,"throwableProxy" 在远程系统上不会为null |
EvaluatorFilter支持使用 java 代码来作为过滤标准。但需要导入额外的包:
org.codehaus.janino
janino
3.1.0
return message.contains("info");
ACCEPT
DENY
%-4relative [%thread] %-5level %logger - %msg%n
public String time() {
log.trace("====trace====");
log.debug("====debug====");
log.info("====info====");
log.warn("====warn====");
log.error("====error====");
}
打印:
21809 [http-nio-8998-exec-1] INFO com.toptop.scd.web.system.controller.module.xmga.XmgaController - ====info====
自定义类:
package com.toptop.scd.base.common.filter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;
public class SampleFilter extends Filter {
@Override
public FilterReply decide(ILoggingEvent event) {
if (event.getMessage().contains("info")) {
return FilterReply.ACCEPT;
} else {
return FilterReply.DENY;
}
}
}
日志配置文件指定过滤器
%-4relative [%thread] %-5level %logger - %msg%n
打印结果:
186556 [http-nio-8998-exec-6] INFO c.l.s.w.s.c.m.x.XmgaController - ====info====
try {
int i = 1/0;
} catch (Exception e) {
log.error("发生异常", e);
}
打印结果:(打印了很多不需要的堆栈信息)
打印结果:
2023-04-25 10:58:03.757 ERROR 36556 --- [nio-8998-exec-1] c.l.s.w.s.c.module.xmga.XmgaController : 发生异常
java.lang.ArithmeticException: / by zero
at com.toptop.scd.web.system.controller.module.xmga.XmgaController.time(XmgaController.java:50)
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.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:123)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:367)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1639)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
1、引入logstash-logback-encoder
net.logstash.logback
logstash-logback-encoder
7.1.1
注意jdk版本依赖:
2、引入
3、通过%stack{}进行配置
stack{100,16,2048,rootFirst,regex1,regex2,evaluatorName}
参数解释:
建议查看源码 net.logstash.logback.stacktrace.ShortenedThrowableConverter#parseOptions()
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %msg%n%stack{100,full,2048,rootFirst,inlineHash,${STE_EXCLUSIONS},}
UTF-8
优化后打印的日志,部分堆栈信息被隐藏:
<#6242a0ba> java.lang.ArithmeticException: / by zero
at com.toptop.scd.web.system.controller.module.xmga.XmgaController.time(XmgaController.java:50)
at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java)
... 3 frames excluded
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
... 5 frames excluded
at com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:123)
... 27 frames excluded
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
... 2 frames excluded