在软件开发的过程中,进行系统日志记录是必不可少的,我利用了Spring aop 自定义注解 面向切面编程 队列 异步 批量处理的方式 完成了我们系统的日志记录,提升了日志记录的性能。
首先 :编写自定义注解,使用该注解注解在controller层的方法上,便于后续的aop记录日志。
/**
* @author 徐塬峰 2019/4/14
* 以自定义注解的方式记录日志
* 注解在方法级别上 运行时启动
**/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
String value() default "";
}
日志记录的实体类,可以根据自己的业务来进行修改。
package com.gaoshu.web.audit;
import java.util.Date;
/**
* 日志记录实体层
* @author 徐塬峰
* @email [email protected]
* @date 2019-05-13
* @description
**/
public class AuditLog {
private String id;
private String description;
private String method;
private String type;
private String requestIp;
private String exceptionCode;
private String exceptionDetail;
private String params;
private Date createDate;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getRequestIp() {
return requestIp;
}
public void setRequestIp(String requestIp) {
this.requestIp = requestIp;
}
public String getExceptionCode() {
return exceptionCode;
}
public void setExceptionCode(String exceptionCode) {
this.exceptionCode = exceptionCode;
}
public String getExceptionDetail() {
return exceptionDetail;
}
public void setExceptionDetail(String exceptionDetail) {
this.exceptionDetail = exceptionDetail;
}
public String getParams() {
return params;
}
public void setParams(String params) {
this.params = params;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
@Override
public String toString() {
return "AuditLog{" +
"id='" + id + '\'' +
", description='" + description + '\'' +
", method='" + method + '\'' +
", type='" + type + '\'' +
", requestIp='" + requestIp + '\'' +
", exceptionCode='" + exceptionCode + '\'' +
", exceptionDetail='" + exceptionDetail + '\'' +
", params='" + params + '\'' +
", createDate=" + createDate +
'}';
}
}
日志的存放的队列编写
package com.gaoshu.web.audit;
import org.springframework.stereotype.Component;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* @author 徐塬峰
* @email [email protected]
* @date 2019-05-13
* @description
**/
@Component
public class AuditLogQueue {
// BlockingDequeu为双端阻塞队列,blockingQueue阻塞队列
private BlockingQueue blockingQueue = new LinkedBlockingQueue();
public void add(AuditLog auditLog) {
blockingQueue.add(auditLog);
}
//poll从队列的头部获取到信息
public AuditLog poll() throws InterruptedException {
return blockingQueue.poll(1, TimeUnit.SECONDS);//每秒钟执行一次
}
}
紧接着:编写系统日志的消费者。可以在DEFAULT_BATCH_SIZE配置当数量达到多少时,进行消费。
/**
* @author 徐塬峰
* @email [email protected]
* @date 2019-05-13
* @description 消息队列的消费者
* 日志保存的线程
**/
@Component
public class AuditLogConsumer implements Runnable {
private static Logger logger = LoggerFactory.getLogger(AuditLogConsumer.class);
private static final int DEFAULT_BATCH_SIZE = 16;
private AuditLogQueue auditLogQueue;
private AuditLogService auditLogService;
private int batchSize = DEFAULT_BATCH_SIZE;
private boolean active = true;
private Thread thread;
//使用该注解在项目启动时候启动线程
@PostConstruct
public void init() {
thread = new Thread(this);
thread.start();
}
//使用该注解在项目结束的舒缓关闭
@PreDestroy
public void close() {
active = false;
}
@Override
public void run() {
while (active) {
execute();
}
}
public void execute() {
List auditLogList = new ArrayList();
try {
int size = 0;
while (size < batchSize) {
AuditLog auditLog = auditLogQueue.poll();//从队列中取出一个
if (auditLog == null) {
break;
}
auditLogList.add(auditLog);
size++;
}
} catch (InterruptedException e) {
e.printStackTrace();
logger.error(e.toString());
}
//如果当前的日志list不为空
if (!auditLogList.isEmpty()) {
// auditLogService.batchLog(auditDtos);
auditLogService.batchLog(auditLogList);
//在这里执行了servivce的方法
}
}
@Resource
public void setAuditLogQueue(AuditLogQueue auditLogQueue) {
this.auditLogQueue = auditLogQueue;
}
@Resource
public void setAuditLogService(AuditLogService auditLogService) {
this.auditLogService = auditLogService;
}
public void setBatchSize(int batchSize) {
this.batchSize = batchSize;
}
然后是service层的编写:
package com.gaoshu.web.audit;
import com.gaoshu.mapper.AuditLogMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author 徐塬峰
* @email [email protected]
* @date 2019-05-13
* @description
**/
@Service
public class AuditLogService {
@Autowired
private AuditLogMapper mapper;
/**
* 对消息队列的日志进行批量处理
*
* @param auditLogList
*/
public void batchLog(List auditLogList) {
auditLogList.parallelStream().forEach((e)->{
System.out.println(e.toString());
mapper.add(e);
});
}
}
配置aop切面
/**
* @author 徐塬峰 2019/3/31
* 使用AOP面向切面编程的方式实现登录拦截 将请求跳转到登录页面
*/
@Aspect
@Component
public class LogAspect {
@Autowired
private AuditLogQueue auditLogQueue;
private static final Logger logger = LogManager.getLogger("用户操作日志");
@Before("@annotation(com.gaoshu.web.annotation.SysLog)")
public Object SysLog(JoinPoint joinPoint) throws Throwable {
Object[] ob = joinPoint.getArgs();
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpSession session = request.getSession();
AdminUserVo user = (AdminUserVo) session.getAttribute("adminUser");
String username = null;
String ip = IpUtils.getRemoteIP(request);
if (user != null) {
username = user.getUsername();
}
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof ServletRequest) {
args[i] = "request对象";
} else if (args[i] instanceof ServletResponse) {
args[i] = "response对象";
} else if (args[i] instanceof MultipartFile) {
args[i] = "MultipartFile对象";
} else if (args[i] instanceof BindingResult) {
args[i] = "BindingResult对象";
}
}
//控制台输出
// logger.info("用户id:{},方法签名:{},方法参数:{} ip地址:{}", username, joinPoint.getSignature(), JsonUtils.toJson(args), ip);
//*========数据库日志=========*//
AuditLog log = new AuditLog();
log.setId(Sid.nextShort());
log.setDescription(joinPoint.getSignature().toString());
log.setMethod(joinPoint.getSignature().toString());
log.setParams(JsonUtils.toJson(args));
log.setType("0");
log.setRequestIp(ip);
log.setExceptionCode( null);
log.setExceptionDetail( null);
log.setCreateDate(new Date());
//保存数据库
auditLogQueue.add(log);
//拿到请求中的所有参数args
return ob;
}
}
controller层处理:使用@SysLog注解在controller层的方法上即可
@SysLog
@ResponseBody
@PostMapping("/resetPassword.do")
public JSONResult resetPassword(HttpServletRequest request, String password, String newPassword) {
AdminUserVo adminUserVo = (AdminUserVo) request.getSession().getAttribute("adminUser");
if (adminUsersService.resetPassword(adminUserVo, password, newPassword)) {
return JSONResult.ok("ok");
}
return JSONResult.errorMsg("error");
}
效果展示:
代码地址:https://github.com/RAOE/next1-boot
以上就是我归纳的对日志记录的实现,如果有哪些地方有问题,欢迎大家留言指正,感激不尽!