前言
springboot jar包部署到服务器后,我们经常需要关注日志是否有报错等信息。但是整天盯着服务器上的日志确实太累了,本文介绍一种可以把springboot日志信息直接同步到钉钉群的方法,供大家参考。
原理介绍
springboot能够将日志直接打印到钉钉群的“自定义机器人”,主要基于下面几点:
- springboot自带的logback日志框架,支持自定义日志输出形式。
- 钉钉群提供了可以推送消息的webhook机器人。
原理:配置springboot的日志输出形式,新增一种自定义的输出形式,与传统的打印到控制台、日志文件中等形式不同,新增的形式直接调用钉钉API,将消息推送给钉钉群的自定义机器人。从而实现springboot报错信息打印到钉钉的目的。
效果演示
代码演示
一、新建钉钉群的webhook机器人
首先,你得有一个钉钉群,然后在钉钉群点击设置,新增自定义机器人:
关于加密方法的配置,请配置“加签”的形式,如下图所示:
二、配置自定义输出形式
在springboot项目中,新建一个自定义日志输出文件:DingDingAppender
,代码如下:
package com.coderbbb.blogv2.config.log;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.ConsoleAppender;
import com.coderbbb.blogv2.BlogV2Application;
import com.coderbbb.blogv2.service.DingService;
/**
* Created by coderbbb on 2019-08-26.
*/
public class DingDingAppender extends ConsoleAppender<LoggingEvent> {
@Override
protected void append(LoggingEvent eventObject) {
if (eventObject.getLevel() == Level.ERROR) {
byte[] byteArray = this.encoder.encode(eventObject);
String errorMsg = new String(byteArray);
DingService dingService = BlogV2Application.applicationContext.getBean(DingService.class);
try {
dingService.sendErr(errorMsg);
} catch (Exception ignored) {
}
}
}
}
在springboot的logback日志输出配置文件中,增加对应的日志输出形式:
<appender name="Ding" class="com.coderbbb.blogv2.config.log.DingDingAppender">
<encoder>
<pattern>${PATTERN}</pattern>
</encoder>
</appender>
在logback配置文件中,root节点添加自定义的输出形式:
<root level="info">
<!--打印日志到文件-->
<appender-ref ref="coderbbb_FILE"/>
<!--打印日志到钉钉群-->
<appender-ref ref="Ding"/>
</root>
完整的logback配置文件代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 文件输出格式 -->
<property name="PATTERN" value="%d{yyyy-MM-dd HH:mm:ss} %level [%thread] - %class{36}.%M %L - %msg%n"/>
<!-- <property name="PATTERN" value="%d{HH:mm:ss.SSS} %contextName [%thread] %highlight(%-5level) %logger{36} - %msg%n" />-->
<!-- <property name="PATTERN" value="%boldGreen(%date{yyyy-MM-dd HH:mm:ss}) %contextName | %highlight(%-5level) | %thread | %logger | %msg%n" />-->
<!-- test文件路径 -->
<property name="FILE_PATH" value="logs"/>
<!-- 每天产生一个文件 -->
<appender name="coderbbb_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 文件路径 -->
<file>${FILE_PATH}/coderbbb.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 文件名称 -->
<fileNamePattern>${FILE_PATH}/coderbbb.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 文件最大保存历史数量 -->
<MaxHistory>30</MaxHistory>
<maxFileSize>50MB</maxFileSize>
</rollingPolicy>
<encoder>
<pattern>${PATTERN}</pattern>
</encoder>
</appender>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${PATTERN}</pattern>
</encoder>
</appender>
<appender name="Ding" class="com.coderbbb.blogv2.config.log.DingDingAppender">
<encoder>
<pattern>${PATTERN}</pattern>
</encoder>
</appender>
<logger name="org.springframework" level="info"/>
<logger name="druid.sql" level="error"/>
<logger name="org.apache" level="error"/>
<logger name="org.ansj" level="off" />
<logger name="org.xbill" level="off" />
<root level="info">
<!-- 打印日志到文件-->
<appender-ref ref="coderbbb_FILE"/>
<!-- 打印日志到钉钉群-->
<appender-ref ref="Ding"/>
</root>
</configuration>
三、开发钉钉群webhook机器人接口API
钉钉webhook机器人签名算法:
package com.coderbbb.blogv2.utils;
import org.apache.commons.codec.digest.HmacAlgorithms;
import org.apache.commons.codec.digest.HmacUtils;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class DingUtil {
public static String sign(long timestamp,String secret) {
String stringToSign = timestamp + "\n" + secret;
byte[] hmac = new HmacUtils(HmacAlgorithms.HMAC_SHA_256, secret).hmac(stringToSign);
return URLEncoder.encode(Base64.getEncoder().encodeToString(hmac), StandardCharsets.UTF_8);
}
}
封装好的java推送消息导钉钉群webhook机器人代码:
package com.coderbbb.blogv2.service;
import com.coderbbb.blogv2.config.properties.DingDingPropertiesConfig;
import com.coderbbb.blogv2.database.dto.ding.DingBaseDTO;
import com.coderbbb.blogv2.database.dto.ding.DingTextMsg;
import com.coderbbb.blogv2.utils.DingUtil;
import com.coderbbb.blogv2.utils.ObjectMapperUtil;
import com.coderbbb.blogv2.utils.OkhttpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DingService {
@Autowired
private DingDingPropertiesConfig dingDingPropertiesConfig;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public void sendErr(String message) throws Exception {
DingBaseDTO dingBaseDTO = new DingBaseDTO();
dingBaseDTO.setMsgtype("text");
DingTextMsg dingTextMsg = new DingTextMsg();
dingTextMsg.setContent(message);
dingBaseDTO.setText(dingTextMsg);
String json = ObjectMapperUtil.get().writeValueAsString(dingBaseDTO);
long timestamp = System.currentTimeMillis();
String url = dingDingPropertiesConfig.getUrl() + "×tamp=" + timestamp + "&sign=" + DingUtil.sign(timestamp, dingDingPropertiesConfig.getSign());
OkhttpUtil.postRaw(url, "application/json;charset=utf-8", json, null);
}
}