异常日志发送邮件logback配置(支持不重复发送,累计到一定次数发送邮件)

错误日志发送邮件配置

问题分析

正常的系统每天都会有各种各样的报错,程序员游走于各种报错中,有些报错是业务上的报错,是因为用户的不合法操作导致了业务报错,这个错误是我们希望返回给用户看到的错误,当然这类错误也不需要程序员关心;还有些错误是程序员真正不想看到的错误,出错会影响用户的使用,而有些用户看到错误,觉得不影响使用就没有反馈给技术人员,或者是看到错误就觉得平台不好用,后边就不再使用了,损失了用户可是大事情啊。为了让异常不再偷偷的发生,就需要在特定的错误场景下,关键的业务代码中将错误信息发送给开发人员。开发人员及时的感知错误,并修复错误。下面介绍一下logback的错误日志发生邮件如何配置。

流程图

先上图,看清楚工作流程之后,再来捋一下代码:
在这里插入图片描述

1.先判断是否发送过错误邮件,如果未发送,第一次发送错误邮件
2.再判断是否超过最大次数,如果超过,删除缓存从0开始计次,到次数了就再次发送失败次数的邮件

即解决了不会收到大量错误邮件的问题,又可以知道错误一共发生了多少次

接下来上代码:


import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;

import java.util.Date;

public class EmailLogger {
    private static final Logger logger = LoggerFactory.getLogger(EmailLogger.class);
    private static final ConcurrentHashMap<String, Long> uniqueKeyMap = new ConcurrentHashMap<>();
    private static final ConcurrentHashMap<String, MailCounter> repeatCountMap = new ConcurrentHashMap<>();
    /** 12小时 **/
    public static final int H12 = 43200;
    /** 24小时 **/
    public static final int DAY = 86400;
    /** 1小时 **/
    public static final int HOUR = 3600;
    /** 半小时 **/
    public static final int HALF_HOUR = 1800;
    /** 一周 **/
    public static final int WEEK = 604800;

    public static void trace(String arg0) {
        logger.trace(arg0);
    }

    public static void trace(String arg0, Object arg1) {
        logger.trace(arg0, arg1);
    }

    public static void trace(String arg0, Object arg1, Object arg2) {
        logger.trace(arg0, arg1, arg2);
    }

    public static void trace(String arg0, Object... arg1) {
        logger.trace(arg0, arg1);
    }

    public static void trace(String arg0, Throwable arg1) {
        logger.trace(arg0, arg1);
    }

    public static void trace(Marker arg0, String arg1) {
        logger.trace(arg0, arg1);
    }

    public static void trace(Marker arg0, String arg1, Object arg2) {
        logger.trace(arg0, arg1, arg2);
    }

    public static void trace(Marker arg0, String arg1, Object arg2, Object arg3) {
        logger.trace(arg0, arg1, arg2, arg3);
    }

    public static void trace(Marker arg0, String arg1, Object... arg2) {
        logger.trace(arg0, arg1, arg2);
    }

    public static void trace(Marker arg0, String arg1, Throwable arg2) {
        logger.trace(arg0, arg1, arg2);
    }

    public static void debug(String arg0) {
        logger.debug(arg0);
    }

    public static void debug(String arg0, Object arg1) {
        logger.debug(arg0, arg1);
    }

    public static void debug(String arg0, Object arg1, Object arg2) {
        logger.debug(arg0, arg1, arg2);
    }

    public static void debug(String arg0, Object... arg1) {
        logger.debug(arg0, arg1);
    }

    public static void debug(String arg0, Throwable arg1) {
        logger.debug(arg0, arg1);
    }

    public static void debug(Marker arg0, String arg1) {
        logger.debug(arg0, arg1);
    }

    public static void debug(Marker arg0, String arg1, Object arg2) {
        logger.debug(arg0, arg1, arg2);
    }

    public static void debug(Marker arg0, String arg1, Object arg2, Object arg3) {
        logger.debug(arg0, arg1, arg2, arg3);
    }

    public static void debug(Marker arg0, String arg1, Object... arg2) {
        logger.debug(arg0, arg1, arg2);
    }

    public static void debug(Marker arg0, String arg1, Throwable arg2) {
        logger.debug(arg0, arg1, arg2);
    }

    public static void info(String arg0) {
        logger.info(arg0);
    }

    public static void info(String arg0, Object arg1) {
        logger.info(arg0, arg1);
    }

    public static void info(String arg0, Object arg1, Object arg2) {
        logger.info(arg0, arg1, arg2);
    }

    public static void info(String arg0, Object... arg1) {
        logger.info(arg0, arg1);
    }

    public static void info(String arg0, Throwable arg1) {
        logger.info(arg0, arg1);
    }

    public static void info(Marker arg0, String arg1) {
        logger.info(arg0, arg1);
    }

