基于自定义Logback的Appender实现异常告警

通过自定义实现Appender,实现日志的实时监控,并通过邮件、飞书机器人等方式提醒。
基础来源: https://juejin.cn/post/7222556620977635389
日志格式化代码来源: plumelog 的 LiteAppender

自定义实现Appender

package com.tzcxyh.appenders;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.ThrowableProxy;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import org.slf4j.helpers.MessageFormatter;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;

/**
 * @Author xuyuhang
 * @Date 2023/6/8 3:04 PM
 * @Description 自定义异常告警Appender
 **/
public class ErrorWarnAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {

    protected void append(ILoggingEvent iLoggingEvent) {
        //为空,不告警
        if(null == iLoggingEvent){
            return;
        }

        /**
         * 下面去调用上报的时候,建议添加一些控制
         * 例如:
         *    过滤掉上报工具类的错误日志(死循环)  if(iLoggingEvent.getLoggerName()..equals("com.test.SendUtils"))
         *    过滤掉10分钟内重复上报的数据   缓存
         */
        //输出信息看看
        System.out.println("error method :\n" + getMethod(iLoggingEvent));

        System.out.println("error message: \n" + getMessage(iLoggingEvent));

    }

    //=========== 以下代码来自plumelog的 LiteAppender================

    /**
     * 获取抛出异常的方法
     * @param logEvent
     * @return
     */
    private static String getMethod(ILoggingEvent logEvent){
        StackTraceElement[] stackTraceElements = logEvent.getCallerData();
        if (stackTraceElements.length > 0) {
            StackTraceElement stackTraceElement = stackTraceElements[0];
            String method = stackTraceElement.getMethodName();
            String line = String.valueOf(stackTraceElement.getLineNumber());
            return method + "(" + stackTraceElement.getFileName() + ":" + line + ")";
        } else {
            return logEvent.getThreadName();
        }
    }



    /**
     * 获取日志堆栈信息
     * @param logEvent
     * @return
     */
    private static String getMessage(ILoggingEvent logEvent) {
        /**
         * 如果是Error级别日志,取错误日志信息
         * log.error("FormattedMessage", e), 获取e中的详细信息
         */
        if (logEvent.getLevel().equals(Level.ERROR)) {
            if (logEvent.getThrowableProxy() != null) {
                ThrowableProxy throwableProxy = (ThrowableProxy)logEvent.getThrowableProxy();
                String[] args = new String[]{logEvent.getFormattedMessage() + "\n" + erroStackTrace(throwableProxy.getThrowable()).toString()};
                return packageMessage("{}", args);
            }
            Object[] args = logEvent.getArgumentArray();
            if (args != null) {
                for(int i = 0; i < args.length; ++i) {
                    if (args[i] instanceof Throwable) {
                        args[i] = erroStackTrace(args[i]);
                    }
                }
                return packageMessage(logEvent.getMessage(), args);
            }
        }
        //直接返回 FormattedMessage
        return logEvent.getFormattedMessage();
    }

    /**
     * 组装成String格式
     * 如果 message 中包含 {}  使用 slf4j 的 MessageFormatter
     * 如果没有,使用自定义的 buildArgsString
     * @param message
     * @param args
     * @return
     */
    private static String packageMessage(String message, Object[] args) {
        return message != null && message.contains("{}") ? MessageFormatter.arrayFormat(message, args).getMessage() : buildArgsString(message, args);
    }

    /**
     * 将 message 和 args 合并成 String
     * @param message
     * @param args
     * @return
     */
    public static String buildArgsString(String message, Object[] args) {
        StringBuilder builder = new StringBuilder(128);
        builder.append(message);
        Object[] var3 = args;
        int var4 = args.length;
        for(int var5 = 0; var5 < var4; ++var5) {
            Object arg = var3[var5];
            builder.append("\n").append(arg);
        }
        return builder.toString();
    }

    /**
     * 错误堆栈数据
     * @param obj
     * @return
     */
    public static Object erroStackTrace(Object obj) {
        if (obj instanceof Exception) {
            Exception eObj = (Exception)obj;
            StringWriter sw = null;
            PrintWriter pw = null;
            Object var5;
            try {
                sw = new StringWriter();
                pw = new PrintWriter(sw);
                String exceptionStack = "\r\n";
                eObj.printStackTrace(pw);
                exceptionStack = sw.toString();
                String var17 = exceptionStack;
                return var17;
            } catch (Exception var15) {
                var15.printStackTrace();
                var5 = obj;
            } finally {
                try {
                    pw.close();
                    sw.close();
                } catch (IOException var14) {
                    var14.printStackTrace();
                }
            }
            return var5;
        } else {
            return obj;
        }
    }
}

使用

配置logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="ERROR_WARN" class="com.tzcxyh.appenders.ErrorWarnAppender">
         <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
              <!--配置只需要Error级别-->
              <level>ERROR</level>
         </filter>
    </appender>
    <logger name="com.tzcxyh.logback">
         <appender-ref ref="ERROR_WARN"/>
    </logger>
</configuration>

测试使用

image.png

注意

1、循环调用问题

在Appender中使用工具类,向监控后台发送错误日志数据,发送飞书、钉钉、邮件等通知用户时,容易出现循环调用

/**
 * 飞书告警推送
 */
@Slf4j
@Component
public class FeishuUtil {
    @Resource
    private RestTemplate restTemplate;

    /**
     * send alert msg to receiver
     */
    public void sendNoticeMsg(String alertContent) {
        try {
            String feishuHookUrl = PropertiesUtil.getInstanse().get("feishuHookUrl");
            FeishuWebHookDto feishuWebHookDto = new FeishuWebHookDto();
            FeishuWebHookDto.Content content = new FeishuWebHookDto.Content();
            ResponseEntity<String> entity = restTemplate.postForEntity(feishuHookUrl, feishuWebHookDto, String.class);
            if (entity.getStatusCode() == HttpStatus.OK) {
                log.info("Send feiShu webHook: {} Success", feishuHookUrl);
            } else {
                log.warn("Send feiShu webHook: {} Failed: {}", feishuHookUrl, entity.getBody());
            }
        } catch (Exception e) {
            //向日志中答应错误日志,然后开始循环发送。。。。
            log.error("飞书发送消息出现异常", e);
        }
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值