Skip to content

以 SpringBootStarter 的方式集成到项目中

自定义 EnableLog 注解

java
  

import org.springframework.context.annotation.Import;  
  
import java.lang.annotation.*;  
  

@Target({ElementType.TYPE})  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
@Import(LogAutoConfiguration.class)  
public @interface EnableLog {  
  
    /**  
     * 开启日志记录时, 接口发生异常 是否发送钉钉告警  
     * @return  
     */  
    boolean exception2SendDingTask() default false;  
}

用于设置那些日志需要打印的配置类

java

@ConfigurationProperties(prefix = "log.not-out")  
public class LogProperties implements Serializable {  
  
    /**  
     * 屏蔽全部, 不打印  
     */  
    private Set<String> all = new HashSet<>();  
    /**  
     * 屏蔽response, 不打印  
     */  
    private Set<String> response = new HashSet<>();  
  
    public Set<String> getAll() {  
        return all;  
    }  
    public void setAll(Set<String> all) {  
        //当前参数不为空, 默认转大写  
        if (ArrayUtil.isNotEmpty(all)) {  
            this.all = all.stream().map(String::toUpperCase).collect(Collectors.toSet());  
        } else {  
            this.all = all;  
        }  
    }  
    public Set<String> getResponse() {  
        return response;  
    }  
    public void setResponse(Set<String> response) {  
        //当前参数不为空, 默认转大写  
        if (ArrayUtil.isNotEmpty(response)) {  
            this.response = response.stream().map(String::toUpperCase).collect(Collectors.toSet());  
        } else {  
            this.response = response;  
        }   
     }  
}

注入 Spring 的配置类

java
  

@Configuration(proxyBeanMethods = false)  
@ConditionalOnClass(EnableLog.class)  
@EnableConfigurationProperties({LogProperties.class})  
public class LogAutoConfiguration {  
  
    @Bean  
    @ConditionalOnMissingBean    public FeignInfoLogAspectj feignInfoLogAspectj(LogProperties logProperties) {  
        return new FeignInfoLogAspectj(logProperties);  
    }  
    @Bean  
    @ConditionalOnMissingBean    public IntegrationInfoLogAspectj integrationInfoLogAspectj(LogProperties logProperties) {  
        return new IntegrationInfoLogAspectj(logProperties);  
    }  
    @Bean  
    @ConditionalOnMissingBean    public RepositoryInfoLogAspectj repositoryInfoLogAspectj() {  
        return new RepositoryInfoLogAspectj();  
    }  
    @Bean  
    @ConditionalOnMissingBean    public ControllerLogAspectj controllerLogAspectj(LogProperties logProperties){  
        return new ControllerLogAspectj(logProperties);  
    }  
  
}

具体切面实现

java

@Slf4j  
@Aspect  
public class ControllerLogAspectj {  
    private LogProperties logProperties;  
  
  
    public ControllerLogAspectj(LogProperties logProperties) {  
        this.logProperties = logProperties;  
    }  
    @Around("execution(* com.xxx..*.controller..*.*..*(..))" +  
            "|| execution(* com.xxx..*.controller.*..*.*(..))"+  
            "|| execution(* com.xxx..*.controller..*.*(..))"  
    )  
    public Object afterSaveLog(ProceedingJoinPoint joinPoint) throws Throwable {  
        Object result = null; //结果对象  
        String argsJson = ""; //入参JSON String  
        String resultJson = "";//出参 JSON String        long start = System.currentTimeMillis(); // 开始时间  
        try {// 入参转换JSON,  
            argsJson = JSONUtils.safeToJSONString(joinPoint.getArgs());  
            result = joinPoint.proceed();            resultJson = JSONUtils.safeToJSONString(result);  
            return result;  
        } catch (Throwable e) {  
            //TODO 接口执行错误, 走钉钉报警+ tranceId  
            log.error("切面报错错误,Exception:{},msg:{},request:{}", e.getMessage(), e, argsJson);  
            throw e;  
        } finally {  
            try {  
                // 类名称.方法名称 (大写)  
                String classMethodName = AopUtils.getClassMethodName(joinPoint);  
                String classMethodNameToUp = classMethodName.toUpperCase();                //TODO-wangshuai 关于通配符匹配  
                //屏蔽日志  
                if (ObjUtil.isNotEmpty(logProperties.getAll())) { //当前配置有值  
                    if(!LogMatchUtils.matchMethodName4Rule(classMethodNameToUp,logProperties.getAll())){  
                        // 正常输出  
                        checkCoverResponse(classMethodNameToUp, classMethodName, argsJson, start, resultJson);  
                    }                } else { // 正常输出  
                    checkCoverResponse(classMethodNameToUp, classMethodName, argsJson, start, resultJson);  
                }            } catch (Exception e) {  
                log.error("切面日志打印报错 错误,Exception:{},msg:{},request:{}", e.getMessage(), e, argsJson);  
            }        }    }  
    private void checkCoverResponse(String classMethodNameToUp, String classMethodName, String argsJson, long start, String resultJson) {  
        if (ObjUtil.isNotEmpty(logProperties.getResponse())) {  
            //配置中包含了当前 方法 屏蔽Response 日志输出  
            if(LogMatchUtils.matchMethodName4Rule(classMethodNameToUp,logProperties.getResponse())){  
                log.info("[Controller]MethodName:{},Request:{},time:{}ms", classMethodName, argsJson, System.currentTimeMillis() - start);  
                return;
            }     
        }
        log.info("[Controller]MethodName:{},Request:{},Response:{},time:{}ms", classMethodName, argsJson, resultJson, System.currentTimeMillis() - start);  
    }
}

