为什么使用 Logback
记得前几年工作的时候,公司使用的日志框架还是 log4j,大约从16年中到现在,日志框架基本都换成了logback,总结一下,logback 大约有以下的一些优点:
内核重写、测试充分、初始化内存加载更小,这一切让 logback 性能和 log4j 相比有诸多倍的提升。
logback 非常自然地直接实现了 slf4j,这个严格来说算不上优点,只是这样,再理解 slf4j 的前提下会很容易理解 logback,也同时很容易用其他日志框架替换 logback。
logback 有比较齐全的200多页的文档。
logback 当配置文件修改了,支持自动重新加载配置文件,扫描过程快且安全,它并不需要另外创建一个扫描线程。
支持自动去除旧的日志文件,可以控制已经产生日志文件的最大数量。
总而言之,如果大家的项目里面需要选择一个日志框架,那么我个人非常建议使用 logback。
Logback 加载
我们简单分析一下 logback 加载过程,当我们使用 logback-classic.jar 时,应用启动,那么 logback 会按照如下顺序进行扫描:
logback 会在类路径下寻找名为 logback-test.xml 的文件。
如果没有找到,logback 会继续寻找名为 logback.groovy 的文件。
如果没有找到,logback 会继续寻找名为 logback.xml 的文件。
如果没有找到,将会通过 JDK 提供的 ServiceLoader 工具在类路径下寻找文件 META-INFO/services/ch.qos.logback.classic.spi.Configurator
,该文件的内容为实现了 Configurator
接口的实现类的全限定类名。
如果以上都没有成功,logback 会通过 BasicConfigurator 为自己进行配置,并且日志将会全部在控制台打印出来。
最后一步的目的是为了保证在所有的配置文件都没有被找到的情况下,提供一个默认的(但是是非常基础的)配置(默认日志输出格式为 %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
)。如果你使用的是 maven,你可以在 src/test/resources 下新建 logback-test.xml。maven 会确保它不会被生成。所以你可以在测试环境中给配置文件命名为 logback-test.xml,在生产环境中命名为 logback.xml。更多内容可查看 [Logback] 3 Logback 的配置 。
案例
SpringBoot Logging 配置
在网上搜了些 Logging 配置,发现千篇一律。基本上没讲述全的,在这推荐大家直接看 spring boot 官方文档:Spring Boot docs – Boot Features Logging
application.properties
基础配置
1 2 3 4 5 6 7 8 9 logging.file.name =log/my.log logging.level.root =info logging.level.org.springframework.web =debug
日志级别的排序为:TRACE < DEBUG < INFO < WARN < ERROR。
Logback 配置
pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <dependency > <groupId > ch.qos.logback</groupId > <artifactId > logback-access</artifactId > <version > 1.2.3</version > </dependency > <dependency > <groupId > ch.qos.logback</groupId > <artifactId > logback-classic</artifactId > <version > 1.2.3</version > </dependency > <dependency > <groupId > ch.qos.logback</groupId > <artifactId > logback-core</artifactId > <version > 1.2.3</version > </dependency >
application.properties
1 2 logging.config =classpath:config/logback-spring.xml
logback-spring.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 <?xml version="1.0" encoding="UTF-8"?> <configuration scan ="true" scanPeriod ="60 seconds" packagingData ="true" > <property name ="log.path" value ="log" /> <appender name ="CONSOLE" class ="ch.qos.logback.core.ConsoleAppender" > <encoder > <pattern > %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern > <charset > UTF-8</charset > </encoder > </appender > <timestamp key ="bySecond" datePattern ="yyyy_MM_dd" /> <appender name ="DEBUG_FILE" class ="ch.qos.logback.core.rolling.RollingFileAppender" > <file > ${log.path}/debug_log_${bySecond}.log</file > 1.设置这个属性为 true 时,立即刷新输出流可以确保日志事件被立即写入,并且可以保证一旦你的应用没有正确关闭 appender,日志事件也不会丢失。 2.设置这个属性为 false 时,有可能会使日志的吞吐量翻两番(视情况而定)。但是,设置为 false,当应用退出的时候没有正确关闭 appender,会导致日志事件没有被写入磁盘,可能会丢失。--> <immediateFlush > false</immediateFlush > <encoder > <pattern > %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern > <charset > UTF-8</charset > </encoder > <rollingPolicy class ="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy" > <fileNamePattern > ${log.path}/debug/debug-log.%d{yyyy-MM-dd}.%i.log</fileNamePattern > <maxFileSize > 100MB</maxFileSize > <maxHistory > 15</maxHistory > <totalSizeCap > 10GB</totalSizeCap > </rollingPolicy > <filter class ="ch.qos.logback.classic.filter.LevelFilter" > <level > debug</level > <onMatch > ACCEPT</onMatch > <onMismatch > DENY</onMismatch > </filter > </appender > <appender name ="INFO_FILE" class ="ch.qos.logback.core.rolling.RollingFileAppender" > <file > ${log.path}/info_log_${bySecond}.log</file > <encoder > <pattern > %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern > <charset > UTF-8</charset > </encoder > <rollingPolicy class ="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy" > <fileNamePattern > ${log.path}/info/info-log.%d{yyyy-MM-dd}.%i.log</fileNamePattern > <maxFileSize > 100MB</maxFileSize > <maxHistory > 15</maxHistory > <totalSizeCap > 10GB</totalSizeCap > </rollingPolicy > <filter class ="ch.qos.logback.classic.filter.LevelFilter" > <level > info</level > <onMatch > ACCEPT</onMatch > <onMismatch > DENY</onMismatch > </filter > </appender > <appender name ="WARN_FILE" class ="ch.qos.logback.core.rolling.RollingFileAppender" > <file > ${log.path}/warn_log_${bySecond}.log</file > <encoder > <pattern > %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern > <charset > UTF-8</charset > </encoder > <rollingPolicy class ="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy" > <fileNamePattern > ${log.path}/warn/warn-log.%d{yyyy-MM-dd}.%i.log</fileNamePattern > <maxFileSize > 100MB</maxFileSize > <maxHistory > 15</maxHistory > <totalSizeCap > 10GB</totalSizeCap > </rollingPolicy > <filter class ="ch.qos.logback.classic.filter.LevelFilter" > <level > warn</level > <onMatch > ACCEPT</onMatch > <onMismatch > DENY</onMismatch > </filter > </appender > <appender name ="ERROR_FILE" class ="ch.qos.logback.core.rolling.RollingFileAppender" > <file > ${log.path}/error_log_${bySecond}.log</file > <encoder > <pattern > %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern > <charset > UTF-8</charset > </encoder > <rollingPolicy class ="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy" > <fileNamePattern > ${log.path}/error/error-log.%d{yyyy-MM-dd}.%i.log</fileNamePattern > <maxFileSize > 100MB</maxFileSize > <maxHistory > 15</maxHistory > <totalSizeCap > 10GB</totalSizeCap > </rollingPolicy > <filter class ="ch.qos.logback.classic.filter.LevelFilter" > <level > error</level > <onMatch > ACCEPT</onMatch > <onMismatch > DENY</onMismatch > </filter > </appender > <appender name ="ASYNC" class ="ch.qos.logback.classic.AsyncAppender" > <queueSize > 256</queueSize > <discardingThreshold > 0</discardingThreshold > <appender-ref ref ="INFO_FILE" /> </appender > <springProfile name ="dev" > 所以其子类 com.example.logback.LogbackApplicationTests 所依赖的有效 logger 日志级别也是 debug; 而不是 appender,name="CONSOLE" 所依附的 logger(也就是 root)的级别 info; 总结就是 com.example.logback.LogbackApplicationTests 类的有效 logger 日志级别是 debug。--> <logger name ="com.example.logback" level ="debug" /> <root level ="info" > <appender-ref ref ="CONSOLE" /> <appender-ref ref ="DEBUG_FILE" /> <appender-ref ref ="INFO_FILE" /> <appender-ref ref ="WARN_FILE" /> <appender-ref ref ="ERROR_FILE" /> </root > </springProfile > <springProfile name ="test" > 1.1 当前 logger 的日志级别是 debug,会拿到大于等于 debug 的全部日志,同时该包的 appender name="DEBUG_FILE" 只匹配 debug 级别的日志,所以会写入 log_debug.log 文件中; 1.2 如果当前 logger 没用设置 level="debug" 该属性,那么会寻找父级的日志级别,而当前包的父级为 root,root 的日志级别为 info,所以只能拿到大于等于 info 的全部日志, 但是该包的 appender name="DEBUG_FILE" 只匹配 debug 级别的日志,所以没用任何的日志会写入 log_debug.log 文件中。 2.additivity 属性:为叠加输出(默认为 true): 2.1 当 additivity="true" 时,因为当前 logger 的日志级别是 debug,所以该包下的日志会全部向父级传递,并会全部打印在控制台; 2.2 当 additivity="false" 时,该包下的全部日志不会向父级传递,控制台不会打印任何日志。--> <logger name ="com.example.logback" level ="debug" additivity ="false" > <appender-ref ref ="DEBUG_FILE" /> </logger > <root level ="info" > <appender-ref ref ="CONSOLE" /> </root > </springProfile > <springProfile name ="prod" > <logger name ="com.example.logback" level ="debug" /> <root level ="info" > <appender-ref ref ="CONSOLE" /> <appender-ref ref ="DEBUG_FILE" /> <appender-ref ref ="INFO_FILE" /> <appender-ref ref ="WARN_FILE" /> <appender-ref ref ="ERROR_FILE" /> </root > </springProfile > </configuration >
注:Spring Profile
的用法可参靠 [Spring Profile] How to Use Spring Profile
测试类
Foo.class
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.example.logback.testlogback;import lombok.extern.slf4j.Slf4j; * @author vincent */ @Slf4j public class Foo { public void doIt () throws NoSuchMethodException { log.debug("Did it again!" ); } }
LogbackApplicationTests.class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.example.logback;import com.example.logback.testlogback.Foo;import lombok.extern.slf4j.Slf4j;import org.junit.jupiter.api.Test;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.ActiveProfiles;@Slf4j @SpringBootTest @ActiveProfiles("dev") class LogbackApplicationTests { @Test void contextLoads () { } @Test public void logTest () { log.debug("logback debug..." ); log.info("logback info.." ); log.warn("logback warn.." ); log.error("logback error.." ); } @Test public void fooTest () throws NoSuchMethodException { log.info("Entering application..." ); Foo foo = new Foo(); foo.doIt(); log.info("Exiting application..." ); } }
案例源码:https://github.com/V-Vincen/logback
If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !