通过AOP 实现记录feign调用日志
feign client aop
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Objects;
/**
* @description: api audit log
* @author: kailiwang
* @date: 11/08/2021
*/
@Aspect
@Component
@Slf4j
public class ApiLogAspect {
@Autowired
SystemService systemService;
@Pointcut("@within(org.springframework.cloud.openfeign.FeignClient)")
public void feignClientAspect() {
// Do nothing as it's an injection
}
@Around("feignClientAspect()")
public Object doAround(ProceedingJoinPoint jp) throws Throwable {
log.info("getTarget:{}", jp.getTarget());
ApiAuditLogEntity apiAuditLog = getApiAuditLog(jp);
Object proceed = null;
String errorMsg = null;
try {
proceed = jp.proceed();
return proceed;
} catch (Throwable ex) {
log.error("ApiLogAspect exception,", ex);
errorMsg = ex.getMessage();
throw ex;
} finally {
if (Objects.nonNull(apiAuditLog)) {
apiAuditLog.setResponseTime(new Date());
// async save log
systemService.asyncSaveApiAuditLog(apiAuditLog, proceed, errorMsg);
}
}
}
private ApiAuditLogEntity getApiAuditLog(JoinPoint jp){
try {
if (!(jp.getSignature() instanceof MethodSignature)) {
return null;
}
MethodSignature methodSignature = (MethodSignature) jp.getSignature();
Method method = methodSignature.getMethod();
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
if(requestMapping == null){
return null;
}
ApiAuditLogEntity apiAuditLogEntity = new ApiAuditLogEntity();
apiAuditLogEntity.setMethod(ArrayUtils.isNotEmpty(requestMapping.method()) ? requestMapping.method()[0].name() : "");
String url = getUrl(jp.getTarget());
String path = ArrayUtils.isNotEmpty(requestMapping.value()) ? requestMapping.value()[0] : "";
String name = requestMapping.name();
if (StringUtils.isNotBlank(path)) {
String pathName = path.startsWith("/") ? path.substring(1).replaceAll("/", "_") : path;
apiAuditLogEntity.setApiName(StringUtils.isNotBlank(name) ? name : pathName);
}
apiAuditLogEntity.setPath(StringUtils.isNotBlank(url) ? url + path : path);
apiAuditLogEntity.setCreatedBy(getCurrentUser());
apiAuditLogEntity.setRequestTime( new Date());
apiAuditLogEntity.setRequest(Arrays.toString(jp.getArgs()));
return apiAuditLogEntity;
} catch (Exception e) {
log.error("build api audit log entity error,", e);
return null;
}
}
private String getUrl(Object target){
String url = null;
if (Objects.isNull(target)) {
return url;
}
String targetStr = target.toString();
if (StringUtils.isBlank(targetStr)) {
return url;
}
int beginIndex = targetStr.indexOf("(") + 1;
int endIndex = targetStr.lastIndexOf(")");
if (beginIndex < endIndex) {
targetStr = targetStr.substring(beginIndex, endIndex);
}
String[] split = targetStr.split(", ");
if (ArrayUtils.isEmpty(split)) {
return url;
}
for (int i = 0; i < split.length; i++) {
if (split[i].contains("url=")) {
String[] strings = split[i].split("=");
url = strings.length > 1 ? strings[1] : null;
break;
}
}
return url;
}
private String getCurrentUser(){
try {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
return UserUtil.getUserName(request);
} catch (Exception e) {
return null;
}
}
}
entity
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
@Entity
@Table(name = "tb_api_audit_log")
@Getter
@Setter
public class ApiAuditLogEntity implements Serializable {
private static final long serialVersionUID = 625948518839067473L;
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "api_name")
private String apiName;
@Column(name = "method")
private String method;
@Column(name = "path")
private String path;
@Column(name = "uid")
private String uid;
@Column(name = "reference_no")
private String referenceNo;
@Column(name = "created_by")
private String createdBy;
@Column(name = "created_date", insertable = false, updatable = false)
private Date createdDate;
@Column(name = "description")
private String description;
@Column(name = "request")
private String request;
@Column(name = "request_time")
private Date requestTime;
@Column(name = "response")
private String response;
@Column(name = "response_time")
private Date responseTime;
@Column(name = "status_code")
private String statusCode;
}
dao
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
/**
* @description: description
* @author: kailiwang
* @date: 12/08/2021
*/
@Repository
@Transactional
public interface ApiAuditLogRepository extends JpaRepository<ApiAuditLogEntity, Long> {
@Modifying
@Query("DELETE FROM ApiAuditLogEntity WHERE createdDate < :expireDate")
Integer deleteByCreatedDateLessThan(Date expireDate);
}
async save log
@Async("asyncExecutor")
public void asyncSaveApiAuditLog(ApiAuditLogEntity apiAuditLog, Object proceed, String desc){
if (Objects.isNull(apiAuditLog)) {
return;
}
if (StringUtils.isNotBlank(desc)) {
apiAuditLog.setDescription(desc);
apiAuditLog.setStatusCode("ERROR");
} else {
apiAuditLog.setStatusCode("SUCCESS");
}
if (Objects.nonNull(proceed)) {
try {
apiAuditLog.setResponse(JsonUtil.toPlainJson(proceed));
} catch (Exception e) {
log.error("ApiLogAspect updateApiResp to json error,", e);
}
}
try {
apiAuditLogRepository.save(apiAuditLog);
log.info("saveApiAuditLog end.");
} catch (Exception e) {
log.error("saveApiAuditLog entity:{}, error,", JsonUtil.toPlainJson(apiAuditLog), e);
}
}
scheduled task
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.net.Inet4Address;
import java.net.UnknownHostException;
import java.util.Date;
@Slf4j
@Component
public class ScheduledTask {
@Autowired
ApiAuditLogRepository apiAuditLogRepository;
@Value("${spring.scheduled.clearApiLogRunIp}")
private String clearApiLogRunIp;
/**
* clear expire api audit log every month
*/
@Scheduled(cron="${spring.scheduled.clearApiLogCron:0 0 0 * * SUN}")
public void syncClearExpireApiAuditLog(){
if (!getlockForRun()) {
log.info("ClearExpireApiAuditLog sync job end due to other app is run.");
return;
}
log.info("ClearExpireApiAuditLog sync job start.");
// before 30 day datetime
Date expireDate = DateUtils.addDays(new Date(), -30);
apiAuditLogRepository.deleteByCreatedDateLessThan(expireDate);
log.info("ClearExpireApiAuditLog sync job end.");
}
private boolean getlockForRun() {
log.info("Get lock for run config ip:{}", clearApiLogRunIp);
String host = null;
try {
host = Inet4Address.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
log.error("Error when get ip", e);
return false;
}
if (host.equalsIgnoreCase(clearApiLogRunIp)) {
return true;
} else {
return false;
}
}
}
sql script
DROP TABLE IF EXISTS `tb_api_audit_log`;
CREATE TABLE IF NOT EXISTS `tb_api_audit_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`api_name` varchar(255) DEFAULT NULL,
`method` varchar(20) DEFAULT NULL,
`path` varchar(255) DEFAULT NULL,
`uid` varchar(100) DEFAULT NULL,
`reference_no` varchar(255) DEFAULT NULL COMMENT 'APPLICATION REFERENCE NUMBER',
`created_by` varchar(255) DEFAULT NULL,
`created_date` datetime NOT NULL DEFAULT current_timestamp(),
`description` longtext DEFAULT NULL,
`request` mediumtext DEFAULT NULL,
`request_time` datetime DEFAULT NULL,
`response` mediumtext DEFAULT NULL,
`response_time` datetime DEFAULT NULL,
`status_code` mediumtext DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE='utf8_general_ci';