错误日志发送邮件配置
问题分析
正常的系统每天都会有各种各样的报错,程序员游走于各种报错中,有些报错是业务上的报错,是因为用户的不合法操作导致了业务报错,这个错误是我们希望返回给用户看到的错误,当然这类错误也不需要程序员关心;还有些错误是程序员真正不想看到的错误,出错会影响用户的使用,而有些用户看到错误,觉得不影响使用就没有反馈给技术人员,或者是看到错误就觉得平台不好用,后边就不再使用了,损失了用户可是大事情啊。为了让异常不再偷偷的发生,就需要在特定的错误场景下,关键的业务代码中将错误信息发送给开发人员。开发人员及时的感知错误,并修复错误。下面介绍一下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了,等等,有了这些全家桶是不是还需要服务器资源,有一大笔开销
算了,简单的处理就可以满足需求了。如果公司已经有了数据分析的业务,那也不差这几个了。
流式数据分析对异常日志的处理和分析这里就不多介绍了。不是这篇文章的重点。