当前位置:

首页 > 编程开发 > JAVA基于Slack怎么实现异常日志报警

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

一、功能介绍实现逻辑:一般情况下,代码中都会对会出现异常的地方,进行处理,最基本的就是打印日志,本文将实现在打印日志时,同时将异常信息发送到Slack频道中,开发或运维人员创建Slack账号,加入频道,便可实时收到异常信息的告警。二、Slack介绍Slack它是一种基于Web的实时通信工具,可作为台式机/笔记本电脑、移动设备的单个应用程序以及Web应用程序使用。基本上,它是您的私人聊天和协作室。对于许多公司而言,它已取代电子邮件/私人论坛/聊天室成为主要的内部基于文本的沟通渠道。可以理解为它是聊天群组+大

本文目录

      一、功能介绍

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

      二、Slack介绍

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

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

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

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

      三、前期准备

      slack配置

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

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

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

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

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

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

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

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

      pom.xml

      
          
              org.apache.httpcomponents
              httpclient
              4.5.2
          
          
              com.alibaba
              fastjson
              1.2.83
          
          
              commons-configuration
              commons-configuration
              1.10
          
          
              junit
              junit
              4.10
              test
          
      

      四、具体实现

      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(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);
          }
      }

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

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

      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 loggers = new ConcurrentHashMap();
         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);
              }
          }
      }

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

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

      五、优化扩展想法

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

      • 可以优化线程池

      • 如果开发人员不能及时查看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;
          }
      }
      本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
      作者最新文章
      编程开发 Java
      相关文章 更多
      Java单元测试入门:使用JUnit 5编写第一个测试用例
      Java单元测试入门:使用JUnit 5编写第一个测试用例

      面向初学者的JUnit 5实用入门指南。从测试概念、Maven依赖配置,到手把手编写首个单元测试用例及断言验证,助你掌握Java单元测试的核心流程。

      Java变量未初始化报错解析:局部变量与成员变量默认值差异
      Java变量未初始化报错解析:局部变量与成员变量默认值差异

      深入解析Java中局部变量未初始化编译报错与成员变量自动赋予默认值的机制差异,涵盖JVM内存分配、编译期确定性赋值检查与实际排查修复方案。

      Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat
      Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat

      详细讲解在Java中获取当前系统时间并进行格式化输出的两种核心方案:基于Java 8全新日期时间API的DateTimeFormatter与传统遗留的SimpleDateFormat,包含完整代码示例、线程安全机制对比及生产选型建议。

      Java中final与static final常量的定义及初始化规则
      Java中final与static final常量的定义及初始化规则

      深入解析Java中final变量与static final常量的定义、内存语义、初始化时机及合法赋值路径,涵盖编译期常量优化与实战避坑指南。

      Java抽象方法与接口方法的区别及选型建议
      Java抽象方法与接口方法的区别及选型建议

      深入对比Java中抽象类抽象方法与接口方法的语法演进、设计意图及运行期差异,并提供清晰的架构选型决策流程与实战范例。

      Java实现HTTP请求与JSON解析的完整代码示例
      Java实现HTTP请求与JSON解析的完整代码示例

      本文提供Java标准库HttpClient发起HTTP GET/POST请求并结合Jackson进行JSON数据反序列化与序列化的完整代码示例与工程实践指南。

      Java递归出现StackOverflowError的原因分析及栈空间优化方案
      Java递归出现StackOverflowError的原因分析及栈空间优化方案

      详细分析Java递归调用出现StackOverflowError的原因,结合代码说明线程栈、栈帧和递归深度之间的关系,并介绍终止条件排查、迭代改写、减少栈帧开销以及通过-Xss调整JVM栈空间的方法。

      Java空指针异常(NPE)的代码排查与修复实例
      Java空指针异常(NPE)的代码排查与修复实例

      针对Java中常见的NullPointerException(NPE),本文从堆栈日志定位、典型代码触发场景、具体修复代码对比到防御体系进行系统讲解,提供实用且易上手的排查与修复方案。

      selenium上传文件教程本地文件路径设置方法
      selenium上传文件教程本地文件路径设置方法

      深入解析Selenium处理文件上传的技术细节,涵盖标准HTML控件的路径直接注入法与非标准控件的系统级模拟方案,提供Windows与Linux环境下的路径设置最佳实践及常见异常排查。

      大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景
      大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景

      针对大数据工程场景,系统对比Java、Scala与Python在执行性能、生态结合、开发效率和维护成本上的核心差异,给出清晰的架构选型依据与落地方案。

      查看更多
      精品专题 更多
      装机必备
      装机必备

      正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

      Windows
      Windows

      正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

      PDF教程
      PDF教程

      正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

      Mac软件 更多
      讯飞智作 网页版
      讯飞智作 网页版
      Windows/ macOS/ Linux

      讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

      火山引擎 网页版
      火山引擎 网页版
      Windows/ macOS/ Linux

      火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

      Aperty macOS版
      Aperty macOS版
      macOS

      Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

      WINDOWS 更多
      讯飞智作 网页版
      讯飞智作 网页版
      Windows/ macOS/ Linux

      讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

      火山引擎 网页版
      火山引擎 网页版
      Windows/ macOS/ Linux

      火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

      PixTV 网页版
      PixTV 网页版
      Windows

      PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。