配置路径匹配类

java
  
import cn.hutool.core.util.StrUtil;  
import lombok.extern.slf4j.Slf4j;  
  
import java.util.Set;  
  
  
@Slf4j  
public class LogMatchUtils {  
  
    /**  
     * 根据规则集合匹配方法名称 []  
     *     * @param methodName 类名.方法名 : TestController.Select  
     * @param ruleList   规则集合 : List["Test*.Page","*Controller.Select"]  
     * @return 当前例子:  true  
     */    public static boolean matchMethodName4Rule(String methodName, Set<String> ruleList) {  
        //边界条件  
        if (null == ruleList || ruleList.isEmpty()) {  //规则为空  
            return false;  
        }        
        if (ruleList.contains("*") || ruleList.contains("*.*") || ruleList.contains(methodName)) {  
            return true;  //当ruleList有*时 return true        
        }  
  
        methodName = methodName.toUpperCase();  //默认全部转为大写字母  
  
        for (String rule : ruleList) {  
            if (StrUtil.isBlank(rule)) {  
                continue;  
            }            
            rule = rule.toUpperCase();            
            if (rule.length() > methodName.length()) {  //rule长度大于methodName长度 匹配下一个  
                continue; // 反例不会存在  
            }  
            if (rule.indexOf('*') == -1) {   //rule中无*时 直接进行eq匹配  
                if (rule.equals(methodName)) {  
                    return true;  
                }  //不一致匹配下一个rule  
                continue;  
            }            //处理通配符  
            if (isCheckResultFlag(rule, methodName)) {  
                //匹配成功  
                return true;  
            }        }        //遍历完全部未匹配到  
        return false;  
    }  
    private static boolean isCheckResultFlag(String rule, String method) {  
        //rule中有*时 进行字符串分割  
        int start = 0;  
        boolean isFirstTemp = true, isEnd = false;//isFirstTemp是否为第一个temp isEnd判断rule结尾是否是*  
        String tempSubStringSonValue = ""; //  切割好的子串 不包含 *  
        //若rule第一个字符为* 跳过*进行匹配  
        if (rule.indexOf('*') == 0) {  
            start = 1;  
            isFirstTemp = false;  
        }        //若rule最后一个字符是* isEnd为true  
        if (rule.charAt(rule.length() - 1) == '*') {  
            isEnd = true;  
        }  
        //将*和*之间子串分割 进行匹配 仅判断匹配不成功  
        for (int i = start; i < rule.length(); i++) {  
            if (rule.charAt(i) != '*') {//当前字符不是*时 存入tempSubStringSonValue  
                tempSubStringSonValue += rule.charAt(i);  
            } else {  
                //当前字符为*时  
                int position = method.indexOf(tempSubStringSonValue);//寻找tempSubStringSonValue在method中第一次出现位置  
                if ((position == -1) || (start == 0 && isFirstTemp && position != 0)) {//position==-1 未匹配到  
                    //start==0&&isFirstTemp&&position!=0 rule开头不是*时 method开头必须为tempSubStringSonValue  
                    return false;  
                } else {//不是开头且已经匹配成功时 更新method和tempSubStringSonValue  
                    method = method.substring(position + tempSubStringSonValue.length(), method.length());  
                    tempSubStringSonValue = "";  
                }                isFirstTemp = false;//更新isFirstTemp的值  
            }  
  
            if (i == rule.length() - 1) {//当搜索到rule末尾时  
                int position = method.indexOf(tempSubStringSonValue);//寻找tempSubStringSonValue在method中第一次出现位置  
                int lastPosition = method.lastIndexOf(tempSubStringSonValue);//寻找tempSubStringSonValue在method中最后一次出现位置  
                int methodLength = method.length() - tempSubStringSonValue.length();  
                if ((position == -1) || (!isEnd && lastPosition != methodLength)) {  
                // position==-1 未匹配到   //当rule结尾不是*时 method必须以temp结尾  
                    return false;  
                }            
            }        
        }        
        return true;  
    }  
}

waitingresult.com