# 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 方法。 ``` image-20220522174555016 [聊一聊过滤器与拦截器](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())); ```