首页 > 文章列表 > 使用Java语言在Slack平台上实现异常日志报警的方法介绍

使用Java语言在Slack平台上实现异常日志报警的方法介绍

java Slack
103 2023-04-26

JAVA基于Slack怎么实现异常日志报警

    一、功能介绍

    实现逻辑:一般情况下,代码中都会对会出现异常的地方,进行处理,最基本的就是打印日志,本文将实现在打印日志时,同时将异常信息发送到Slack频道中,开发或运维人员创建Slack账号,加入频道,便可实时收到异常信息的告警。

    二、Slack介绍

    Slack 它是一种基于Web的实时通信工具,可作为台式机/笔记本电脑、移动设备的单个应用程序以及Web应用程序使用。基本上,它是您的私人聊天和协作室。对于许多公司而言,它已取代电子邮件/私人论坛/聊天室成为主要的内部基于文本的沟通渠道。

    可以理解为它是聊天群组 + 大规模工具集成 + 文件整合 + 统一搜索。截至2014年底,Slack 已经整合了电子邮件、短信、Google Drives、Twitter、Trello、Asana、GitHub 等 65 种工具和服务,可以把各种碎片化的企业沟通和协作集中到一起。几个重要的概念:

    工作区:相当去工作空间,用户可以加入或者创建不同的工作区,很多时候,工作区的名称和URL将是公司名称。

    频道:频道可以区分为不同的团队或者主题,也可以理解成相当于微信,频道中的成员共享频道中的信息。

    三、前期准备

    slack配置

    • 创建账号,登录,可以使用app或者用浏览器登录网页版

    • 创建自己的工作区,还可以邀请其他人加入工作区。

    • 创建频道,邀请同事加入,此时可以往频道中发信息,加入频道的人都可以看到信息

    工作区添加应用Incoming WebHook,选择频道,保存Webhook URL,后面将通过Webhook实现程序往频道中发消息。

    pom.xml

    <dependencies>
    
        <dependency>
    
            <groupId>org.apache.httpcomponents</groupId>
    
            <artifactId>httpclient</artifactId>
    
            <version>4.5.2</version>
    
        </dependency>
    
        <dependency>
    
            <groupId>com.alibaba</groupId>
    
            <artifactId>fastjson</artifactId>
    
            <version>1.2.83</version>
    
        </dependency>
    
        <dependency>
    
            <groupId>commons-configuration</groupId>
    
            <artifactId>commons-configuration</artifactId>
    
            <version>1.10</version>
    
        </dependency>
    
        <dependency>
    
            <groupId>junit</groupId>
    
            <artifactId>junit</artifactId>
    
            <version>4.10</version>
    
            <scope>test</scope>
    
        </dependency>
    
    </dependencies>

    四、具体实现

    1.实现Slack发送消息

    SlackUtil 给Slack发消息工具类
    package com.yy.operation;
    
    import com.yy.common.CommonThreadFactory;
    
    import com.yy.common.ConnUtil;
    
    import org.apache.commons.lang.StringUtils;
    
    import java.text.MessageFormat;
    
    import java.text.SimpleDateFormat;
    
    import java.util.concurrent.*;
    
    import java.util.logging.Level;
    
    import java.util.logging.Logger;
    
    /**
    
     * @author :Max
    
     * @date :Created in 2022/8/26 下午12:54
    
     * @description:
    
     */
    
    public class SlackUtil {
    
        private static final Logger logger = Logger.getLogger(SlackUtil.class.getCanonicalName());
    
        private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        private static final String SEND_USER_NAME ="运维机器人";
    
        private static int MAX_RETRY =3;
    
        /**
    
         * 线程池 抛弃策略DiscardPolicy:这种策略,会默默的把新来的这个任务给丢弃;不会得到通知
    
          */
    
        private static ExecutorService executor = new ThreadPoolExecutor(10,30,60,TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(200),new CommonThreadFactory("Slack"), new ThreadPoolExecutor.DiscardPolicy());
    
        private static String MSG_FORMAT ="payload='{'"channel": "{0}", "username": "{1}", "text": "{2}", "icon_emoji": ":ghost:"'}'" ;
    
        /**
    
         * 保存的Webhook URL ,需要初始化
    
         */
    
        private static String WEBHOOK_URL ;
    
        private static boolean SLACK_ABLE;
    
        public static void setSlackConfig(String webhookUrl){
    
            WEBHOOK_URL = webhookUrl;
    
            SLACK_ABLE = true;
    
        }
    
        /**
    
         * slack异步发消息,保证不能影响到主功能
    
          * @param channel
    
         * @param msg
    
         */
    
        public static void send(final String channel, final String msg){
    
            if(!SLACK_ABLE){
    
                return;
    
            }
    
            if(StringUtils.isBlank(msg)){
    
                return;
    
            }
    
            executor.submit(new Runnable() {
    
                @Override
    
                public void run() {
    
                    try {
    
                        SlackUtil.send(channel,sdf.format(System.currentTimeMillis())+"   "+msg,MAX_RETRY);
    
                    } catch (Exception e) {
    
                        logger.log(Level.SEVERE, e.getMessage(), e);
    
                    }
    
                }
    
            });
    
        }
    
        /**
    
         * 如果slask发消息失败,会最多尝试发三次,三次都失败,会打印异常信息
    
          * @param channel
    
         * @param msg
    
         * @param retry
    
         * @throws Exception
    
         */
    
        public static void send(String channel, String msg, int retry) throws Exception {
    
            if(msg.indexOf(""")>=0 ||msg.indexOf("{")>=0 ||msg.indexOf("}")>=0){
    
                msg =msg.replace(""","'").replace("{","[").replace("}","]");
    
            }
    
            String payload = MessageFormat.format(MSG_FORMAT, channel,SEND_USER_NAME,msg);
    
            String result = ConnUtil.getContentByPostWithUrlencode(WEBHOOK_URL,payload);
    
            logger.info("result:"+result);
    
            if(StringUtils.isEmpty(result) ||!result.startsWith("ok")){
    
                --retry;
    
                if(retry>0){
    
                    try {
    
                        TimeUnit.SECONDS.sleep(retry*5);
    
                    } catch (InterruptedException e) {
    
                        e.printStackTrace();
    
                    }
    
                    send(channel,msg,retry);
    
                }else{
    
                    throw new Exception("Fail to send slack:"+result+"\nmsg:"+msg);
    
                }
    
            }
    
        }
    
    }
    向 webhook发起请求通过Urlencode
    package com.yy.common;
    
    import org.apache.http.HttpEntity;
    
    import org.apache.http.HttpResponse;
    
    import org.apache.http.client.HttpClient;
    
    import org.apache.http.client.methods.HttpPost;
    
    import org.apache.http.entity.StringEntity;
    
    import org.apache.http.impl.client.HttpClientBuilder;
    
    import java.io.BufferedReader;
    
    import java.io.InputStream;
    
    import java.io.InputStreamReader;
    
    import java.util.logging.Level;
    
    import java.util.logging.Logger;
    
    /**
    
     * @author :Max
    
     * @date :Created in 2022/8/26 下午1:44
    
     * @description:
    
     */
    
    public class ConnUtil {
    
        private static final Logger logger = Logger.getLogger(ConnUtil.class.getCanonicalName());
    
        public static String getContentByPostWithUrlencode(String url,String msg){
    
            StringEntity entity = new StringEntity(msg, "UTF-8");
    
            entity.setContentEncoding("UTF-8");
    
            entity.setContentType(" application/x-www-form-urlencoded");
    
            HttpClient httpClient = HttpClientBuilder.create().build();
    
            HttpPost request = new HttpPost(url);
    
            request.setEntity(entity);
    
            HttpResponse response = null;
    
            try {
    
                response = httpClient.execute(request);
    
                HttpEntity responseEntity = response.getEntity();
    
                if (responseEntity != null) {
    
                    InputStream instream = responseEntity.getContent();
    
                    BufferedReader reader = new BufferedReader(new InputStreamReader(instream));
    
                    StringBuffer contents = new StringBuffer();
    
                    String line = null;
    
                    while ((line = reader.readLine()) != null) {
    
                        contents.append(line);
    
                        contents.append("\n");
    
                    }
    
                    return contents.toString();
    
                }
    
            } catch (Exception ex) {
    
                logger.log(Level.SEVERE, ex.getMessage(), ex);
    
            }
    
            return null;
    
        }
    
    }
    SlackUtil测试
    package com.yy.test;
    
    import com.yy.common.SlackChannelEnum;
    
    import com.yy.operation.SlackUtil;
    
    import org.junit.Assert;
    
    import org.junit.Test;
    
    import java.util.concurrent.TimeUnit;
    
    /**
    
     * @author :Max
    
     * @date :Created in 2022/8/28 下午2:37
    
     * @description:
    
     */
    
    public class SlackTest {
    
        static {
    
            SlackUtil.setSlackConfig("https://hooks.slack.com/services/*******");
    
        }
    
        @Test
    
        public void test(){
    
            SlackUtil.send(SlackChannelEnum.EXCEPTION.channel,"test ~");
    
            try {
    
                TimeUnit.MINUTES.sleep(1);
    
            } catch (InterruptedException e) {
    
                e.printStackTrace();
    
            }
    
            Assert.assertTrue(true);
    
        }
    
    }

    发送成功,可以在频道中看到信息

    2.重写打印日志类

    常见异常打日志处理
    public class LoggerTest {
    
        private static final Logger logger = Logger.getLogger(LoggerTest.class.getCanonicalName());
    
        @Test
    
        public void test() {
    
            try {
    
                int i = 1 / 0;
    
            } catch (Exception e) {
    
                logger.log(Level.SEVERE, e.getMessage(), e);
    
            }
    
        }
    
    }
    重写封装打印日志的方法
    package com.yy.operation;
    
    import com.yy.common.SlackChannelEnum;
    
    import org.apache.commons.lang.StringUtils;
    
    import java.io.PrintWriter;
    
    import java.io.StringWriter;
    
    import java.net.Inet4Address;
    
    import java.net.InetAddress;
    
    import java.text.MessageFormat;
    
    import java.util.logging.Level;
    
    import java.util.logging.LogRecord;
    
    import java.util.logging.Logger;
    
    /**
    
     * @author  Max
    
     * @date :Created in 2022/8/4 下午5:14
    
     * @description:
    
     */
    
    public class CommonLogger {
    
        private Logger logger;
    
        private CommonLogger(String className) {
    
            logger = Logger.getLogger(className);
    
        }
    
        private static String SERVER;
    
        private static String EXCEPTION_ALARM_FORMAT = "EXCEPTION 发生异常!\n环境 :{0}\n信息 :{1}\n详情 :{2}";
    
        private static String WARNING_ALARM_FORMAT = "WARNING 发生告警!\n环境 :{0}\n信息 :{1}";
    
        private static String SEVERE_ALARM_FORMAT = "SEVERE 发生告警!\n环境 :{0}\n信息 :{1}";
    
        private static String LOG_ALARM_FORMAT = "LOG 发生告警!\n环境 :{0}\n信息 :{1}";
    
        private static String USER_BEHAVIOR_FORMAT = "CUSTOMER \n环境 :{0}\n信息 :{1}";
    
        static {
    
            try{
    
                InetAddress ip4 = Inet4Address.getLocalHost();
    
                SERVER = ip4.getHostAddress();
    
            }catch (Exception e){
    
                SERVER ="undefined server";
    
            }
    
        }
    
        public static CommonLogger getLogger(String name) {
    
            return new CommonLogger(name);
    
        }
    
        /**
    
         * Print exception information, send slack
    
         *
    
         * @param level
    
         * @param msg
    
         * @param e
    
         */
    
        public void log(Level level, String msg, Throwable e) {
    
            if(StringUtils.isBlank(msg)){
    
                return;
    
            }
    
            msg =dolog(level,msg, e);
    
            msg = MessageFormat.format(EXCEPTION_ALARM_FORMAT, SERVER, formatMsg(msg), getErrmessage(e));
    
            SlackUtil.send(SlackChannelEnum.EXCEPTION.channel, msg);
    
        }
    
        /**
    
         * Print user behavior information, send slack
    
         *
    
         * @param msg
    
         */
    
        public void userBehaviorInfo(String msg) {
    
            if(StringUtils.isBlank(msg)){
    
                return;
    
            }
    
            msg =dolog(Level.INFO,msg);
    
            msg = MessageFormat.format(USER_BEHAVIOR_FORMAT, SERVER, formatMsg(msg));
    
            SlackUtil.send(SlackChannelEnum.EXCEPTION.channel, msg);
    
        }
    
        public String formatMsg(String msg){
    
            StringBuilder source =new StringBuilder(logger.getName());
    
            msg=transferMsgSource(source,msg);
    
            return source.toString()+" "+msg;
    
        }
    
        /**
    
         * Print warning severe information, send slack
    
         *
    
         * @param msg
    
         */
    
        public void severe(String msg) {
    
            if(StringUtils.isBlank(msg)){
    
                return;
    
            }
    
            msg = dolog(Level.SEVERE,msg);
    
            msg = MessageFormat.format(SEVERE_ALARM_FORMAT, SERVER, formatMsg(msg));
    
            SlackUtil.send(SlackChannelEnum.EXCEPTION.channel, msg);
    
        }
    
        /**
    
         * Print warning severe information, send slack
    
         *
    
         * @param msg
    
         */
    
        public void warning(String msg) { 
    
            if(StringUtils.isBlank(msg)){
    
                return;
    
             }
    
            msg = dolog(Level.WARNING,msg);
    
            msg = MessageFormat.format(WARNING_ALARM_FORMAT, SERVER, formatMsg(msg));
    
            SlackUtil.send(SlackChannelEnum.EXCEPTION.channel, msg);
    
        }
    
        /**
    
         * Print warning log information, send slack
    
         *
    
         * @param msg
    
         */
    
        public void log(Level severe, String msg) {
    
            if(StringUtils.isBlank(msg)){
    
                return;
    
            }
    
            msg =dolog(severe,msg);
    
            msg = MessageFormat.format(LOG_ALARM_FORMAT, SERVER, formatMsg(msg));
    
            SlackUtil.send(SlackChannelEnum.EXCEPTION.channel, msg);
    
        }
    
        public static String getErrmessage(Throwable t) {
    
            return getThrowable(t);
    
        }
    
        public void info(String msg) {
    
            dolog(Level.INFO,msg);
    
        }
    
        public void fine(String msg) {
    
            logger.fine(msg);
    
        }
    
        public void setLevel(Level level) {
    
            logger.setLevel(level);
    
        }
    
        public String dolog(Level level, String msg) {
    
            return dolog(level,msg,null);
    
        }
    
        /**
    
         *
    
          * @param level
    
         * @param msg
    
         * @param thrown
    
         * @return msg="["+currentThread.getName()+"] "+a.getMethodName()+" "+msg;
    
         */
    
        public String dolog(Level level, String msg, Throwable thrown) {
    
            LogRecord lr = new LogRecord(level, msg);
    
            lr.setLevel(level);
    
            if(thrown!=null){
    
                lr.setThrown(thrown);
    
            }
    
            Thread currentThread = Thread.currentThread();
    
            StackTraceElement[] temp=currentThread.getStackTrace();
    
            StackTraceElement a=(StackTraceElement)temp[3];
    
            lr.setThreadID((int) currentThread.getId());
    
            lr.setSourceClassName(logger.getName());
    
            lr.setSourceMethodName(a.getMethodName());
    
            lr.setLoggerName(logger.getName());
    
            logger.log(lr);
    
            return "["+currentThread.getName()+"] "+a.getMethodName()+" "+msg;
    
        }
    
        public static String getThrowable(Throwable e) {
    
            String throwable = "";
    
            if (e != null) {
    
                StringWriter sw = new StringWriter();
    
                PrintWriter pw = new PrintWriter(sw);
    
                pw.println();
    
                e.printStackTrace(pw);
    
                pw.close();
    
                throwable = sw.toString();
    
            }
    
            return throwable;
    
        }
    
        public static String transferMsgSource(StringBuilder source,String msg){
    
            if(msg.indexOf(" ")>0){
    
                String threadName = msg.substring(0,msg.indexOf(" "))+ " ";
    
                msg=msg.substring(threadName.length());
    
                source.insert(0,threadName);
    
                if(msg.indexOf(" ")>0) {
    
                    String method = msg.substring(0, msg.indexOf(" "));
    
                    source.append( "." + method);
    
                    msg = msg.substring(method.length()+1);
    
                }
    
            }
    
            return msg;
    
        }
    
    }
    package com.yy.operation;
    
    import java.text.MessageFormat;
    
    import java.util.concurrent.ConcurrentHashMap;
    
    import java.util.logging.Level;
    
    import java.util.logging.Logger;
    
    public class LoggerUtil {
    
       private static Logger curLogger = Logger.getLogger(LoggerUtil.class.getCanonicalName());
    
       private static ConcurrentHashMap<String, CommonLogger> loggers = new ConcurrentHashMap<String, CommonLogger>();
    
       public static CommonLogger getLogger(Class<?> clazz) {
    
          String className = clazz.getCanonicalName();
    
          CommonLogger logger = loggers.get(className);
    
          if (logger == null) {
    
             logger = CommonLogger.getLogger(className);
    
             curLogger.fine(MessageFormat.format("Register logger for {0}", className));
    
             loggers.put(className, logger);
    
          }
    
          return logger;
    
       }
    
    }
    测试日志类

    定义日志类时发生改变,调用出的代码无需更改,以较小的代价,集成异常报警功能

    public class LoggerTest {
    
        private static final Logger logger = Logger.getLogger(LoggerTest.class.getCanonicalName());
    
        @Test
    
        public void test() {
    
            try {
    
                int i = 1 / 0;
    
            } catch (Exception e) {
    
                logger.log(Level.SEVERE, e.getMessage(), e);
    
            }
    
        }
    
    }

    测试结果,频道中出现打印的异常信息,方便开发运维人员定位

    五、优化扩展想法

    • 可以不仅仅实现打印异常日志,也可以打印用户的一些关键行为,如充值等,频道可以设置多个,发送不同主题的消息

    • 可以优化线程池

    • 如果开发人员不能及时查看slack,也可以集成电子邮件,Slack中可以添加mailclark应用(单独收费),经过配置后,发动频道中的信息,可以自动邮件发送给任意邮箱,接受者无需创建slack账号。具体配置可参考链接。

    其他代码

    package com.yy.common;
    
    import java.util.concurrent.ThreadFactory;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
    
     * @author :Max
    
     * @date :Created in 2022/8/26 下午1:51
    
     * @description:
    
     */
    
    public class CommonThreadFactory implements ThreadFactory {
    
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
    
        private final ThreadGroup group;
    
        private final AtomicInteger threadNumber = new AtomicInteger(1);
    
        private final String threadNamePrefix;
    
        private final String nameSpecific;
    
        private final boolean isDaemon;
    
        public CommonThreadFactory(String nameSpecific) {
    
            this(nameSpecifihttps://juejin.cn/post/7136858841756467230#heading-4c, false);
    
        }
    
        public CommonThreadFactory(String nameSpecific, boolean isDaemon) {
    
            SecurityManager s = System.getSecurityManager();
    
            this.group = (s != null) ? s.getThreadGroup() :
    
                    Thread.currentThread().getThreadGroup();
    
            this.threadNamePrefix = "eg-pool-" + poolNumber.getAndIncrement() + "-thread";
    
            this.nameSpecific = nameSpecific;
    
            this.isDaemon = isDaemon;
    
        }
    
        @Override
    
        public Thread newThread(Runnable r) {
    
            Thread t = new Thread(group, r, String.format("%s-%d-%s",
    
                    this.threadNamePrefix, threadNumber.getAndIncrement(), this.nameSpecific), 0);
    
            t.setDaemon(isDaemon);
    
            t.setPriority(Thread.NORM_PRIORITY);
    
            return t;
    
        }
    
    }
    public enum SlackChannelEnum {
    
        EXCEPTION("#test-example");
    
        public String channel;
    
        SlackChannelEnum(String channel) {
    
            this.channel = channel;
    
        }
    
    }