springBoot 整合 logback异常告警 发送企业微信

一、项目背景
代码异常的增多(error级别日志增多)也是系统异常的一种,对于这种情况,收到报警消息之后,我们开发同学一般需要登录到线上机器,查看错误日志来排查具体的原因。这种情况下,如果报警消息中能够包括出现异常的上下文以及异常堆栈,不仅能第一时间发现问题,这样的话还能够一定程度上提高问题的排查修复效率。以及我们也要关注线上的异常日志,进而针对异常日志反应的程序问题来相应的优化改造代码。
所以我们就基于企业微信和logback日志系统来实现error级别异常日志发送企业微信群消息报警功能,消息中包括了异常上下文以及异常堆栈消息。
二、技术选型
针对日志监控,通常的手段有以下几种:
1.在日志输出时,并在写到磁盘前,就可以通过拦截手段拿到日志并分析日志的级别,或者在异常处理时就触发日志警告(比如告警系统Cat),也可以通过Java agent的手段(如skywalking使用探针技术收集),当然也可以在日志写到磁盘上,由ELK采集日志并分析日志,并将异常结果推送到用户,比如可以将日志采集并存储到阿里云的SLS,并由SLS分析并触发日志告警。
2.Logback是由log4j创始人设计的又一个开源日志组件。logback当前分成三个模块:logback-core,logback- classic和logback-access。logback-core是其它两个模块的基础模块。logback-classic是log4j的一个 改良版本。此外logback-classic完整实现SLF4J API使你可以很方便地更换成其它日志系统如log4j或JDK14 Logging。logback-access访问模块与Servlet容器集成提供通过Http来访问日志的功能。
三.为什么要使用Logback
其他的告警,都需要搭建一套完整的告警系统,如Cat,skywalking等都需要搭建独立的系统。因此,我们就实现由logback集成企信机器人来实现告警功能。通过logback,我们是通过appender将日志分析并写入到磁盘上,那么我们可以在appender获取到日志内容,并判断是否需要发送告警推送。
由于我们的系统中已经使用了Logback,我们就可以很方便的管理我们的日志。Logback也提供了一些可实现的日志处理供我们自定义实现来处理日志操作。
Logback详细介绍及其日志输出打印格式:https://www.cnblogs.com/origalom/p/11120423.html
四.接入思路及实现
企业微信群机器人配置:
企业微信给所有的企业微信群提供了机器人功能,通过群机器人,可以提供一些自定义的消息推送。简单来说就是直接通过http调用webhook接口来发送群消息。
群机器人配置官方链接:https://developer.work.weixin.qq.com/document/path/91770
首先需要明确,微信报警消息中需要发送哪些数据。
新建TextMessage类,来定义微信报警中需要发送文本数据及设置需提醒的人或手机号。
新建Message类,来定义微信报警中需要发送哪些数据格式。
新建MessageManager类,实现sendMessage()方法来发送消息提醒。
具体实现:

public static boolean sendMessage(Message message, String webHookAddress) {
        OkHttpClient client = new OkHttpClient.Builder()
                // 设置连接超时时间
                .connectTimeout(10, TimeUnit.SECONDS)
                // 设置读取超时时间
                .readTimeout(20, TimeUnit.SECONDS)
                .build();
        MediaType contentType = MediaType.parse("application/json; charset=utf-8");
        RequestBody body = RequestBody.create(contentType, JSONObject.toJSONString(message));
        Request request = new Request.Builder().url(webHookAddress).post(body).addHeader("cache-control", "no-cache").build();
        try {
            log.info("===开始调用企业微信发送消息!");
            Response response = client.newCall(request).execute();
            byte[] datas = response.body().bytes();
            String respMsg = new String(datas);
            JSONObject resultJSON = JSONObject.parseObject(respMsg);
            if (resultJSON.getIntValue("errcode") == 0) {
                log.info("消息发送成功!");
                return true;
            }
            log.info("消息发送失败, 错误信息如下: {}", resultJSON.getString("errmsg"));
            return false;
        } catch (IOException e) {
//            log.info("消息发送成功!");
            log.info("消息发送失败, 异常信息如下: {}", e.getMessage());
            return false;
        }
    }

logback拦截error级别的日志:
在logback中,要自定义Appeder,只需要继承 AppenderBase类实现append()方法即可。
首先定义一个抽象类AbstractAlarmAppender,该类继承自 AppenderBase类,并实现了append()方法:
具体实现代码:

