以 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;
}
}