    public static void info(Marker arg0, String arg1, Object arg2) {
        logger.info(arg0, arg1, arg2);
    }

    public static void info(Marker arg0, String arg1, Object arg2, Object arg3) {
        logger.info(arg0, arg1, arg2, arg3);
    }

    public static void info(Marker arg0, String arg1, Object... arg2) {
        logger.info(arg0, arg1, arg2);
    }

    public static void info(Marker arg0, String arg1, Throwable arg2) {
        logger.info(arg0, arg1, arg2);
    }

    public static void warn(String arg0) {
        logger.warn(arg0);
    }

    public static void warn(String arg0, Object arg1) {
        logger.warn(arg0, arg1);
    }

    public static void warn(String arg0, Object... arg1) {
        logger.warn(arg0, arg1);
    }

    public static void warn(String arg0, Object arg1, Object arg2) {
        logger.warn(arg0, arg1, arg2);
    }

    public static void warn(String arg0, Throwable arg1) {
        logger.warn(arg0, arg1);
    }

    public static void warn(Marker arg0, String arg1) {
        logger.warn(arg0, arg1);
    }

    public static void warn(Marker arg0, String arg1, Object arg2) {
        logger.warn(arg0, arg1, arg2);
    }

    public static void warn(Marker arg0, String arg1, Object arg2, Object arg3) {
        logger.warn(arg0, arg1, arg2, arg3);
    }

    public static void warn(Marker arg0, String arg1, Object... arg2) {
        logger.warn(arg0, arg1, arg2);
    }

    public static void warn(Marker arg0, String arg1, Throwable arg2) {
        logger.warn(arg0, arg1, arg2);
    }

    public static void error(String msg) {
        logger.error(msg);
    }

    public static void error(String format, Object arg) {
        logger.error(format, arg);
    }

    public static void error(String format, Object arg1, Object arg2) {
        logger.error(format, arg1, arg2);
    }

    public static void error(String format, Object... arguments) {
        logger.error(format, arguments);
    }

    public static void error(String msg, Throwable t) {
        logger.error(msg, t);
    }

    public static void error(Marker marker, String msg) {
        logger.error(marker, msg);
    }

    public static void error(Marker marker, String format, Object arg) {
        logger.error(marker, format, arg);
    }

    public static void error(Marker marker, String format, Object arg1, Object arg2) {
        logger.error(marker, format, arg1, arg2);
    }

    public static void error(Marker marker, String format, Object... arguments) {
        logger.error(marker, format, arguments);
    }

    public static void error(Marker marker, String msg, Throwable t) {
        logger.error(marker, msg, t);
    }

    /**
     * 发送报错邮件,如果在限定时间段内已经发过邮件了,则不发邮件。如果报错邮件的重复次数达到上限,则发送次数邮件
     *
     * @param seconds  限定的时间(秒)
     * @param maxTimes 报错的次数上限
     */
    public static void distinctError(long seconds, int maxTimes, String msg, Throwable t) {
        String uniqueKey = getUniqueKey();
        //如果在一定的秒数内没有发过邮件,才会发送
        if (isNotRepeated(uniqueKey, seconds)) {
            logger.error(msg, t);
        }
        //返回累积的报错邮件达到了多少次,如果达到了上限,则发送次数邮件并清空次数
        MailCounter counter = getRepeatMailCounter(uniqueKey, maxTimes);
        if (counter.getTimes() >= maxTimes) {
            String content = "\t 重复次数:" + counter.getTimes()
                    + "\n \t 代码位置:" + uniqueKey
                    + "\n \t 开始时间:" + new Date(counter.getFirstTime()) + "\n \t ";
            logger.error(content + msg, t);
        }
    }

    /**
     * 发送报错邮件,如果在限定时间段内已经发过邮件了,则不发邮件。如果报错邮件的重复次数达到上限,则发送次数邮件
     *
     * @param seconds  限定的时间(秒)
     * @param maxTimes 报错的次数上限
     */
    public static void distinctError(long seconds, int maxTimes, String msg) {
        String uniqueKey = getUniqueKey();
        //如果在一定的秒数内没有发过邮件,才会发送
        if (isNotRepeated(uniqueKey, seconds)) {
            logger.error(msg);
        }
        //返回累积的报错邮件达到了多少次,如果达到了上限,则发送次数邮件并清空次数
        MailCounter counter = getRepeatMailCounter(uniqueKey, maxTimes);
        if (counter.getTimes() >= maxTimes) {
            String content = "\t 重复次数:" + counter.getTimes()
                    + "\n \t 代码位置:" + uniqueKey
                    + "\n \t 开始时间:" + new Date(counter.getFirstTime()) + "\n \t ";
            logger.error(content + msg);
        }
    }

