springboot+企微实现项目异常告警

httpServletRequest.getParameterMap()为空_凡凡轶崔的博客-CSDN博客

群机器人配置说明 - 接口文档 - 企业微信开发者中心 (qq.com)

由于项目中暂时没有接入日志系统,导致平时查看异常信息非常的不方便。而且每次都需要产品,测试发现了才知道出错了,这影响不太好。

于是结合 切面 + 全局异常拦截 等技术实现结合企微的实时告警。

我是利用企微中提供的群机器人发布异常信息,新建一个机器人会提供一个Webhook地址

springboot+企微实现项目异常告警_第1张图片

开发者可以按提供的Webhook发起HTTP POST 请求,即可实现给该群组发送消息。具体可以参考文章:群机器人配置说明 - 接口文档 - 企业微信开发者中心 (qq.com)

使用的消息内容类型:markdown类型

{
    "msgtype": "markdown",
    "markdown": {
        "content": "实时新增用户反馈<font color=\"warning\">132</font>,请相关同事注意。\n
        >类型:<font color=\"comment\">用户反馈</font>
        >普通用户反馈:<font color=\"comment\">117</font>
        >VIP用户反馈:<font color=\"comment\">15例"
    }
}
参数 是否必填 说明
msgtype 消息类型,此时固定为markdown
content markdown内容,最长不超过4096个字节,必须是utf8编码

使用原因:参数较少并且内容长度支持较长。

异常告警机器人代码:

@Component
@Slf4j
public class ExceptionMsgBot {

    private static final String METHOD_GET = "GET";
    private static final String METHOD_POST = "POST";

    /**
     * 企微机器人webhook地址
     */
    @Value("${ExceptionMsgBot.url}")
    private String url;

    /**
     * 是否启用
     */
    @Value("${ExceptionMsgBot.isEnable:true}")
    private Boolean isEnable;

    /**
     * 最大长度
     */
    @Value("${ExceptionMsgBot.maxSize:2500}")
    private int maxSize;

    public void send(Exception e, HttpServletRequest request) {
        if (!isEnable) {
            return;
        }

        String title = "标题";
        //时间
        String now = DateUtil.format(new Date(), DatePattern.NORM_DATETIME_FORMAT);
        //请求url
        String api = request.getRequestURL().toString();
        //请求参数
        String params = JSONUtil.toJsonStr(request.getAttribute("params"));
        if (!StringUtils.isEmpty(params)) {
            //将json转成普通string
            params = params.replace("\"","\\\"");
        }
        //服务器IP
        String serverIP = NetUtil.getLocalhostStr();
        //客户端IP
        String clientIP = request.getRemoteAddr();

        String textMsg = "{\n" +
            "    \"msgtype\": \"markdown\",\n" +
            "    \"markdown\": {\n" +
            "        \"content\": \"" + title + "\\n\n" +
            "         【url】: " + api + " \n" +
            "         【参数】: " + params + " \n" +
            "         【时间】: " + now + " \n" +
            "         【serverIp】: " + serverIP + " \n" +
            "         【clientIp】: " + clientIP + " \n" +
            "         【message】: " + e.getMessage() + " \n" +
            "         【cause】: " + e.getCause() + " \n" +
            "         【StackTrace】: " + e + "  \"\n" +
            "    }\n" +
            "}\n";

        //发送消息
        HttpResponse response = HttpRequest
            .post(url)
            .header(Header.CONTENT_TYPE, "application/json; charset=UTF-8")
            .body(textMsg)
            .execute();

        log.info("【异常消息发送结果】 {}", response.body());
    }
}

这次设计的异常信息主要展示8点

  • 请求接口路径
  • 请求参数
  • 当前时间
  • 服务器ip
  • 客户端ip
  • e.message
  • e.cause
  • e.stack

除了请求参数需要特殊处理,其他参数都可以方便获取。

因为每次请求接口,项目中会有个全局日志切面类打印输出每次请求的信息。

这个全局日志切面类会在进入controller之前进行拦截,从而可以获取到每次请求的参数。然后将参数封装到request,传递给内部web容器使用,也就是本文章的异常告警机器人使用。

@Aspect
@Configuration
@Slf4j
public class GlobalControllerLogAspect {

    @Pointcut("execution (* com.*..controller..*.*(..))")
    public void controllers() {
    }

    @Around("controllers()")
    public Object aroundProcess(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        long startTime = System.currentTimeMillis();
        Object proceed = null;
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            String uid = request.getHeader(HeaderConstant.UID);
            List<Object> params = new ArrayList<>();
            if (args != null && args.length > 0) {
                for (Object arg : args) {
                    if (arg instanceof HttpServletRequest || arg instanceof HttpServletResponse) {
                        continue;
                    } else if (arg instanceof MultipartFile) {
                        continue;
                    } else {
                        params.add(arg);
                    }
                }
            }
            //保存请求参数
            request.setAttribute("params", JSONUtil.toJsonStr(params));
            log.info("接口方法:{},uid:{},请求参数:{}", request.getRequestURL().toString(), uid, JSONUtil.toJsonStr(params));
            proceed = joinPoint.proceed(args);
            log.info("响应结果:{},耗时:{}ms", JSONUtil.toJsonStr(proceed), System.currentTimeMillis() - startTime);
        } finally {
        }
        return proceed;
    }
}

可以在 joinPoint(连接点) 获取到请求参数。

异常告警机器人全局异常拦截器埋点

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @Autowired
    private ExceptionMsgBot bot;

    @ExceptionHandler(value = Exception.class)
    public void handle(Exception e, HttpServletRequest request, HttpServletResponse response) {
        log.error("接口[{}]异常, ", request.getRequestURI(), e);
        CommonResult<Object> result = CommonResult.failed("系统出了点小问题");
        //埋点
        bot.send(e, request);
        String json = JSONUtil.toJsonStr(result);
        response.setHeader("Content-Type", "application/json; charset=UTF-8");
        try {
            response.getWriter().write(json);
        } catch (IOException ex) {
            log.error("接口[{}]IO异常, ", request.getRequestURI(), ex);
        }
    }
}

当有异常信息被全局异常拦截器捕获时,保存异常日志的同时,发送告警信息通知开发等相关人员处理。

效果:

异常告警,请相关同事注意!
         【url】: http://172.18.139.x:9001/xxx 
         【参数】: ["uid",{"f1":9,"f2":2,"f3":2377,"f4":false,"f5":"","f6":1}] 
         【时间】: 2022-04-14 16:35:28 
         【serverIp】: 172.18.139.x 
         【clientIp】: 172.18.139.x 
         【message】: xxx不能为空 
         【cause】: null 
         【StackTrace】: com.xxx.common.component.exception.ApiException: xxx不能为空  

完美~

你可能感兴趣的:(SpringBoot,Java,java,spring,boot)