使用
Logback 的 Maven 依赖:
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version></dependency>
其依赖关系如下:
其中 logback-core 是 Logback 的核心包,而 logback-classic 是 Slf4j 的实现。我们写一个示例:
public class LogbackTest {private static final Logger LOGGER = LoggerFactory.getLogger(LogbackTest.class);public static void main(String[] args) {LOGGER.debug("Hello Logback!");// 打印内部的状态LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();StatusPrinter.print(lc);}}
在没有配置文件的情况下,Logback 的日志信息默认会输出到控制台。我们还可以通过 StatusPrinter 来打印 Logback 的内部执行信息:
可以看到,Logback 没有在 classpath 路径下找到 logback-test.xml、logback.groovy、logback.xml 文件,因此采用默认的配置,即输出到控制台。下面我们来看看 Logback 的配置文件如何设置:
配置文件
Logback 的配置文件非常灵活,最基本的结构为
1. appender
appender 就是配置日志的输出目的地,通过 name 属性指定名字,通过 class 属性指定目的地:
- ch.qos.logback.core.ConsoleAppender:输出到控制台
- ch.qos.logback.core.FileAppender:输出到文件
- ch.qos.logback.core.rolling.RollingFileAppender:文件大小超过阈值时产生一个新文件
- ch.qos.logback.classic.AsyncAppender:异步输出
除了输出到本地,还可以通过 SocketAppender 和 SSLSocketAppender 输出到远程设备,通过 SMTPAppender 输出到邮件,甚至可以通过 DBAppender 通过 JDBC 输出到数据库中。
此外还有两个通用属性为:encoder 负责把日志信息转换成字节数组,并且把字节数组写到输出流。pattern 用来指定日志的输出格式,具体配置参见官网。
2. root
level 表示日志级别,值可以为:TRACE、DEBUG、INFO、WARN、ERROR、ALL、OFF。
appender-ref 用来指定具体的 appender。
3. 自动重载配置
之前提到 Logback 很强的一个功能就是支持自动重载配置,那想要启用这个功能也非常简单,只需要在 configuration 元素上添加 scan=true 即可。启用后,Logback 会起一个 ReconfigureOnChangeTask 的定时任务来监视配置文件的变化。
默认情况下,扫描的时间间隔是一分钟一次。如果想要调整时间间隔,可以通过 scanPeriod 属性进行调整,单位可以是毫秒(milliseconds)、秒(seconds)、分钟(minutes)或者小时(hours)。下面这个示例指定的时间间隔是 30 秒:
<configuration scan="true" scanPeriod="30 seconds">...</configuration>
注意:如果指定了时间间隔,没有指定时间单位,默认的时间单位为毫秒。
4. 异步输出
使用 Logback 提供的 AsyncAppender 即可实现异步的日志记录。AsyncAppende 类似装饰模式,也就是在不改变类原有基本功能的情况下为其增添新功能。这样,我们就可以把 AsyncAppender 附加在其他的 Appender 上,将其变为异步的。
定义一个异步 Appender ASYNCFILE,包装之前的同步文件日志记录的 FileAppender,就可以实现异步记录日志到文件:
<appender name="ASYNCFILE" class="ch.qos.logback.classic.AsyncAppender"><appender-ref ref="FILE"/></appender>
但在使用 AsyncAppender 我们要注意它的一些参数配置,下面通过源码进行分析:
public class AsyncAppender extends AsyncAppenderBase<ILoggingEvent> {boolean includeCallerData = false;//是否收集调用方数据protected boolean isDiscardable(ILoggingEvent event) {Level level = event.getLevel();return level.toInt() <= Level.INFO_INT;//丢弃<=INFO级别的日志}protected void preprocess(ILoggingEvent eventObject) {eventObject.prepareForDeferredProcessing();if (includeCallerData)eventObject.getCallerData();}}public class AsyncAppenderBase<E> extends UnsynchronizedAppenderBase<E> implements AppenderAttachable<E> {BlockingQueue<E> blockingQueue;//异步日志的关键,阻塞队列public static final int DEFAULT_QUEUE_SIZE = 256;//默认队列大小int queueSize = DEFAULT_QUEUE_SIZE;static final int UNDEFINED = -1;int discardingThreshold = UNDEFINED;boolean neverBlock = false;//控制队列满的时候加入数据时是否直接丢弃,不会阻塞等待@Overridepublic void start() {...blockingQueue = new ArrayBlockingQueue<E>(queueSize);if (discardingThreshold == UNDEFINED)discardingThreshold = queueSize / 5;//默认丢弃阈值是队列剩余量低于队列长度的20%,参见isQueueBelowDiscardingThreshold方法...}@Overrideprotected void append(E eventObject) {if (isQueueBelowDiscardingThreshold() && isDiscardable(eventObject)) { //判断是否可以丢数据return;}preprocess(eventObject);put(eventObject);}private boolean isQueueBelowDiscardingThreshold() {return (blockingQueue.remainingCapacity() < discardingThreshold);}private void put(E eventObject) {if (neverBlock) { //根据neverBlock决定使用不阻塞的offer还是阻塞的put方法blockingQueue.offer(eventObject);} else {putUninterruptibly(eventObject);}}//以阻塞方式添加数据到队列private void putUninterruptibly(E eventObject) {boolean interrupted = false;try {while (true) {try {blockingQueue.put(eventObject);break;} catch (InterruptedException e) {interrupted = true;}}} finally {if (interrupted) {Thread.currentThread().interrupt();}}}}
includeCallerData 用于控制是否收集调用方数据,默认是 false,此时方法行号、方法名等信息将不能显示(源码第 2 行以及 9 到 10 行)。
queueSize 用于控制阻塞队列大小,使用的 ArrayBlockingQueue 阻塞队列(源码第 26 行),默认大小是 256(源码第 17 行),即内存中最多保存 256 条日志。
discardingThreshold 是控制丢弃日志的阈值,主要是防止队列满后一直阻塞。默认情况下,队列剩余量低于队列长度的 20%,就会丢弃 TRACE、DEBUG 和 INFO 级别的日志。(参见源码第 3 到 6 行、19 到 20 行、27 到 28 行、34 到 36 行)
neverBlock 用于控制队列满的时候,加入的数据是否直接丢弃,不会阻塞等待,默认是 false(源码第 46 行)。这里需要注意一下 offer 方法和 put 方法的区别,当队列满的时候 offer 方法不阻塞,而 put 方法会阻塞;neverBlock 为 true 时,使用 offer 方法。
可以看出 queueSize、discardingThreshold 和 neverBlock 这三个参数息息相关,使用时务必按需进行设置和取舍,到底是性能为先,还是数据不丢为先。
如果考虑绝对性能为先,那就设置 neverBlock 为 true,永不阻塞。如果考虑绝对不丢数据为先,那就设置 discardingThreshold 为 0,即使是 <= INFO 的级别日志也不会丢,但最好把 queueSize 设置大一点,毕竟默认的 queueSize 太小,太容易阻塞。如果希望兼顾两者,可以丢弃不重要的日志,把 queueSize 设置大一点,再设置一个合理的 discardingThreshold。
5. 配置总览
<?xml version="1.0" encoding="UTF-8"?><!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 --><configuration scan="true" scanPeriod="60 seconds" debug="false"><!-- 运行环境,dev:开发,test:测试,pre:预生产,pro:生产 --><property name="system_host" value="dev" /><property file="system.properties" /><!-- 上下文变量设置,用来定义变量值,其中name的值是变量的名称,value的值时变量定义的值。 通过<property>定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 --><property name="CONTEXT_NAME" value="logback-test" /><!-- 日志文件存放路径设置,绝对路径 --><property name="logs.dir" value="/opt/logs" /><!-- 日志文件存放路径设置,tomcat路径 --><property name="logs.dir" value="${catalina.base}/logs" /><!-- 定义日志文件 相对输入位置 --><property name="log_dir" value="log" /><!-- 日志输出格式设置 --><!--%d{yyyy-MM-dd HH:mm:ss} [%level] - %msg%nLogger: %loggerClass: %classFile: %fileCaller: %callerLine: %lineMessage: %mMethod: %MRelative: %relativeThread: %threadException: %exxException: %xExnopException: %nopexrException: %rExMarker: %markernewline:%n--><property name="CUSTOM_LOG_PATTERN"value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{90} - %msg%n" /><!-- 上下文名称:<contextName>, 每个logger都关联到logger上下文, 默认上下文名称为“default”。但可以使用<contextName>设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改。 --><contextName>${CONTEXT_NAME}</contextName><!-- <appender>是<configuration>的子节点,是负责写日志的组件。 有两个必要属性name和class。name指定appender名称,class指定appender的实现类。 --><appender name="console" class="ch.qos.logback.core.ConsoleAppender"><!-- 对日志进行格式化。 --><encoder><pattern>${CUSTOM_LOG_PATTERN}</pattern><charset>UTF-8</charset></encoder></appender><appender name="file"class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 按天来回滚,如果需要按小时来回滚,则设置为{yyyy-MM-dd_HH} --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>log/testC.%d{yyyy-MM-dd}.%i.log</fileNamePattern><!-- 如果按天来回滚,则最大保存时间为30天,30天之前的都将被清理掉 --><maxHistory>30</maxHistory><!-- 按时间回滚的同时,按文件大小来回滚 --><timeBasedFileNamingAndTriggeringPolicyclass="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy></rollingPolicy><!-- 也可以使用下面这个策略--><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><!--日志文件保留天数--><MaxHistory>30</MaxHistory><!--日志文件最大的大小--><MaxFileSize>100MB</MaxFileSize><!--日志整体最大可选的totalSizeCap属性控制所有归档文件的总大小。当超过总大小上限时,将异步删除最旧的存档。totalSizeCap属性也需要设置maxHistory属性。此外,“最大历史”限制总是首先应用,“总大小上限”限制其次应用。--><totalSizeCap>10GB</totalSizeCap></rollingPolicy><!-- 过滤器,只记录WARN级别的日志 --><!-- 果日志级别等于配置级别,过滤器会根据onMath 和 onMismatch接收或拒绝日志。 --><filter class="ch.qos.logback.classic.filter.LevelFilter"><!-- 设置过滤级别 --><level>WARN</level><!-- 用于配置符合过滤条件的操作 --><onMatch>ACCEPT</onMatch><!-- 用于配置不符合过滤条件的操作 --><onMismatch>DENY</onMismatch></filter><!-- 日志输出格式 --><encoder><pattern>${CUSTOM_LOG_PATTERN}</pattern><charset>UTF-8</charset></encoder></appender><appender name="log_file"class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值。 --><file>${logs.dir}/logback-test.log</file><!-- 按照固定窗口模式生成日志文件,当文件大于20MB时,生成新的日志文件。窗口大小是1到3,当保存了3个归档文件后,将覆盖最早的日志 --><rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"><!-- 必须包含“%i”例如,假设最小值和最大值分别为1和2,命名模式为 mylog%i.log,会产生归档文件mylog1.log和mylog2.log。还可以指定文件压缩选项,例如,mylog%i.log.gz或者 没有log%i.log.zip --><FileNamePattern>${logs.dir}/logback-test.%i.log</FileNamePattern><!-- 窗口索引最小值 --><minIndex>1</minIndex><!-- 窗口索引最大值 --><maxIndex>3</maxIndex></rollingPolicy><!-- 日志级别过滤器(自定义计算条件) --><filter class="ch.qos.logback.core.filter.EvaluatorFilter"><evaluator class="ch.qos.logback.classic.boolex.GEventEvaluator"><expression>e.level.toInt() == WARN.toInt() || e.level.toInt() == INFO.toInt()</expression></evaluator><OnMismatch>DENY</OnMismatch><OnMatch>NEUTRAL</OnMatch></filter><!-- 激活滚动的条件。 --><triggeringPolicyclass="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"><!-- 活动文件的大小,默认值是10MB --><maxFileSize>30MB</maxFileSize></triggeringPolicy><!-- 对记录事件进行格式化。 --><encoder><pattern>${CUSTOM_LOG_PATTERN}</pattern><charset>UTF-8</charset></encoder></appender><!-- 异步输出 --><appender name="ASYNC_logback" class="ch.qos.logback.classic.AsyncAppender"><!-- 控制丢弃日志的阈值,主要是防止队列满后阻塞。默认如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 --><!-- <discardingThreshold>0</discardingThreshold> --><!-- 更改默认的阻塞队列的深度,该值会影响性能。默认值为256,即内存中最多保存256条日志 --><!-- <queueSize>256</queueSize> --><!-- 添加附加的appender,最多只能添加一个 --><appender-ref ref="log_file" /></appender><!-- 指定包输出路径 --><!-- 用来设置某一个 包 或者具体的某一个 类 的日志打印级别、以及指定<appender>, name:用来指定受此logger约束的某一个包或者具体的某一个类。level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。如果未设置此属性,那么当前loger将会继承上级的级别。additivity:是否向上级logger传递打印信息。默认是true。(这个logger的上级就是上面的root) <logger>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个logger。 --><logger name="org.logback.test" level="DEBUG" additivity="true"><appender-ref ref="stdout" /></logger><!-- 特殊的<logger>元素,是根logger。只有一个level属性,应为已经被命名为"root". level:设置打印级别,大小写无关:TRACE,DEBUG, INFO, WARN, ERROR, ALL 和 OFF,不能设置为INHERITED或者同义词NULL。默认是DEBUG。 <root>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个loger。 --><root><level value="WARN" /><!-- if表达式,需要Janino jar --><!-- Janino 2.6.0版本开始,除了janino.jar之外, commons-compiler.jar也需要在类路径中 --><if condition='property("system_host").contains("dev")'><then><appender-ref ref="stdout" /></then></if><appender-ref ref="file" /></root></configuration>
参考资料
Logback 的官网上是有一份手册的,非常详细,足足 200 多页,只不过是英文版的。如果英文阅读能力有限的话,可以到 GitHub 上查看翻译的中文版。
