|
@@ -1,11 +1,18 @@
|
|
|
package com.chelvc.framework.email;
|
|
|
|
|
|
+import java.util.Map;
|
|
|
import java.util.Objects;
|
|
|
|
|
|
+import ch.qos.logback.classic.Level;
|
|
|
+import ch.qos.logback.classic.filter.ThresholdFilter;
|
|
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
|
|
+import ch.qos.logback.classic.spi.IThrowableProxy;
|
|
|
import ch.qos.logback.core.AppenderBase;
|
|
|
import ch.qos.logback.core.Layout;
|
|
|
+import ch.qos.logback.core.spi.FilterReply;
|
|
|
+import com.chelvc.framework.base.util.SpringUtils;
|
|
|
import com.chelvc.framework.common.util.StringUtils;
|
|
|
+import com.google.common.collect.Maps;
|
|
|
import lombok.Getter;
|
|
|
import lombok.Setter;
|
|
|
import org.springframework.beans.BeansException;
|
|
@@ -30,8 +37,10 @@ public class LogbackEmailAppender extends AppenderBase<ILoggingEvent> implements
|
|
|
|
|
|
private String to;
|
|
|
private String subject;
|
|
|
+ private long interval = 60000;
|
|
|
private boolean asynchronous = true;
|
|
|
private Layout<ILoggingEvent> layout;
|
|
|
+ private final Map<Integer, Long> hashes = Maps.newConcurrentMap();
|
|
|
|
|
|
/**
|
|
|
* 判断日志邮件发送器是否已准备就绪
|
|
@@ -51,6 +60,31 @@ public class LogbackEmailAppender extends AppenderBase<ILoggingEvent> implements
|
|
|
return Objects.requireNonNull(EMAIL_HANDLER);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 获取事件hash值
|
|
|
+ *
|
|
|
+ * @param event 事件对象实例
|
|
|
+ * @return hash值
|
|
|
+ */
|
|
|
+ protected int hash(ILoggingEvent event) {
|
|
|
+ IThrowableProxy throwable = event.getThrowableProxy();
|
|
|
+ int hash = throwable == null ? 0 : Objects.hash((Object[]) throwable.getStackTraceElementProxyArray());
|
|
|
+ return 31 * Math.max(hash, 1) + Objects.hashCode(event.getLoggerName());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断是否是重复事件
|
|
|
+ *
|
|
|
+ * @param event 事件对象实例
|
|
|
+ * @return true/false
|
|
|
+ */
|
|
|
+ protected boolean isDuplicated(ILoggingEvent event) {
|
|
|
+ int hash = this.hash(event);
|
|
|
+ long now = System.currentTimeMillis();
|
|
|
+ Long timestamp = this.hashes.put(hash, now);
|
|
|
+ return timestamp != null && now - timestamp <= this.interval;
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
|
|
EMAIL_HANDLER = applicationContext.getBean(EmailHandler.class);
|
|
@@ -58,7 +92,7 @@ public class LogbackEmailAppender extends AppenderBase<ILoggingEvent> implements
|
|
|
|
|
|
@Override
|
|
|
protected void append(ILoggingEvent event) {
|
|
|
- if (!this.isReady()) {
|
|
|
+ if (!this.isReady() || (event.getLevel() == Level.ERROR && this.isDuplicated(event))) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -75,4 +109,55 @@ public class LogbackEmailAppender extends AppenderBase<ILoggingEvent> implements
|
|
|
handler.send(body);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 日志邮件过滤器实现
|
|
|
+ */
|
|
|
+ public static class Filter extends ThresholdFilter {
|
|
|
+ private String[] includes;
|
|
|
+ private String[] excludes;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断事件是否满足邮件发送条件
|
|
|
+ *
|
|
|
+ * @param event 事件对象实例
|
|
|
+ * @return true/false
|
|
|
+ */
|
|
|
+ protected boolean matches(ILoggingEvent event) {
|
|
|
+ String logger = event.getLoggerName();
|
|
|
+ return (StringUtils.isEmpty(this.excludes) || !SpringUtils.isPath(logger, this.excludes))
|
|
|
+ && (StringUtils.isEmpty(this.includes) || SpringUtils.isPath(logger, this.includes));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置包含的日志包路径
|
|
|
+ *
|
|
|
+ * @param includes 包路径
|
|
|
+ */
|
|
|
+ public void setIncludes(String includes) {
|
|
|
+ this.includes = StringUtils.ifEmpty(includes, StringUtils::splitActives);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置排除的日志包路径
|
|
|
+ *
|
|
|
+ * @param excludes 包路径
|
|
|
+ */
|
|
|
+ public void setExcludes(String excludes) {
|
|
|
+ this.excludes = StringUtils.ifEmpty(excludes, StringUtils::splitActives);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public FilterReply decide(ILoggingEvent event) {
|
|
|
+ if (!this.isStarted()) {
|
|
|
+ return FilterReply.NEUTRAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ FilterReply reply = super.decide(event);
|
|
|
+ if (reply == FilterReply.NEUTRAL && this.matches(event)) {
|
|
|
+ return FilterReply.NEUTRAL;
|
|
|
+ }
|
|
|
+ return FilterReply.DENY;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|