logback 简介
logback 官网:https://logback.qos.ch/
logback 由三个模块组成:
- logback-core
- logback-classic
- logback-access
logback-core
是其它模块的基础设施,其它模块基于它构建,logback-core
提供了一些关键的通用机制。
logback-classic
的地位和作用等同于 Log4J
,它也被认为是 Log4J
的一个改进版,并且它实现了简单日志门面 SLF4J
;
logback-access
主要作为一个与 Servlet
容器交互的模块,比如说tomcat
或者 jetty
,提供一些与 HTTP
访问相关的功能。
配置文件
接下来会介绍关于 logback 配置文件的配置项。
Configuration
整个 logback.xml 配置文件的结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <configuration scan="true" scanPeriod="60 seconds" debug="false"> <property name="glmapper-name" value="glmapper-demo" /> <contextName>${glmapper-name}</contextName> <appender> //xxxx </appender> <logger> //xxxx </logger> <root> //xxxx </root> </configuration>
|
scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
contextName
logger上下文,默认名称为 “default”。可以使用 contextName 标签设置成其他名字,用于区分不同应用程序。
1
| <contextName>xxl_job</contextName>
|
property
用于定义变量标签,name 为变量的名称,value 的值是变量的值。可以用 “${name}” 来使用变量。
1 2
| <property name="LOG_PATH" value="./logs/xxl-job-admin" />
|
logger
用来设置某一个包或者具体的某一个类的日志打印级别以及指定appender
。这里的 level 是向下兼容的,即 DEBUG 级别的也会包含 INFO 级别的日志。
name:用来指定受此logger
约束的某一个包或者具体的某一个类。
level:用来设置打印级别(TRACE
, DEBUG
, INFO
, WARN
, ERROR
, ALL
和 OFF
),还有一个值INHERITED
或者同义词NULL
,代表强制执行上级的级别。如果没有设置此属性,那么当前logger
将会继承上级的级别。
addtivity:用来描述是否向上级logger
传递打印信息。默认是true
。
1 2 3
| <logger name="org.springframework" level="WARN" additivity="true" /> <logger name="org.mybatis" level="DEBUG" additivity="true" /> <logger name="com.xxl.job.executor" level="DEBUG" additivity="true"/>
|
root
根logger,也是一种logger,且只有一个level属性。根logger 用于控制 appender 配置的日志等级和输出权限。
1 2 3 4 5 6 7 8 9
| <root level="INFO"> <appender-ref ref="FILEERROR" /> <appender-ref ref="FILEWARN" /> <appender-ref ref="FILEINFO" /> <appender-ref ref="FILEALL" /> <appender-ref ref="STDOUT" /> </root>
|
filter
filter其实是appender里面的子元素。它作为过滤器存在,执行一个过滤器会有返回DENY,NEUTRAL,ACCEPT三个枚举值中的一个。可以为appender
添加一个或多个过滤器,可以用任意条件对日志进行过滤。appender
有多个过滤器时,按照配置顺序执行。
- DENY:日志将立即被抛弃不再经过其他过滤器
- NEUTRAL:有序列表里的下个过滤器过接着处理日志
- ACCEPT:日志会被立即处理,不再经过剩余过滤器
filter 还指定了一个 class,class有两个种类:
- ThresholdFilter:临界值过滤器,过滤掉低于临界值的日志。当日志级别等于或高于临界值,过滤器返回 NEUTRAL;当日志级别低于临界值时,日志会被拒绝。
- LevelFilter:级别过滤器,根据日志级别进行过滤。如果日志级别等于配置级别,过滤器会根据 onMatch 和 onMismatch 接收或拒绝日志。
1 2 3 4 5 6 7 8 9 10 11 12 13
| <appender name="FILEINFO" class="ch.qos.logback.core.rolling.RollingFileAppender"> ...... <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>info</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender>
|
appender
appender
是一个日志打印的组件,这里组件里面定义了打印过滤的条件、打印输出方式、滚动策略、编码方式、打印格式等等。但是它只是一个配置,这个配置的开关和打印级别由 logger 或者 root 的 appender-ref 指定某个具体的 appender 控制。
appender 的种类
appender
有两个属性 name
和class
;name
指定appender
名称,class
指定appender
的全限定名。
- ConsoleAppender:把日志添加到控制台
- FileAppender:把日志添加到文件
- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件。它是FileAppender的子类
1 2 3
| <appender name="GLMAPPER-LOGGERONE" class="ch.qos.logback.core.rolling.RollingFileAppender"> </appender>
|
append 子标签
如果是 true
,日志被追加到文件结尾,如果是false
,清空现存文件,默认是true
。
file 子标签
file 标签用于指定被写入的文件名,可以是相对也可以是绝对路径,如果上级目录不存在会自动创建,没有默认值。
1 2 3
| <file> ${logging.path}/glmapper-spring-boot/glmapper-loggerone.log </file>
|
表示当前appender将会将日志写入到${logging.path}/glmapper-spring-boot/glmapper-loggerone.log
这个目录下。
rollingPolicy 子标签
这个子标签用来描述滚动策略的。这个只有appender
的class
是RollingFileAppender
时才需要配置。
1 2 3 4 5 6 7 8 9 10 11
| <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/all/log-all-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy>
|
TimeBasedRollingPolicy
最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。这个下面又包括了两个属性:
- FileNamePattern
- maxHistory
1 2 3 4 5 6 7 8 9
| <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <FileNamePattern> ${logging.path}/glmapper-spring-boot/glmapper-loggerone.log.%d{yyyy-MM-dd} </FileNamePattern> <MaxHistory>30</MaxHistory> </rollingPolicy>
|
上面的这段配置表明每天生成一个日志文件,保存30天的日志文件
FixedWindowRollingPolicy
根据固定窗口算法重命名文件的滚动策略。
encoder 子标签
对记录事件进行格式化。它干了两件事:
1 2 3 4 5
| <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> <charset>UTF-8</charset> </encoder>
|
目前encoder
只有PatternLayoutEncoder
一种类型。
不同日志隔离级别打印
根据包、类隔离
1 2 3 4 5 6 7 8 9 10 11 12
| <logger name="com.glmapper.spring.boot.service" level="${logging.level}" additivity="false"> <appender-ref ref="GLMAPPER-SERVICE" /> <appender-ref ref="GERROR-APPENDER" /> </logger>
<logger name="com.glmapper.spring.boot.task.TestLogTask" level="${logging.level}" additivity="true"> <appender-ref ref="SCHEDULERTASKLOCK-APPENDER" /> <appender-ref ref="ERROR-APPENDER" /> </logger>
|
根据环境隔离
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
| <springProfile name="dev"> <root level="debug"> <appender-ref ref="STDOUT"/>
</root> </springProfile>
<springProfile name="sit"> <root level="debug"> <appender-ref ref="STDOUT"/>
</root> </springProfile>
<springProfile name="uat"> <root level="info"> <appender-ref ref="STDOUT"/>
</root> </springProfile>
<springProfile name="prod"> <root level="info"> <appender-ref ref="STDOUT"/> <appender-ref ref="sendErrorMsgAppender"/> </root> </springProfile>
|
logback小实战
使用 logback 配置将日志定义到自定义输出源,可以拿SpringBoot 整合 logback 发送企微通知作为例子。
要实现error级别异常日志异常报警,就是要捕获所有的error级别的日志,然后解析出异常数据,调用企业微信接口发送消息即可。
Logback中的Appender
类用来表示日志的输出的目的地。所以我们只需要自定义一个 Appeder
,然后在Logback的配置文件中的所有的Logger
配置中(或者是所有Error级别的 Logger
配置)增加这个自定义的Appeder
就可以以拦截所有的(异常)日志。
首先定义一个企微发送消息的Appender。
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
| public class SendErrorMsgAppender extends UnsynchronizedAppenderBase<LoggingEvent> {
private String pattern;
private PatternLayout layout;
private Level nowLevel = Level.ERROR;
protected Logger logger = LoggerFactory.getLogger(this.getClass());
@Override protected void append(LoggingEvent eventObject) { if (eventObject == null || !eventObject.getLevel().isGreaterOrEqual(nowLevel)) { return; } try { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String url = "", userAccount = "", bodyString = ""; if (requestAttributes != null) { HttpServletRequest request = requestAttributes.getRequest();
url = request.getRequestURI(); userAccount = getUserinfo(request); bodyString = ServletUtil.getBody(request);
if (StringUtils.isNotBlank(bodyString)) { bodyString = JSON.toJSONString(JSON.parse(bodyString)); }
logger.info("url:{}", url); } else if (StringUtils.isBlank(eventObject.getFormattedMessage())) { logger.info("lockKey:{}", eventObject.getLoggerName()); return; } Environment bean = SpringUtil.getBean(Environment.class);
String active = bean.getProperty("spring.profiles.active"); if (!"prod".equals(active)) { return; }
String serverName = "jr-ai-open-api"; String errorMessage = layout.doLayout(eventObject); String webHook = bean.getProperty("qyWeChat.webHook");
if (StringUtils.isNotBlank(webHook)) { toWechat(webHook, serverName, active, url, userAccount, bodyString, errorMessage); } } catch (Exception e) { e.printStackTrace(); } }
@Override public void start() { PatternLayout patternLayout = new PatternLayout(); patternLayout.setContext(context); patternLayout.setPattern(getPattern()); patternLayout.start(); this.layout = patternLayout; super.start(); }
public void toWechat(String robotUrl, String projectName, String environment, String requestUrl, String requestAccount, String requestBody, String errorLog) throws Exception { String markdownContent = buildMarkdownContent(projectName, environment, requestUrl, requestAccount, requestBody, errorLog); String markdownMsg = "{\"msgtype\": \"markdown\", \"markdown\": {\"content\": " + JSON.toJSONString(markdownContent) + "}}"; try (CloseableHttpClient httpclient = HttpClients.createDefault()) { HttpPost httppost = new HttpPost(robotUrl); httppost.addHeader("Content-Type", "application/json; charset=utf-8"); httppost.setEntity(new StringEntity(markdownMsg, "utf-8")); httpclient.execute(httppost); } catch (Exception e) { e.printStackTrace(); } }
private String buildMarkdownContent(String projectName, String environment, String requestUrl, String requestAccount, String requestBody, String errorLog) { return "<font color=\"red\"> 【ERROR 通知】 </font> \n" + "> <font color=\"comment\"> 触发项目:</font> <font color=\"info\"> " + projectName + "</font> \n" + "> <font color=\"comment\"> 触发环境:</font> <font color=\"info\"> " + environment + "</font> \n" + "> <font color=\"comment\"> 触发时间:</font> " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + "\n" + "> <font color=\"comment\"> 请求URL:</font> " + requestUrl + "\n" + "> <font color=\"comment\"> 请求账号:</font> " + requestAccount + "\n" + "> <font color=\"comment\"> 请求Body:</font> \n```json\n" + requestBody + "\n```\n\n" + "<font color='red'>【Exception 详情】</font> \n```json\n" + errorLog + "\n```\n"; }
public String getPattern() { return pattern; }
public void setPattern(String pattern) { this.pattern = pattern; }
public PatternLayout getLayout() { return layout; }
public void setLayout(PatternLayout layout) { this.layout = layout; }
public Level getNowLevel() { return nowLevel; }
public void setNowLevel(Level nowLevel) { this.nowLevel = nowLevel; } }
|
在 logback.xml 中定义 SendErrorMsgAppender 的输出源,并指定在生产环境下才输出日志。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <appender name="sendErrorMsgAppender" class="com.junrunrenli.proxy.exception.SendErrorMsgAppender"> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] [%X{loginName}] %-5level %logger{50} [line:%L]: %ex{10} -%msg%n</pattern> <filter class="ch.qos.logback.core.filter.EvaluatorFilter"> <evaluator> <expression>return message.contains("Broken pipe");</expression> </evaluator> <OnMatch>DENY</OnMatch> <OnMismatch>ACCEPT</OnMismatch> </filter> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <OnMatch>ACCEPT</OnMatch> <OnMismatch>DENY</OnMismatch> </filter> </appender>
<springProfile name="prod"> <root level="info"> <appender-ref ref="STDOUT"/> <appender-ref ref="sendErrorMsgAppender"/> </root> </springProfile>
|
这样配置以后,项目中所有使用log.error()
方法打印的日志(即error级别日志)都会通过企业微信发出消息报警。