public abstract class AbstractAlarmAppender extends AppenderBase<LoggingEvent> {

    private static final String template_offline = "服务名: %s \n当前环境: %s \n日志等级: %s \n异常时间: %s \n异常描述: %s \n异常详细信息: %s";

    @Override
    protected void append(LoggingEvent eventObject) {
        try {
            Level level = eventObject.getLevel();
            if (Level.ERROR != level) {
                // 只处理error级别的报错
                return;
            }
            // 获取用户在日志中输出的语句,一般涵盖异常上下文
            LoggerContextVO loggerContextVO = eventObject.getLoggerContextVO();
            IThrowableProxy proxy = eventObject.getThrowableProxy();
            Map<String, String> propertyMap = loggerContextVO.getPropertyMap();
            String active = propertyMap.get("ACTIVE");
            if (StringUtil.isEmpty(active) || !active.equals("prod")) {
//               只处理prod线上环境
                return;
            }
            // 获取异常堆栈
            Throwable t = ((ThrowableProxy) proxy).getThrowable();
            String name = propertyMap.get("APPLICATION_NAME");
            String messageText = String.format(template_offline,
                    StringUtil.isBlank(name) ? loggerContextVO.getName() : name,
                    propertyMap,
                    eventObject.getLevel(),
                    DateTimeUtil.formatTime(new Date(), DateTimeUtil.DEFAULT_FORMAT_DATE_TIME),
                    t.toString().length() <= 1000 ? t : t.toString().substring(0, 1000),
                    Arrays.toString(t.getStackTrace()).length() <= 1888 ? Arrays.toString(t.getStackTrace()) : Arrays.toString(t.getStackTrace()).substring(0, 1888));
            System.out.println("-----" + messageText);
            monitor(messageText);
        } catch (Exception e) {
            addError("日志报警异常,异常原因:{}", e);
        }
    }
    protected abstract void monitor(String messageText);
}

append()方法中的 LoggingEvent参数对应的数据内容 如下图:
在这里插入图片描述

我们首先定义一个WechatAlarm类,该类实现自 AlarmService类,并实现了 alarm()方法异步组装参数并调用发送消息的接口:
具体实现代码:

public class WechatAlarm implements WxAlarmService {
    private ExecutorService executorService;
    private String webHookUrl;

    public WechatAlarm() {
    }

    public WechatAlarm(String webHookUrl) {
        this.webHookUrl = webHookUrl;
    }

    private Message buildParam(String messageText) {

        Message message = new Message();
        //发送文本消息
        message.setMsgtype(MessageTypeEnum.text);
        TextMessage textMessage = new TextMessage();
        textMessage.setContent(messageText);
        //是否@所有人
//        textMessage.setMentioned_list(Arrays.asList("@all"));
        JSONObject text = JSONObject.parseObject(JSONObject.toJSONString(textMessage));
        message.setText(text);
        return message;
    }

    @Override
    public boolean alarm(String messageText) {

        ThreadPoolExecutorUtils.getCacheExecutorService().execute(() ->
        {
            try {
                // 在线程池中调用http接口发送微信消息
                Message message = buildParam(messageText);
                MessageManager.sendMessage(message, webHookUrl);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        return true;
    }
}

日志打印格式的编写如下图:(也可以在logback的配置文件中配置)
在这里插入图片描述

此时我们需要读取spring 中的配置文件,会有一个问题:
logback.xml的配置加载优先于spring的 .yml .properties 相关配置文件,如果我们需要读取spring配置的相关变量配置信息就会加载不到。
所以我们需要通过logback的配置文件中配置来获取spring里配置的相关变量信息。如下图:

[外链图片转存失败,源站可能有防盗在这里插入!链机制,建描述]议将图片上https://传(imblog.csdnimg.cn/c9f95fa9af40ab9ae9d530y8Zj6d9f9934.png?x-oss-process=image/watermark,type_1F3LXplbmhlaQ,shadow_80,text_Q1NETiBAMjBmZW7=,size_20,color_FFFFFF,t_70,g_se,x_164(获取当前环境和当前服务名)]
加载对应的参数项,如下图:在这里插入图片描述
log.error()方法的打印方式 如下:
log.error("异常描述的信息:大括弧 ", 异常的简要信息, 整个异常);
log.error("数组越界异常:{} ", e.getMessage(), e);

如何添加企业微信机器人,如下图:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值