    /**
     * 发送报错邮件,如果在限定时间段内已经发过邮件了,则不发邮件。如果报错邮件的重复次数达到上限,则发送次数邮件
     *
     * @param seconds  限定的时间(秒)
     * @param maxTimes 报错的次数上限
     */
    public static void distinctError(long seconds, int maxTimes, String msg, Object t) {
        String uniqueKey = getUniqueKey();
        //如果在一定的秒数内没有发过邮件,才会发送
        if (isNotRepeated(uniqueKey, seconds)) {
            logger.error(msg, t);
        }
        //返回累积的报错邮件达到了多少次,如果达到了上限,则发送次数邮件并清空次数
        MailCounter counter = getRepeatMailCounter(uniqueKey, maxTimes);
        if (counter.getTimes() >= maxTimes) {
            String content = "\t 重复次数:" + counter.getTimes()
                    + "\n \t 代码位置:" + uniqueKey
                    + "\n \t 开始时间:" + new Date(counter.getFirstTime()) + "\n \t ";
            logger.error(content + msg, t);
        }
    }

    /**
     * 发送报错邮件,如果在限定时间段内已经发过邮件了,则不发邮件。如果报错邮件的重复次数达到上限,则发送次数邮件
     *
     * @param seconds  限定的时间(秒)
     * @param maxTimes 报错的次数上限
     */
    public static void distinctError(long seconds, int maxTimes, String msg, Object... obj) {
        String uniqueKey = getUniqueKey();
        //如果在一定的秒数内没有发过邮件,才会发送
        if (isNotRepeated(uniqueKey, seconds)) {
            logger.error(msg, obj);
        }
        //返回累积的报错邮件达到了多少次,如果达到了上限,则发送次数邮件并清空次数
        MailCounter counter = getRepeatMailCounter(uniqueKey, maxTimes);
        if (counter.getTimes() >= maxTimes) {
            String content = "\t 重复次数:" + counter.getTimes()
                    + "\n \t 代码位置:" + uniqueKey
                    + "\n \t 开始时间:" + new Date(counter.getFirstTime()) + "\n \t ";
            logger.error(content + msg, obj);
        }
    }

    /**
     * 发送报错邮件,如果在限定时间段内已经发过邮件了,则不发邮件。如果报错邮件的重复次数达到上限,则发送次数邮件
     *
     * @param seconds  限定的时间(秒)
     * @param maxTimes 报错的次数上限
     */
    public static void distinctError(long seconds, int maxTimes, Marker marker, String msg, Throwable t) {
        String uniqueKey = getUniqueKey();
        //如果在一定的秒数内没有发过邮件,才会发送
        if (isNotRepeated(uniqueKey, seconds)) {
            logger.error(marker, msg, t);
        }
        //返回累积的报错邮件达到了多少次,如果达到了上限,则发送次数邮件并清空次数
        MailCounter counter = getRepeatMailCounter(uniqueKey, maxTimes);
        if (counter.getTimes() >= maxTimes) {
            String content = "\n \t 重复次数:" + counter.getTimes()
                    + "\n \t 代码位置:" + uniqueKey
                    + "\n \t 开始时间:" + new Date(counter.getFirstTime()) + "\n \t ";
            logger.error(marker, content + msg, t);
        }
    }

    /**
     * 发送报错邮件,如果在限定时间段内已经发过邮件了,则不发邮件。如果报错邮件的重复次数达到上限,则发送次数邮件
     *
     * @param seconds  限定的时间(秒)
     * @param maxTimes 报错的次数上限
     */
    public static void distinctError(long seconds, int maxTimes, Marker marker, String msg, Object... objects) {
        String uniqueKey = getUniqueKey();
        //如果在一定的秒数内没有发过邮件,才会发送
        if (isNotRepeated(uniqueKey, seconds)) {
            logger.error(marker, msg, objects);
        }
        //返回累积的报错邮件达到了多少次,如果达到了上限,则发送次数邮件并清空次数
        MailCounter counter = getRepeatMailCounter(uniqueKey, maxTimes);
        if (counter.getTimes() >= maxTimes) {
            String content = "\n \t 重复次数:" + counter.getTimes()
                    + "\n \t 代码位置:" + uniqueKey
                    + "\n \t 开始时间:" + new Date(counter.getFirstTime()) + "\n \t ";
            logger.error(content + msg, objects);
        }
    }

    /**
     * 发送报错邮件,如果在限定时间段内已经发过邮件了,则不发邮件。如果报错邮件的重复次数达到上限,则发送次数邮件
     *
     * @param seconds  限定的时间(秒)
     * @param maxTimes 报错的次数上限
     */
    public static void distinctError(long seconds, int maxTimes, Marker marker, String msg) {
        String uniqueKey = getUniqueKey();
        //如果在一定的秒数内没有发过邮件,才会发送
        if (isNotRepeated(uniqueKey, seconds)) {
            logger.error(marker, msg);
        }
        //返回累积的报错邮件达到了多少次,如果达到了上限,则发送次数邮件并清空次数
        MailCounter counter = getRepeatMailCounter(uniqueKey, maxTimes);
        if (counter.getTimes() >= maxTimes) {
            String content = "\t 重复次数:" + counter.getTimes()
                    + "\n \t 代码位置:" + uniqueKey
                    + "\n \t 开始时间:" + new Date(counter.getFirstTime()) + "\n \t ";
            logger.error(content + msg);
        }
    }

