# logback-demo
**Repository Path**: dazhaxie/logback-demo
## Basic Information
- **Project Name**: logback-demo
- **Description**: springboot整合logback日志以及注意事项
- **Primary Language**: Java
- **License**: GPL-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 2
- **Created**: 2022-09-26
- **Last Updated**: 2022-09-26
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# logback日志配置详解
## springboot加载日志配置文件优先级
基于springboot项目加载logback日志配置文件得出的结论
```markdown
1.最先加载application.properties文件中的logging.config配置文件,如果有相关配置,则停止查找直接初始化当前配置文件
2.先加载初始化类路径下logback原生的配置文件,按照【logback-test.groovy、logback-test.xml、 logback.groovy、logback.xml】顺序查找,如果有相关配置,则停止查找直接进行初始化当前配置文件
3.如果没有logback原生配置文件,则顺序遍历查找spring相关的logback配置文件,按【logback-test-spring.groovy、logback-test-spring.xml、 logback-spring.groovy、logback-spring.xml】顺序查找,如果有相关配置,则停止查找直接进行初始化当前配置文件
4.如果以上都找不到logback相关配置文件,则会创建一个打印到控制台console的日志实例
```
看源码
> 1. 先加载LoggingApplicationListener类initializeSystem()方法,加载配置的logging.config指定的配置文件,如果存在则加载当前日志配置文件。如下:
>
> ```yaml
> logging:
> config: classpath:logback-local.xml
> ```
>
> ```java
> /**
> * The name of the Spring property that contains a reference to the logging
> * configuration to load.
> */
> public static final String CONFIG_PROPERTY = "logging.config";
>
>
> private void initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile) {
> String logConfig = StringUtils.trimWhitespace(environment.getProperty(CONFIG_PROPERTY));
> try {
> LoggingInitializationContext initializationContext = new LoggingInitializationContext(environment);
> if (ignoreLogConfig(logConfig)) {
> system.initialize(initializationContext, null, logFile);
> }
> else {
> // 1.先加载logging.config配置的文件
> system.initialize(initializationContext, logConfig, logFile);
> }
> }
> catch (Exception ex) {
> Throwable exceptionToReport = ex;
> while (exceptionToReport != null && !(exceptionToReport instanceof FileNotFoundException)) {
> exceptionToReport = exceptionToReport.getCause();
> }
> exceptionToReport = (exceptionToReport != null) ? exceptionToReport : ex;
> // NOTE: We can't use the logger here to report the problem
> System.err.println("Logging system failed to initialize using configuration from '" + logConfig + "'");
> exceptionToReport.printStackTrace(System.err);
> throw new IllegalStateException(ex);
> }
> }
>
>
> // LogbackLoggingSystem类initialize()方法
> @Override
> public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
> LoggerContext loggerContext = getLoggerContext();
> if (isAlreadyInitialized(loggerContext)) {
> return;
> }
> super.initialize(initializationContext, configLocation, logFile);
> loggerContext.getTurboFilterList().remove(FILTER);
> markAsInitialized(loggerContext);
> if (StringUtils.hasText(System.getProperty(CONFIGURATION_FILE_PROPERTY))) {
> getLogger(LogbackLoggingSystem.class.getName()).warn("Ignoring '" + CONFIGURATION_FILE_PROPERTY
> + "' system property. Please use 'logging.config' instead.");
> }
> }
>
>
> // AbstractLoggingSystem类initialize方法
> @Override
> public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
> if (StringUtils.hasLength(configLocation)) {
> initializeWithSpecificConfig(initializationContext, configLocation, logFile);
> return;
> }
> initializeWithConventions(initializationContext, logFile);
> }
> ```
>
> // 2.logging.config如果没有配置,则加载初始化类路径下logback的原生配置文件
>
> ```markdown
> logback-test.groovy, logback-test.xml, logback.groovy, logback.xml
> ```
>
> ```java
> // AbstractLoggingSystem类initializeWithConventions方法
> private void initializeWithConventions(LoggingInitializationContext initializationContext, LogFile logFile) {
> // 1.先加载初始化类路径下logback的配置文件
> String config = getSelfInitializationConfig();
> if (config != null && logFile == null) {
> // self initialization has occurred, reinitialize in case of property changes
> reinitialize(initializationContext);
> return;
> }
> // 2.再加载spring相关的logback日志
> if (config == null) {
> config = getSpringInitializationConfig();
> }
> if (config != null) {
> loadConfiguration(initializationContext, config, logFile);
> return;
> }
> loadDefaults(initializationContext, logFile);
> }
>
>
> // AbstractLoggingSystem类getSelfInitializationConfig方法
> protected String getSelfInitializationConfig() {
> return findConfig(getStandardConfigLocations());
> }
>
>
> // AbstractLoggingSystem类findConfig方法
> private String findConfig(String[] locations) {
> for (String location : locations) {
> ClassPathResource resource = new ClassPathResource(location, this.classLoader);
> if (resource.exists()) {
> return "classpath:" + location;
> }
> }
> return null;
> }
>
> // LogbackLoggingSystem类getStandardConfigLocations方法
> @Override
> protected String[] getStandardConfigLocations() {
> return new String[] { "logback-test.groovy", "logback-test.xml", "logback.groovy", "logback.xml" };
> }
> ```
>
> // 3.如果logback原生配置文件也不存在,则加载spring相关的logback日志
>
> ```java
> // LogbackLoggingSystem类getSpringInitializationConfig方法
> protected String getSpringInitializationConfig() {
> return findConfig(getSpringConfigLocations());
> }
>
> // LogbackLoggingSystem类findConfig方法
> private String findConfig(String[] locations) {
> // 遍历如果文件存在,则直接返回
> for (String location : locations) {
> ClassPathResource resource = new ClassPathResource(location, this.classLoader);
> if (resource.exists()) {
> return "classpath:" + location;
> }
> }
> return null;
> }
>
>
> // LogbackLoggingSystem类getSpringConfigLocations方法
> protected String[] getSpringConfigLocations() {
> String[] locations = getStandardConfigLocations();
> for (int i = 0; i < locations.length; i++) {
> String extension = StringUtils.getFilenameExtension(locations[i]);
> locations[i] = locations[i].substring(0, locations[i].length() - extension.length() - 1) + "-spring."
> + extension;
> }
> return locations;
> }
> ```
>
> 4.如果以上都找不到logback相关配置文件,则会创建一个打印到控制台console的日志实例
>
> LogbackLoggingSystem类loadDefaults方法
>
> ```java
> protected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) {
> LoggerContext context = getLoggerContext();
> stopAndReset(context);
> boolean debug = Boolean.getBoolean("logback.debug");
> if (debug) {
> StatusListenerConfigHelper.addOnConsoleListenerInstance(context, new OnConsoleStatusListener());
> }
> Environment environment = initializationContext.getEnvironment();
> // Apply system properties directly in case the same JVM runs multiple apps
> new LoggingSystemProperties(environment, context::putProperty).apply(logFile);
> LogbackConfigurator configurator = debug ? new DebugLogbackConfigurator(context)
> : new LogbackConfigurator(context);
> new DefaultLogbackConfiguration(initializationContext, logFile).apply(configurator);
> context.setPackagingDataEnabled(true);
> }
> ```
参考:[SpringBoot中logback日志配置文件加载顺序&配置记录](https://blog.csdn.net/FromTheWind/article/details/105712549)
## logback详细配置
- 根节点是 configuration,可包含0个或多个 appender
- property用来设置相关变量,通过key-value的方式配置,然后在后面的配置文件中通过 ${key}来访问
```markdown
```
- appender标签
```markdown
appender是一个日志打印的组件,这里组件里面定义了打印过滤的条件、打印输出方式、滚动策略、编码方式、打印格式等等。但是它仅仅是一个打印组件,如果我们不使用一个logger或者root的appender-ref指定某个具体的appender时,它就没有什么意义。
因此appender让我们的应用知道怎么打、打印到哪里、打印成什么样;而logger则是告诉应用哪些可以这么打。例如某个类下的日志可以使用这个appender打印或者某个包下的日志可以这么打印。
有两个必要的属性,name和class
name:用于指定appender的名称
class:用于指定appender全限定名
class主要有三种类型:ConsoleAppender、FileAppender、RollingFileAppender
ConsoleAppender:把日志输到控制台
FileAppender:把日志输到文件
RollingFileAppender:把日志输到文件并且进行定期的清理
```
子节点:
```markdown
标签:表示对参数进行格式化。是0.9.19版本之后引进的,以前的版本使用,logback极力推荐的是使用而不是
标签:筛选出指定级别的日志进行处理
ThresholdFilter会将指定level级别及其以上的都打印出来
LevelFilter可以指定拒绝和接受策略
:滚动记录文件,先将日志记录到指定文件,当符合某个条件时再将日志记录到其他文件
```
- RollingFileAppender子节点
```markdown
TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。有以下子节点:
:
必要节点,包含文件名及“%d”转换符, “%d”可以包含一个 java.text.SimpleDateFormat指定的时间格式,如:%d{yyyy-MM}。如果直接使用 %d,默认格式是 yyyy-MM-dd。 RollingFileAppender 的file字节点可有可无,通过设置file,可以为活动文件和归档文件指定不同位置,当前日志总是记录到file指定的文件(活动文件),活动文件的名字不会改变;如果没设置file,活动文件的名字会根据fileNamePattern 的值,每隔一段时间改变一次。“/”或者“\”会被当做目录分隔符。
:
:可选节点,指定文件保留时间周期,超过周期的就会被删除。
```
- root标签
```markdown
root节点将日志级别大于等于指定的level及其以上的级别的日志交给已经配置好的进行处理。
使用子标签用来指定哪个appender
```
- logger标签
```markdown
用来设置某一个包或者具体某一个类的日志打印级别、以及指定
使用子标签用来指定哪个appender
三个属性:name、level、additivity
name:用来指定受此logger约束的某一个包或者具体的某一个类。
level:用来设置打印级别。如果没有配置level,即继承父级的level。5个常用打印级别从低至高依次为TRACE、DEBUG、INFO、WARN、ERROR。
additivity:是否向上级logger传递打印的信息,默认为true。
```
```markdown
关于logger的获取,一般logger是配置name的。我们再代码中经常通过指定的CLass来获取Logger,比如这样LoggerFactory.getLogger(Test.class);,其实这个最后也是转成对应的包名+类名的字符串com.kongtrio.Test.class。假设有一个logger配置的那么是com.kongtrio,那么通过LoggerFactory.getLogger(Test.class)获取到的logger就是这个logger。
```
logger和root标签关系
```markdown
和标签都是Logger组件。但root是根logger,它只有一个level属性。它们的level只能指定trace、debug、info、warn、error。日志级别从低到高: TRACE < DEBUG < INFO < WARN < ERROR
注意:
- 日志可以有0个或多个logger,但最多一个root
- 如果只指定了root,不使用logger指定当前系统包下的配置,那么整个系统就会按root配置方式打印日志,配置的级别太低会出现日志刷屏
- 使用logger指定当前系统包下或某个类的日志配置,那么整个系统会按logger配置的来打印日志
```
## Mybatis输出sql日志
查看源码:
> 当配置的日志级别是**debug**时才会被ConnectionLogger包裹一层,才会打印日志
```java
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
```
配置日志输出到控制台和日志中的方式
1. 首先配置一个level是debug级别的来记录debug日志的
2. 指定mapper映射接口所在的包level是debug级别
参考:[Java日志框架:logback详解](https://www.cnblogs.com/xrq730/p/8628945.html)
[logback介绍和配置详解](https://www.jianshu.com/p/04065d8cb2a9)
[看完这个不会配置 logback ,请你吃瓜! - 掘金 (juejin.cn)](https://juejin.cn/post/6844903641535479821)
## MDC
作用:
> 在处理请求前将请求的**唯一标识**放到MDC容器中,如traceId,这个**唯一标识**会随着日志一起输出,以此来区分该条日志是属于那个请求的。并在请求处理完成之后清除MDC容器。
在OncePerRequestFilter过滤器中使用。**注:OncePerRequestFilter是spring提供的实现的filter,所有的请求都会经过该过滤器**
```java
/**
* @Description: 先于拦截器进入,后于拦截器结束
* @Author party-abu
* @Date 2022/5/22 10:02
*/
@Component
public class PerRequestFilter extends OncePerRequestFilter {
private static final String TRACE_ID = "traceId";
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) {
try {
MDC.put(TRACE_ID, UUID.randomUUID().toString().replace("-", ""));
filterChain.doFilter(request, response);
} catch (IOException | ServletException e) {
e.printStackTrace();
} finally {
MDC.clear();
}
}
}
```
## 过滤器与拦截器
```markdown
Filter 在请求进入 Servlet 容器,且到达 Servlet 之前对请求做预处理;在 Servlet 处理完请求后对响应做后处理。
Interceptor 在请求进入 Servlet,且到达 Controller 之前对请求做预处理;在 Controller 处理完请求后对 ModelAndView 做后处理,在视图渲染完成后再做一些收尾工作。
当 Filter 和 Interceptor 同时存在时,Filter 对请求的预处理要先于 Interceptor 的 preHandle 方法;Filter 对响应的后处理要后于 Interceptor 的 postHandle 方法和 afterCompletion 方法。
```
[聊一聊过滤器与拦截器](https://juejin.cn/post/7097781804827934733#heading-16)
## demo遇到的问题
```markdown
1. 使用JSONObject时日志请求参数出现转义“/”
JSONObject jsonObject = new JSONObject();
jsonObject.put("requestTime", requestTime + "毫秒");
jsonObject.put("result", returnValue);
// 直接调用toString()会加上转义/
log.info("响应结果:{}", jsonObject.toString());
// 直接调用toString()会加上转义/
log.info("响应结果:{}", jsonObject.toString());
// 需要使用JSON.parseObject()去掉转义/
log.info("响应结果:{}", JSON.parseObject(jsonObject.toString()));
```