一、Logback 简介
Logback 是一个日志框架,旨在成为 log4j 的替代品。它由 Ceki Gülcü 创建并维护,是一款开源的日志框架,是 slf4j(Simple Logging Facade for Java)的实现。相比于 log4j,Logback 具有更高的性能和更好的可扩展性,并提供了众多的特性,如异步日志、动态日志级别、决策器等。
在项目中使用 Logback 可以很方便地记录系统运行时的信息、警告和错误等,对于开发人员来说是非常有帮助的。
Logback 的基本概念。
- Logger:Logger 是 Logback 的核心概念,它用于记录日志。Logger 是按照类的层次结构进行命名的,每个 Logger 对象都有一个名字,如果没有显式指定名字,则使用当前类的全限定名作为它的名字。
- Appender:Appender 用于定义日志的输出方式,例如输出到控制台、写入文件、发送邮件等。Logback 有多种类型的 Appender,可以同时输出到多个目标。
- Layout:Layout 用于定义日志输出格式,例如日期格式、日志级别、线程名、类名等。Logback 有多种类型的 Layout,可以根据需求选择使用。
- Filter:Filter 用于对日志进行过滤,可以根据日志级别、关键字、线程名等条件进行过滤。
二、Logback 的主要功能和特点
1. Logback 的主要功能
Logback 的主要功能包括:
- 支持多种日志级别:Logback 支持多种日志级别,包括 TRACE、DEBUG、INFO、WARN 和 ERROR 等。
- 多种日志输出方式:Logback 支持将日志输出到控制台、文件、Syslog、JMS、邮件等多种输出方式,用户可以根据自己的需求选择不同的 Appender。
- 灵活的配置方式:Logback 的配置文件可以使用 XML 或者 Groovy 编写,非常灵活方便。
- 高性能:Logback 的性能非常好,可以满足高并发场景下的需求。
- 精细的过滤功能:Logback 支持使用 Filter 对日志进行精细的过滤操作,可以根据日志级别、线程名、关键字等条件进行过滤。
2. Logback 的特点
Logback 与其他日志系统相比,具有以下几个特点:
- 高性能:Logback 是目前 Java 日志框架中性能最好的一个,它支持异步输出和无锁数据结构等方式来提升性能。
- 灵活的配置:Logback 配置文件可以使用 XML 或者 Groovy 编写,非常灵活方便。同时,Logback 还提供了 Web 界面工具 JaninoConfigurer,可以通过 Web 页面来配置 Logback。
- 多种输出方式:Logback 支持多种输出方式,并且可以自定义 Appender 和 Layout,对于比较复杂的应用场景也可以很好地满足需求。
- 易于集成:对于 Spring、Hibernate、Lucene 等流行框架,Logback 都提供了官方的集成插件,使用起来非常方便。
三、Logback 常用配置
1. name 属性
Logger 中的 name 属性用于记录器的命名,它是一个字符串,可以用任何字符串来命名 Logger。Logback 中同一个 Logger 可以有多个子 Logger,它们之间构成了一个层次结构,其命名规则按照“类所在包名+类名称”的方式进行。例如:
<logger name="com.demo.UserService" level="DEBUG" additivity="false">
<appender-ref ref="FILE"/>
<appender-ref ref="CONSOLE"/>
</logger>
上述配置中,Logger 的 name 属性为 com.demo.UserService,表示一个 UserService 类的 Logger。如果不指定 name,则默认使用 RootLogger,即根 Logger。
2. level 属性
Logger 的 level 属性用于指定输出的日志级别,Logback 日志的级别由低到高分别为 TRACE、DEBUG、INFO、WARN、ERROR、OFF。若设置了 Logger 的 level 属性,则将只输出指定级别及更高级别的日志信息,例如:
<logger name="com.demo.UserService" level="DEBUG" additivity="false">
<appender-ref ref="FILE"/>
<appender-ref ref="CONSOLE"/>
</logger>
上述配置中,Logger 的 level 属性为 DEBUG,表示只输出 DEBUG 级别及更高级别的日志信息。
3. additivity 属性
additivity 属性表示是否继承父 Logger 的 Appender,即是否在父 Logger 的 Appender 中同时输出相同的日志信息。如果为 false,则该 Logger 只会将日志信息输出到自己指定的 Appender 中,并不会将日志信息传递给父 Logger。默认情况下,additivity 属性为 true。
<logger name="com.demo.UserService" level="DEBUG" additivity="false">
<appender-ref ref="FILE"/>
<appender-ref ref="CONSOLE"/>
</logger>
上述配置中,additivity 属性为 false,表示 UserService Logger 只会将日志信息输出到自己指定的 Appender,而不会将日志信息传递给 RootLogger(父 Logger)。
4. ConsoleAppender 配置
ConsoleAppender 可以将日志信息输出到控制台,配置示例如下:
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<target>System.out</target>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
上述配置中,我们定义了一个名为 CONSOLE 的 Appender,其 class 属性指定为 ch.qos.logback.core.ConsoleAppender,表示输出到控制台。具体细节如下:
(1)target:指定输出目标,可以是 System.out 或 System.err,默认为 System.out。
(2)encoder:指定输出格式,通常使用 PatternLayoutEncoder 来指定输出格式,具体内容详见 Layout 配置。
5. FileAppender 配置
FileAppender 可以将日志信息输出到文件中,配置示例如下:
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>log/test.log</file>
<append>false</append>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} : %msg%n</pattern>
</encoder>
</appender>
上述配置中,我们定义了一个名为 FILE 的 Appender,其 class 属性指定为 ch.qos.logback.core.FileAppender,表示输出到文件。具体细节如下:
(1)file:指定输出文件路径及名称。
(2)append:指定是否追加数据到输出文件中,若为 true 则追加,否则覆盖,默认值为 true。
(3)encoder:指定输出格式,通常使用 PatternLayoutEncoder 来指定输出格式,具体内容详见 Layout 配置。
6. RollingFileAppender 配置
RollingFileAppender 可以将日志信息输出到文件中,同时支持按时间、大小等条件进行切割,以避免单个日志文件过大的问题,配置示例如下:
<appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>log/test.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log/test.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} : %msg%n</pattern>
</encoder>
</appender>
上述配置中,我们定义了一个名为 ROLLING_FILE 的 Appender,其 class 属性指定为 ch.qos.logback.core.rolling.RollingFileAppender,表示输出到文件。具体细节如下:
(1)file:指定输出文件路径及名称。
(2)rollingPolicy:指定切割策略,此处使用的是 ch.qos.logback.core.rolling.TimeBasedRollingPolicy,表示按时间切割。fileNamePattern 即指定切割后文件名称的格式,maxHistory 指定最大的历史文件个数。
(3)encoder:指定输出格式,通常使用 PatternLayoutEncoder 来指定输出格式,具体内容详见 Layout 配置。
7. SMTPAppender 配置
SMTPAppender 可以将日志信息以邮件形式发送到指定邮箱,配置示例如下:
<appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
<smtpHost>smtp.test.com</smtpHost>
<smtpPort>465</smtpPort>
<ssl>true</ssl>
<username>[email protected]</username>
<password>123456</password>
<to>[email protected],[email protected]</to>
<from>[email protected]</from>
<subject>错误日志</subject>
<layout class="ch.qos.logback.classic.html.HTMLLayout"/>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
上述配置中,我们定义了一个名为 EMAIL 的 Appender,其 class 属性指定为 ch.qos.logback.classic.net.SMTPAppender,表示通过邮件发送日志信息。具体细节如下:
(1)smtpHost、smtpPort:指定邮件服务器的地址和端口。
(2)ssl:是否使用 SSL 连接邮件服务器。
(3)username、password:邮件服务器的登录账号和密码。
(4)to、from:指定接收邮件的邮箱地址和发送者邮箱地址。
(5)subject:邮件主题。
(6)layout:指定输出格式,可以使用各种 Layout 类型,此处使用的是 HTMLLayout。
(7)filter:指定过滤条件,此处使用的是 ThresholdFilter,表示只发送 ERROR 级别及以上的日志信息。
8. PatternLayout 配置
PatternLayout 可以按照指定的格式输出日志信息,下面是一些常用的格式占位符及其含义:
(1)%d{HH:mm:ss.SSS}:输出日志的时间,精确到毫秒。
(2)[%thread]:输出日志的线程名。
(3)%-5level:输出日志级别,左对齐并占位 5 个字符,若不足则用空格补齐。
(4)%logger{36}:输出日志所在类的名称,最长 36 个字符。
(5): %msg%n:输出日志消息及换行符。
配置示例如下:
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} : %msg%n</pattern>
</encoder>
上述配置中,我们使用 PatternLayoutEncoder 来指定输出格式,其 pattern 属性指定了输出格式。具体细节可见上面的格式占位符及其含义。
9. HTMLLayout 配置
HTMLLayout 可以将日志信息以 HTML 格式进行输出,用于可视化展示和查看。配置示例如下:
<layout class="ch.qos.logback.classic.html.HTMLLayout"/>
上述配置中,我们使用 HTMLLayout 类型来指定输出格式,其 class 属性指定为 ch.qos.logback.classic.html.HTMLLayout。如果需要添加样式和自定义输出内容,可以通过 CSS 和 Header、Footer 配置进行实现。
10. LevelFilter 配置
LevelFilter 用于根据日志级别过滤日志信息,可用于只记录某些级别的日志信息或忽略某些级别的日志信息,配置示例如下:
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>DENY</onMatch>
<onMismatch>ACCEPT</onMismatch>
</filter>
上述配置中,我们使用 LevelFilter 类型来指定过滤器,其 class 属性指定为 ch.qos.logback.classic.filter.LevelFilter。具体细节如下:
(1)level:指定要过滤的日志级别。
(2)onMatch:指定当过滤条件匹配时要执行的操作,此处使用 DENY 表示拒绝输出。
(3)onMismatch:指定当过滤条件未匹配时要执行的操作,此处使用 ACCEPT 表示接受输出。
- DuplicateMessageFilter 配置
DuplicateMessageFilter 用于过滤掉重复的日志信息,即当多个日志消息内容相同时,只记录其中一个日志信息,可用于减少日志量或避免误导信息,配置示例如下:
<filter class="ch.qos.logback.classic.filter.DuplicateMessageFilter">
<allowedRepetitions>1</allowedRepetitions>
</filter>
上述配置中,我们使用 DuplicateMessageFilter 类型来指定过滤器,其 class 属性指定为 ch.qos.logback.classic.filter.DuplicateMessageFilter。allowedRepetitions 属性指定了允许的重复次数,此处设置为 1 表示当某个日志消息出现重复内容时,只记录一次该日志信息。
四、配置示例
1. 一个简单常用的配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 定义日志文件保存的路径和文件名 -->
<property name="LOG_HOME" value="/var/log/myapp"/>
<property name="APP_NAME" value="myapp"/>
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 按日期拆分的文件输出 -->
<appender name="rollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/${APP_NAME}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/${APP_NAME}-%d{yyyy-MM-dd}.log.gz</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 过滤一些无用的日志 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<!-- 设置日志记录器 -->
<logger name="com.mypackage" level="debug" additivity="false">
<appender-ref ref="rollingFile"/>
<appender-ref ref="console"/>
</logger>
<!-- 根的日志记录器 -->
<root level="error">
<appender-ref ref="rollingFile"/>
<appender-ref ref="console"/>
</root>
</configuration>
上述配置代码中,控制台输出和按日期拆分的文件输出是两个常见的 appender。这里也可以添加其他的 appender 来实现将日志发往不同渠道(邮件、数据库等),根据需要进行修改即可。在日志文件备份方面,这里使用了 TimeBasedRollingPolicy 滚动策略,可以在每天或每个小时结束时生成一个新的日志文件。当然,也可以使用 SizeAndTimeBasedRollingPolicy 等其他的日志文件备份策略。
注意,上述代码中的路径、文件名、包名等都是示例,实际应用时需要根据具体情况进行修改。
2. ELK相关配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{35} : %msg%n</pattern>
</encoder>
</appender>
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{35} : %msg%n</pattern>
</encoder>
</appender>
<appender name="logstash" class="ch.qos.logback.core.net.SyslogAppender">
<syslogHost>${ELK_HOST}</syslogHost>
<syslogPort>${ELK_PORT}</syslogPort>
<suffixPattern>%msg%n</suffixPattern>
<facility>USER</facility>
<includeMDC>true</includeMDC>
<layout class="net.logstash.logback.layout.LogstashLayout">
<jsonFormatter class="net.logstash.logback.jackson.LogstashJacksonJsonProvider"/>
<fieldNames>
<message>log</message>
</fieldNames>
<customFields>{"app_name":"${appName}"}</customFields>
</layout>
</appender>
<logger name="com.example" level="INFO">
<appender-ref ref="logstash"/>
<appender-ref ref="console"/>
<appender-ref ref="file"/>
</logger>
<root level="INFO">
<appender-ref ref="logstash"/>
<appender-ref ref="console"/>
<appender-ref ref="file"/>
</root>
</configuration>
3. 比较详细的配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!-- 定义日志文件保存的路径和文件名 -->
<property name="LOG_DIR" value="/var/log/myapp"/>
<property name="APP_NAME" value="myapp"/>
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 按日期拆分的文件输出 -->
<appender name="rollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/${APP_NAME}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIR}/${APP_NAME}-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
</appender>
<!-- 异步输出日志,避免阻塞主线程 -->
<appender name="asyncAppender" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>1024</queueSize>
<appender-ref ref="rollingFile"/>
<!-- 还可以添加其他 appender -->
</appender>
<!-- 按大小和日期拆分的文件输出,比较灵活 -->
<appender name="rollingFile2" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/${APP_NAME}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIR}/${APP_NAME}-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
</appender>
<!-- 根据系统环境变量动态配置日志级别 -->
<appender name="dynamicThresholdLogging" class="ch.qos.logback.classic.sift.SiftingAppender">
<discriminator>
<key>env</key>
<defaultValue>dev</defaultValue>
</discriminator>
<sift>
<appender name="FILE-${env}" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/${APP_NAME}-${env}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIR}/${APP_NAME}-${env}-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>${env == 'prod' ? 'INFO' : 'DEBUG'}</level>
</filter>
</appender>
</sift>
</appender>
<!-- 设置日志记录器 -->
<logger name="com.mypackage" level="debug" additivity="false">
<appender-ref ref="asyncAppender"/>
</logger>
<!-- 根的日志记录器 -->
<root level="error">
<appender-ref ref="asyncAppender"/>
<appender-ref ref="console"/>
</root>
<!-- MDC(Mapped Diagnostic Context) -->
<conversionRule conversionWord="reqId" converterClass="com.acme.logback.ReqIdConverter"/>
<appender name="mdcDemo" class="ch.qos.logback.classic.net.SyslogAppender">
<syslogHost>localhost</syslogHost>
<facility>LOCAL0</facility>
<suffixPattern>%mdc{reqId} - %message</suffixPattern>
</appender>
<logger name="com.acme.service" level="TRACE">
<appender-ref ref="mdcDemo"/>
</logger>
<!-- MDC 的另一种使用方式:通过配置文件 -->
<appender name="mdcDemo2" class="ch.qos.logback.classic.net.SyslogAppender">
<syslogHost>localhost</syslogHost>
<facility>LOCAL0</facility>
<suffixPattern>%X{reqId} - %message</suffixPattern>
</appender>
<logger name="com.acme.service2" level="TRACE">
<appender-ref ref="mdcDemo2"/>
</logger>
<!-- Logback Groovy 编写复杂的过滤条件 -->
<appender name="groovyDemo" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/${APP_NAME}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIR}/${APP_NAME}-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.core.filter.Filter">
<groovy>
def mdc = event.getMDC()
if (mdc == null) {
return FilterReply.NEUTRAL
}
def user = mdc.get("user")
if (user == null || !user.equals("admin")) {
return FilterReply.DENY
}
return FilterReply.ACCEPT
</groovy>
</filter>
</appender>
<logger name="com.mypackage2" level="debug">
<appender-ref ref="groovyDemo"/>
</logger>
<!-- JMX 监控,可以在 JConsole 或者 JVisualVM 中查看 -->
<jmxConfigurator/>
<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/>
<!-- 日志分离存储 -->
<appender name="dbAppender" class="com.example.logback.appender.DBAppender">
<connectionSource class="com.example.logback.datasource.DataSourceConnectionSource">
<url>jdbc:mysql://localhost:3306/dbname</url>
<user>username</user>
<password>password</password>
<driverClass>com.mysql.jdbc.Driver</driverClass>
<minConnectionsPerPartition>5</minConnectionsPerPartition>
<maxConnectionsPerPartition>20</maxConnectionsPerPartition>
<partitionCount>2</partitionCount>
</connectionSource>
<bufferSize>1000</bufferSize>
<tableName>logs</tableName>
<columns>
<column name="timestamp" pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}" />
<column name="level" pattern="%level" />
<column name="logger" pattern="%logger" />
<column name="message" pattern="%message" />
</columns>
</appender>
<logger name="com.example.myapp" level="INFO">
<appender-ref ref="asyncAppender"/>
<appender-ref ref="dbAppender"/>
</logger>
</configuration>
此份 Logback 配置有以下功能:
- 按大小和日期拆分的文件输出:可以通过配置文件来实现,比较灵活;
- 根据系统环境变量动态配置日志级别:可以根据环境变量的值来决定输出的日志级别;
- MDC(Mapped Diagnostic Context):通过为每个线程关联一个 Map 来实现在日志输出中添加自定义的上下文信息,例如请求 ID、用户信息等;
- 可以通过配置文件或者编写 Java 类来使用 MDC;
- 可以通过 Groovy 或者 JavaScript 等脚本语言来编写复杂的过滤条件,可以根据日志事件中的任意字段来决定是否输出;
- JMX 监控:可以在 JConsole 或者 JVisualVM 中查看 Logback 使用情况和日志输出;
- 日志分离存储:将日志输出到数据库中。
需要注意的是,上述配置代码的变量值和路径都是示例,实际使用时需要根据实际情况进行修改。