企业微信机器人通知
描述
生产环境对异常监控实时性要求比较高,通常情况大家都会使用邮件报警,非常重要的会使用短信报警。邮件大家容易忽略,而且邮箱空间有限需要定期清理,短信又要收费,所以我们要采用一种既要免费又要实时性高的方式,那就是大家平时工作所用的沟通工具(企微,钉钉)
目的
ERROR级别日志实时报警
自定义业务预警通知
对系统无侵入设计
能够快速定位问题
最重要能够实时群发通知
邮件通知
可以看到邮件通知非常多,而且及时性也不够
方案
既然要无侵入设计,就不能到处埋点,最终选择自定义Appender的方式处理
由于公司内部工作沟通工具是企业微信,所以就借助企业微信机器人发送实时通知消息,根据请求链路ID结合ELK工具就可以从消息通知直接跳转到ELK,查看完整的链路日志从而快速定位问题
废话不多说直接上代码。。。
ErrorWarnAppender
public class ErrorWarnAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
@Setter
private String packageNames;
@Override
protected void append(ILoggingEvent event) {
if (null == event || "DEV".equalsIgnoreCase(ApplicationHelper.getEnv()) || StrUtil.isEmpty(MDC.get(ScmConstant.REQUEST_ID))) {
return;
}
boolean success = Arrays.stream(packageNames.split(",")).anyMatch(event.getLoggerName()::startsWith);
if (!success) {
return;
}
if (null == event.getThrowableProxy()) {
RobotUtil.sendWarn(event.getFormattedMessage());
} else {
RobotUtil.sendWarn(event.getFormattedMessage(), event.getThrowableProxy().getClassName() + ":" + event.getThrowableProxy().getMessage());
}
}
}
RobotUtil
public class RobotUtil {
/**
* 警告地址
*/
private static final String WARN_URL = "XXX";
/**
* 通知地址
*/
private static final String NOTICE_URL = "XXX";
/**
* 测试环境ELK地址
*/
private static final String ELK_URL = "XXX";
/**
* 生产环境ELK地址
*/
private static final String PRD_ELK_URL = "XXX";
/**
* 发送企业微信机器人通知消息
*
* @param message
*/
public static void sendNotice(StringBuilder message) {
try {
// 发送通知消息
if (isPro()) {
HttpUtil.post(NOTICE_URL, RobotUtil.getMessage(message));
} else {
message.insert(0, "env:" + ApplicationHelper.getEnv() + "\n");
HttpUtil.post(WARN_URL, RobotUtil.getMessage(message));
}
} catch (Exception ignored) {
}
}
/**
* 发送企业微信机器人警告消息
*
* @param message
*/
public static void sendWarn(String message) {
sendWarn(message, null);
}
/**
* 发送企业微信机器人警告消息
*
* @param message
* @param exception
*/
public static void sendWarn(String message, String exception) {
if (!ApplicationHelper.getSendWarnEnable()) {
return;
}
try {
String requestId = MDC.get(ScmConstant.REQUEST_ID);
StringBuilder text = new StringBuilder();
text.append("env:").append(isPro() ? "<font color=\"red\">**生产环境**</font>" : ApplicationHelper.getEnv()).append("\n");
text.append("applicationName:").append(ApplicationHelper.getApplicationName()).append("\n");
if (StrUtil.isNotEmpty(requestId)) {
text.append("requestId:").append("[").append(requestId).append("](").append(String.format(isPro() ? PRD_ELK_URL : ELK_URL, requestId)).append(")").append("\n");
}
text.append("message:").append(StrUtil.subPre(message, 1000)).append("\n");
if (StrUtil.isNotEmpty(exception)) {
text.append("exception:").append(StrUtil.subPre(exception, 1000)).append("\n");
}
// 发送警告消息
HttpUtil.post(WARN_URL, RobotUtil.getMessage(text));
} catch (Exception ignored) {
}
}
/**
* 消息内容
*
* @param message
* @return
*/
private static String getMessage(Object message) {
Map<String, Object> textMap = new HashMap<>();
textMap.put("msgtype", "markdown");
Map<String, Object> contentMap = new HashMap<>();
contentMap.put("content", message);
textMap.put("markdown", contentMap);
return JSONUtil.toJsonStr(textMap);
}
/**
* 是否生产环境
*
* @return
*/
private static boolean isPro() {
return "PRO".equalsIgnoreCase(ApplicationHelper.getEnv());
}
}
logback配置
1、添加appender 配置
<appender name="ERROR_WARN" class="org.slf4j.ErrorWarnAppender">
<packageNames>org.xx,com.xx,com.yy</packageNames>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
2、各个环境增加appender
<springProfile name="test">
<root level="INFO">
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="ERROR_FILE"/>
<appender-ref ref="ERROR_WARN"/>
</root>
</springProfile>
<springProfile name="prd">
<root level="INFO">
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="ERROR_FILE"/>
<appender-ref ref="ERROR_WARN"/>
</root>
</springProfile>
效果展示
可以看到消息能够区分环境,应用名称,链路ID,错误信息(生产环境红色醒目提醒)
注意事项
1、
发送企业微信消息是https请求接口,每分钟限制最多20条消息,所以异常报警只发送error级别的通知
同时还可以借助于kafka异步消息,发送邮件,短信 等等消息通知
2、
我们会把message和exception 截取前1000个字符
3、