    /**
     * 返回同样的错误邮件已经累积的次数,如果次数已经达到上限,则清除次数
     *
     * @param uniqueKey
     * @param maxTimes
     * @return
     */
    private static MailCounter getRepeatMailCounter(String uniqueKey, int maxTimes) {
        if (maxTimes <= 1) {
            maxTimes = 2;
        }
        MailCounter counter;
        if (!repeatCountMap.containsKey(uniqueKey)) {
            counter = new MailCounter();
            repeatCountMap.put(uniqueKey, counter);
            return counter;
        } else {
            counter = repeatCountMap.get(uniqueKey);
            counter.setTimes(counter.getTimes() + 1);
            if (maxTimes <= counter.getTimes()) {
                repeatCountMap.remove(uniqueKey);
            }
        }
        return counter;
    }

    /**
     * 判断在限定的秒数内是否有过重复的操作
     *
     * @param uniqueKey
     * @param seconds
     * @return
     */
    private static boolean isNotRepeated(String uniqueKey, long seconds) {

        if (!uniqueKeyMap.containsKey(uniqueKey)) {
            uniqueKeyMap.put(uniqueKey, System.currentTimeMillis());
            return true;
        } else {
            Long value = uniqueKeyMap.get(uniqueKey);
            if ((System.currentTimeMillis() - value) / 1000 >= seconds) {
                uniqueKeyMap.put(uniqueKey, System.currentTimeMillis());
                return true;
            } else {
                return false;
            }
        }
    }

    /**
     * 获取外层调用工具类的方法的堆栈信息
     *
     * @return
     */
    private static String getUniqueKey() {
        return Thread.currentThread().getStackTrace()[3].toString();
    }

    private static class MailCounter {
        private long firstTime;
        private int times;

        public MailCounter() {
            this.firstTime = System.currentTimeMillis();
            this.times = 1;
        }

        public long getFirstTime() {
            return firstTime;
        }

        public void setFirstTime(long firstTime) {
            this.firstTime = firstTime;
        }

        public int getTimes() {
            return times;
        }

        public void setTimes(int times) {
            this.times = times;
        }
    }
}

需要在pom文件中添加email和slf4j的引用:

  <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.3</version>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-core</artifactId>
      <version>1.2.3</version>
    </dependency>
    <dependency>
      <groupId>javax.mail</groupId>
      <artifactId>mail</artifactId>
      <version>1.4.7</version>
    </dependency>

代码中如何调用:

// 错误邮件报警,半个小时内每10次错误发一次
EmailLogger.distinctError(EmailLogger.HALF_HOUR, 10, "测试错误信息发送");

logback.xml中如何配置:

 <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
    <smtpHost>smtp.163.com</smtpHost>
    <smtpPort>25</smtpPort>
    <SSL>true</SSL>
    <username>发送报警邮件的邮箱账号</username>
    <password>发送邮件的邮箱密码</password>

    <to>报警邮件发送给谁</to>
    <from>报警邮件发送的邮箱</from>
    <subject>TESTING: %logger{20} - %m</subject>
    <asynchronousSending>true</asynchronousSending>
    <layout class="ch.qos.logback.classic.PatternLayout">
      <pattern>%d{yyyy/MM/dd HH:mm:ss.SSS} [%-5level] [%thread] |- %m%n</pattern>
    </layout>
  </appender>

  <logger name="com.xxx.cloud.util.EmailLogger" level="ERROR" additivity="false">
    <appender-ref ref="EMAIL" />
  </logger>

好了,接下来就在业务系统内添加关键的错误邮件报警吧。快速感知业务错误。这种做法其实对业务系统的侵入性还挺高。如果想要侵入性不高的也可以,就不能用这种办法去做了,接下来,我们的流式数据分析就上场了。
流式数据分析相对于上面邮件报警有优点也有缺点
优点是对业务系统无侵入性,开发人员不需要关注异常,只需要正常打印错误日志即可
缺点是需要一套数据分析的全家桶,什么kafka了,zookeeper了,storm/spark了,等等,有了这些全家桶是不是还需要服务器资源,有一大笔开销
算了,简单的处理就可以满足需求了。如果公司已经有了数据分析的业务,那也不差这几个了。

流式数据分析对异常日志的处理和分析这里就不多介绍了。不是这篇文章的重点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值