Browse Source

优化日志邮件过滤逻辑

woody 1 year ago
parent
commit
d0735d512c

+ 6 - 1
framework-base/src/main/java/com/chelvc/framework/base/interceptor/GlobalExceptionInterceptor.java

@@ -70,6 +70,11 @@ import org.springframework.web.multipart.support.MissingServletRequestPartExcept
 @RestControllerAdvice
 @Order(Ordered.HIGHEST_PRECEDENCE)
 public class GlobalExceptionInterceptor extends AbstractErrorController implements Filter {
+    /**
+     * 客户端中断异常
+     */
+    private static final String CLIENT_ABORT_EXCEPTION = "org.apache.catalina.connector.ClientAbortException";
+
     private final ErrorAttributeOptions options = ErrorAttributeOptions.of(ErrorAttributeOptions.Include.values());
 
     public GlobalExceptionInterceptor() {
@@ -263,7 +268,7 @@ public class GlobalExceptionInterceptor extends AbstractErrorController implemen
     @ExceptionHandler(Exception.class)
     public Result<?> exception(HttpServletRequest request, HttpServletResponse response, Exception e) {
         Result<?> result = this.error2result(ErrorUtils.real(e));
-        if (Objects.equals(result.getCode(), Result.ERROR)) {
+        if (Result.ERROR.equals(result.getCode()) && !CLIENT_ABORT_EXCEPTION.equals(e.getClass().getName())) {
             LoggingContextHolder.error(log, request, e);
         } else {
             LoggingContextHolder.warn(log, request, e);

+ 86 - 1
framework-email/src/main/java/com/chelvc/framework/email/LogbackEmailAppender.java

@@ -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;
+        }
+    }
 }