JAVA基于Slack實(shí)現(xiàn)異常日志報(bào)警詳解
一、功能介紹
在我們?nèi)粘i_(kāi)發(fā)中,如果系統(tǒng)在線上環(huán)境上,發(fā)生異常,開(kāi)發(fā)人員不能及時(shí)知曉來(lái)修復(fù),可能會(huì)造成重大的損失,因此后端服務(wù)中加入異常報(bào)警的功能是十分必要的,而開(kāi)發(fā)一個(gè)功能全面的異常報(bào)警服務(wù),可能會(huì)花費(fèi)較長(zhǎng)的周期,今天給大家?guī)?lái)一種基于Slack實(shí)現(xiàn)異常日志報(bào)警的辦法。
實(shí)現(xiàn)邏輯:一般情況下,代碼中都會(huì)對(duì)會(huì)出現(xiàn)異常的地方,進(jìn)行處理,最基本的就是打印日志,本文將實(shí)現(xiàn)在打印日志時(shí),同時(shí)將異常信息發(fā)送到Slack頻道中,開(kāi)發(fā)或運(yùn)維人員創(chuàng)建Slack賬號(hào),加入頻道,便可實(shí)時(shí)收到異常信息的告警。
二、Slack介紹
Slack 它是一種基于Web的實(shí)時(shí)通信工具,可作為臺(tái)式機(jī)/筆記本電腦、移動(dòng)設(shè)備的單個(gè)應(yīng)用程序以及Web應(yīng)用程序使用?;旧?,它是您的私人聊天和協(xié)作室。對(duì)于許多公司而言,它已取代電子郵件/私人論壇/聊天室成為主要的內(nèi)部基于文本的溝通渠道。
可以理解為它是聊天群組 + 大規(guī)模工具集成 + 文件整合 + 統(tǒng)一搜索。截至2014年底,Slack 已經(jīng)整合了電子郵件、短信、Google Drives、Twitter、Trello、Asana、GitHub 等 65 種工具和服務(wù),可以把各種碎片化的企業(yè)溝通和協(xié)作集中到一起。幾個(gè)重要的概念:
工作區(qū):相當(dāng)去工作空間,用戶(hù)可以加入或者創(chuàng)建不同的工作區(qū),很多時(shí)候,工作區(qū)的名稱(chēng)和URL將是公司名稱(chēng)。
頻道:頻道可以區(qū)分為不同的團(tuán)隊(duì)或者主題,也可以理解成相當(dāng)于微信,頻道中的成員共享頻道中的信息。
三、前期準(zhǔn)備
slack配置
- 創(chuàng)建賬號(hào),登錄,可以使用app或者用瀏覽器登錄網(wǎng)頁(yè)版
- 創(chuàng)建自己的工作區(qū),還可以邀請(qǐng)其他人加入工作區(qū)。
- 創(chuàng)建頻道,邀請(qǐng)同事加入,此時(shí)可以往頻道中發(fā)信息,加入頻道的人都可以看到信息
工作區(qū)添加應(yīng)用Incoming WebHook,選擇頻道,保存Webhook URL,后面將通過(guò)Webhook實(shí)現(xiàn)程序往頻道中發(fā)消息。
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>
四、具體實(shí)現(xiàn)
1.實(shí)現(xiàn)Slack發(fā)送消息
SlackUtil 給Slack發(fā)消息工具類(lèi)
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 ="運(yùn)維機(jī)器人"; private static int MAX_RETRY =3; /** * 線程池 拋棄策略DiscardPolicy:這種策略,會(huì)默默的把新來(lái)的這個(gè)任務(wù)給丟棄;不會(huì)得到通知 */ 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異步發(fā)消息,保證不能影響到主功能 * @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發(fā)消息失敗,會(huì)最多嘗試發(fā)三次,三次都失敗,會(huì)打印異常信息 * @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發(fā)起請(qǐng)求通過(guò)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測(cè)試
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); } }
發(fā)送成功,可以在頻道中看到信息
2.重寫(xiě)打印日志類(lèi)
常見(jiàn)異常打日志處理
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); } } }
重寫(xiě)封裝打印日志的方法
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 發(fā)生異常!\n環(huán)境 :{0}\n信息 :{1}\n詳情 :{2}"; private static String WARNING_ALARM_FORMAT = "WARNING 發(fā)生告警!\n環(huán)境 :{0}\n信息 :{1}"; private static String SEVERE_ALARM_FORMAT = "SEVERE 發(fā)生告警!\n環(huán)境 :{0}\n信息 :{1}"; private static String LOG_ALARM_FORMAT = "LOG 發(fā)生告警!\n環(huán)境 :{0}\n信息 :{1}"; private static String USER_BEHAVIOR_FORMAT = "CUSTOMER \n環(huá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; } }
測(cè)試日志類(lèi)
定義日志類(lèi)時(shí)發(fā)生改變,調(diào)用出的代碼無(wú)需更改,以較小的代價(jià),集成異常報(bào)警功能
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); } } }
測(cè)試結(jié)果,頻道中出現(xiàn)打印的異常信息,方便開(kāi)發(fā)運(yùn)維人員定位
五、優(yōu)化擴(kuò)展想法
- 可以不僅僅實(shí)現(xiàn)打印異常日志,也可以打印用戶(hù)的一些關(guān)鍵行為,如充值等,頻道可以設(shè)置多個(gè),發(fā)送不同主題的消息
- 可以?xún)?yōu)化線程池
- 如果開(kāi)發(fā)人員不能及時(shí)查看slack,也可以集成電子郵件,Slack中可以添加mailclark應(yīng)用(單獨(dú)收費(fèi)),經(jīng)過(guò)配置后,發(fā)動(dòng)頻道中的信息,可以自動(dòng)郵件發(fā)送給任意郵箱,接受者無(wú)需創(chuàng)建slack賬號(hào)。具體配置可參考鏈接。
其他代碼
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; } }
以上就是JAVA基于Slack實(shí)現(xiàn)異常日志報(bào)警詳解的詳細(xì)內(nèi)容,更多關(guān)于JAVA Slack異常日志報(bào)警的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java實(shí)現(xiàn)水仙花數(shù)的計(jì)算
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)水仙花數(shù)的計(jì)算,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08簡(jiǎn)單談?wù)凷truts動(dòng)態(tài)表單(DynamicForm)
下面小編就為大家?guī)?lái)一篇簡(jiǎn)單談?wù)凷truts動(dòng)態(tài)表單(DynamicForm)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08java后臺(tái)實(shí)現(xiàn)js關(guān)閉本頁(yè)面,父頁(yè)面指定跳轉(zhuǎn)或刷新操作
這篇文章主要介紹了java后臺(tái)實(shí)現(xiàn)js關(guān)閉本頁(yè)面,父頁(yè)面指定跳轉(zhuǎn)或刷新操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11Java中的System.arraycopy()淺復(fù)制方法詳解
這篇文章主要介紹了Java中的System.arraycopy()淺復(fù)制方法詳解,Java數(shù)組的復(fù)制操作可以分為深度復(fù)制和淺度復(fù)制,簡(jiǎn)單來(lái)說(shuō)深度復(fù)制,可以將對(duì)象的值和對(duì)象的內(nèi)容復(fù)制;淺復(fù)制是指對(duì)對(duì)象引用的復(fù)制,需要的朋友可以參考下2023-11-11詳解application.properties和application.yml文件的區(qū)別
這篇文章主要介紹了詳解application.properties和application.yml文件的區(qū)別,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-01-01maven profile動(dòng)態(tài)選擇配置文件詳解
這篇文章主要介紹了maven profile動(dòng)態(tài)選擇配置文件詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11