JAVA implementa alarme de log anormal com base no Slack

1. Introdução à função

Em nosso desenvolvimento diário, se houver uma anormalidade no ambiente online do sistema, o desenvolvedor não pode saber a tempo de repará-lo, o que pode causar grandes prejuízos, portanto, é muito necessário adicionar a função de alarme anormal ao back -fim de serviço, e para desenvolver uma função Um serviço abrangente de alarme anormal pode levar um longo período de tempo.Hoje, trarei um método para realizar alarme de log anormal baseado no Slack.

Lógica de implementação: Em circunstâncias normais, o código lidará com o local anormal. O mais básico é imprimir o log. Este artigo perceberá que quando o log for impresso, a informação anormal será enviada ao canal Slack ao mesmo tempo Pessoal de desenvolvimento ou operação e manutenção Crie uma conta no Slack, participe de um canal e receba alertas em tempo real sobre informações anormais.

2. Introdução ao Slack

Slack É uma ferramenta de comunicação em tempo real baseada na web disponível como um único aplicativo para desktop/laptop, dispositivos móveis, bem como um aplicativo da web. Basicamente, é sua sala privada de bate-papo e colaboração. Para muitas empresas, ele substituiu o e-mail/fóruns privados/salas de bate-papo como o principal canal de comunicação interno baseado em texto. Pode-se entender que é um grupo de bate-papo + integração de ferramentas em larga escala + integração de arquivos + pesquisa unificada . No final de 2014, o Slack integrou 65 ferramentas e serviços, como e-mail, SMS, Google Drives, Twitter, Trello, Asana, GitHub, etc., que podem reunir várias comunicações e colaboração corporativas fragmentadas. Vários conceitos importantes:

Workspaces : Em vez de ir para workspaces, os usuários podem ingressar ou criar diferentes workspaces, muitas vezes o nome e a URL do workspace será o nome da empresa.

Canais : Os canais podem ser divididos em diferentes equipes ou tópicos, e também podem ser entendidos como equivalentes ao WeChat, onde os membros de um canal compartilham informações no canal.

3. Preparação preliminar

configuração de folga

  1. Crie uma conta, faça login, você pode usar o aplicativo ou fazer login na versão web com um navegador
  2. Crie seu próprio espaço de trabalho e convide outras pessoas para seu espaço de trabalho.
  3. Crie um canal, convide colegas para participar, você pode enviar informações para o canal neste momento e todos que ingressarem no canal poderão ver as informações
  4. Adicione o aplicativo Incoming WebHook à área de trabalho , selecione o canal, salve a URL do Webhook e envie mensagens para o canal por meio do programa de implementação do Webhook.imagem.png

imagem.png

imagem.png

imagem.png

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>
复制代码

Em quarto lugar, a implementação específica

1. Implemente o Slack para enviar mensagens

Classe de ferramenta de mensagens SlackUtil para 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);
            }
        }
    }


}
复制代码

Faça uma solicitação ao webhook por meio do 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;
    }

}
复制代码

Teste 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);
    }
}
复制代码

Enviado com sucesso, você pode ver as informações no canal

imagem.png

2. Reescreva a classe de log de impressão

Processamento de log de exceção comum

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);
        }
    }
}
复制代码

Substituir o método de encapsulamento do log de impressão

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;
   }
}
复制代码

classe de registro de teste

Quando a classe de log é alterada, o código chamado não precisa ser alterado e a função de alarme anormal é integrada a um pequeno custo.

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);
        }
    }
}
复制代码

Resultados de testes, informações anormais impressas no canal, o que é conveniente para o pessoal de desenvolvimento, operação e manutenção localizar

imagem.png

Quinto, otimize a ideia de expansão

  1. Ele pode não apenas imprimir logs anormais, mas também imprimir alguns comportamentos-chave dos usuários, como recarga, etc. Vários canais podem ser configurados para enviar mensagens com diferentes tópicos
  2. O pool de threads pode ser otimizado
  3. Se os desenvolvedores não conseguirem visualizar o slack a tempo, eles também podem integrar o e-mail. O aplicativo Mailclark (cobrado separadamente) pode ser adicionado ao Slack. Após a configuração, as informações no canal de ativação podem ser enviadas automaticamente para qualquer caixa de correio, e o destinatário não precisa criar uma conta no slack. Para configuração específica, consulte o link .

Todos são bem vindos para dar sugestões de otimização, e trocar muito!

outro código

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(nameSpecific, 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;
    }
}
复制代码

Acho que você gosta

Origin juejin.im/post/7136858841756467230
Recomendado
Clasificación