效果图
建好数据库即可,表不存在会自动创建,别说什么高并发会怎么怎么样了,高并发不用搜索引擎,还想用数据库记录日志就是疯了
下面第一条是异常信息,第二条是正常的数据返回。
LogAspect.java
日志切面
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fu.work.entity.Log;
import com.fu.work.service.LogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 日志切面
*/
@Slf4j
@Aspect
@Component
public class LogAspect {
@Resource
private LogService logService;
//把fu替换成你自己的包即可
@Pointcut("execution(* com.fu.*.controller.*Controller.*(..))||execution(* com.fu.*.*.controller.*Controller.*(..))||execution(* com.fu.*.*.*.controller.*Controller.*(..))")
public void logOperation() {
}
/**
* 抛出异常时执行(Around和AfterThrowing互斥,只会有一个执行)
*/
@AfterThrowing(pointcut = "logOperation()", throwing = "e")
public void afterThrowing(Exception e){
try {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
assert attributes != null;
HttpServletRequest request = attributes.getRequest();
Map<String, String[]> parametersMap = request.getParameterMap();
Map<String, Object> parameters = new HashMap<>();
if (parametersMap != null) {
parametersMap.forEach((key, value) -> {
parameters.put(key, value[0]);
});
}
ObjectMapper om = new ObjectMapper();
Log logObj = new Log();
logObj.setUserId("userId");//通过token获取
logObj.setUsername("Meta");//通过token获取
logObj.setApplicationName("应用名称");
logObj.setCode(500);//状态码
logObj.setMethod(request.getMethod());//请求方法
logObj.setRequestURI(request.getRequestURI());//请求URI
logObj.setRequestData(String.valueOf(parameters));//请求数据
logObj.setMsg(r.getMsg());//返回信息
logObj.setErrorMsg(om.writeValueAsString(e));//错误信息
logObj.setCreateTime(new Date());//创建时间
//异步入库
logService.insert(logObj);
} catch (Exception ex) {
log.error("日志入库异常:", ex);
}
}
/**
* 环绕是最后执行的(Around和AfterThrowing互斥,只会有一个执行)
*/
@Around("logOperation()")
public Object doAround(ProceedingJoinPoint point) throws Throwable {
Object result = point.proceed();//执行切点
try {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
assert attributes != null;
HttpServletRequest request = attributes.getRequest();
Map<String, String[]> parametersMap = request.getParameterMap();
Map<String, Object> parameters = new HashMap<>();
if (parametersMap != null) {
parametersMap.forEach((key, value) -> {
parameters.put(key, value[0]);
});
}
Log logObj = new Log();
logObj.setUserId("userId");//通过token获取
logObj.setUsername("Meta");//通过token获取
logObj.setApplicationName("应用名称");
logObj.setCode(200);//状态码
logObj.setMethod(request.getMethod());//请求方法
logObj.setRequestURI(request.getRequestURI());//请求URI
logObj.setRequestData(String.valueOf(parameters));//请求数据
logObj.setRespondData(String.valueOf(result));//返回数据
logObj.setMsg(r.getMsg());//返回信息
logObj.setCreateTime(new Date());//创建时间
//异步入库
log.info("打印当前A线程名称:{}", Thread.currentThread().getName());
logService.insert(logObj);
} catch (Exception ex) {
log.error("日志入库异常:", ex);
}
return result;
}
}
AsyncConfig.java
异步方法配置需要在@SpringBootApplication注解上方加上@EnableAsync注解开启异步线程
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 异步线程配置:确保执行方法和异步执行的方法不在同一个类
*/
@Configuration
public class AsyncConfig {
@Value("${spring.task.execution.pool.core-size}")
private int corePoolSize;
@Value("${spring.task.execution.pool.max-size}")
private int maxPoolSize;
@Value("${spring.task.execution.pool.queue-capacity}")
private int queueCapacity;
@Value("${spring.task.execution.thread-name-prefix}")
private String threadNamePrefix;
@Value("${spring.task.execution.pool.keep-alive}")
private int keepAliveSeconds;
@Bean("logAsync")
public Executor logAsync() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setThreadNamePrefix(threadNamePrefix);
executor.setKeepAliveSeconds(keepAliveSeconds);
/*
拒绝处理策略
CallerRunsPolicy():交由调用方线程运行,比如 main 线程。
AbortPolicy():直接抛出异常。
DiscardPolicy():直接丢弃。
DiscardOldestPolicy():丢弃队列中最老的任务。
*/
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
LogService.java
@Slf4j
@Service
public class LogService {
@Resource
private LogDao logDao;
/**
* 异步日志入库
*/
@Async("logAsync")
public void insert(Log logObj) {
log.info("打印当前B线程名称:{}",Thread.currentThread().getName());
//获取当前年月
String tableName = getYearMonth(0);
try {
logDao.insert(tableName, logObj);
} catch (BadSqlGrammarException e) {
log.error("入库异常:", e);
//如果是不存在表,则创建表
if (1146 == e.getSQLException().getErrorCode()) {
//判断下个月的日志表是否存在,如果不存在则创建
logDao.crateTable(getYearMonth(0));
//再次入库
logDao.insert(tableName, logObj);
}
} catch (Exception e) {
log.error("未知异常:", e);
}
}
//自定义方法==============================================================================================================
/**
* 获取年月
*
* @param addOrReduceMonth 正数表示后几个月,负数表示前几个月,默认0,表示当前月
*/
public static String getYearMonth(int addOrReduceMonth) {
//获取当前月份的表
Calendar cal = Calendar.getInstance();
cal.add(Calendar.MONTH, addOrReduceMonth);
SimpleDateFormat dft = new SimpleDateFormat("yyyyMM");
return dft.format(cal.getTime());
}
}
LogDao.java
@Mapper
public interface LogDao{
/**
* 创建日志表
*/
int crateTable(@Param("tableName") String tableName);
/**
* 写入日志
*/
int insert(@Param("tableName") String tableName,@Param("log") Log log);
}
LogMapper.xml
<resultMap type="com.fu.work.entity.Log" id="BaseResultMap">
<result property="id" column="id" jdbcType="BIGINT"/>
<result property="userId" column="user_id" jdbcType="VARCHAR"/>
<result property="username" column="username" jdbcType="VARCHAR"/>
<result property="applicationName" column="application_name" jdbcType="VARCHAR"/>
<result property="code" column="code" jdbcType="SMALLINT"/>
<result property="method" column="method" jdbcType="VARCHAR"/>
<result property="requestURI" column="request_uri" jdbcType="VARCHAR"/>
<result property="requestData" column="request_data" jdbcType="VARCHAR"/>
<result property="respondData" column="respond_data" jdbcType="VARCHAR"/>
<result property="msg" column="msg" jdbcType="VARCHAR"/>
<result property="errorMsg" column="error_msg" jdbcType="VARCHAR"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">id, user_id, username, application_name, code, `method`, request_uri, request_data, respond_data, msg, error_msg, create_time</sql>
<!-- 创建日志表 -->
<update id="crateTable" parameterType="String">
CREATE TABLE IF NOT EXISTS log_${tableName} (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` char(36) DEFAULT NULL COMMENT '用户ID',
`username` varchar(64) DEFAULT NULL COMMENT '用户名',
`application_name` varchar(255) DEFAULT NULL COMMENT '应用名称',
`code` smallint(6) DEFAULT NULL COMMENT '状态码',
`method` varchar(16) DEFAULT NULL COMMENT '请求方法',
`request_uri` varchar(2048) DEFAULT NULL COMMENT '请求URI',
`request_data` longtext DEFAULT NULL COMMENT '请求数据',
`respond_data` longtext DEFAULT NULL COMMENT '返回数据',
`msg` text DEFAULT NULL COMMENT '返回信息',
`error_msg` longtext DEFAULT NULL COMMENT '详细错误信息',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `create_time` (`create_time`) USING BTREE COMMENT '创建时间索引',
KEY `user_id` (`user_id`) COMMENT '用户ID索引',
KEY `application_name` (`application_name`) COMMENT '应用名称索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
</update>
<!-- 新增 -->
<insert id="insert">
insert into log_${tableName}
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="log.id != null">
id,
</if>
<if test="log.userId != null">
user_id,
</if>
<if test="log.username != null">
username,
</if>
<if test="log.applicationName != null">
application_name,
</if>
<if test="log.code != null">
code,
</if>
<if test="log.method != null">
`method`,
</if>
<if test="log.requestURI != null">
request_uri,
</if>
<if test="log.requestData != null">
request_data,
</if>
<if test="log.respondData != null">
respond_data,
</if>
<if test="log.msg != null">
msg,
</if>
<if test="log.errorMsg != null">
error_msg,
</if>
<if test="log.createTime != null">
create_time,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="log.id != null">
#{log.id},
</if>
<if test="log.userId != null">
#{log.userId},
</if>
<if test="log.username != null">
#{log.username},
</if>
<if test="log.applicationName != null">
#{log.applicationName},
</if>
<if test="log.code != null">
#{log.code},
</if>
<if test="log.method != null">
#{log.method},
</if>
<if test="log.requestURI != null">
#{log.requestURI},
</if>
<if test="log.requestData != null">
#{log.requestData},
</if>
<if test="log.respondData != null">
#{log.respondData},
</if>
<if test="log.msg != null">
#{log.msg},
</if>
<if test="log.errorMsg != null">
#{log.errorMsg},
</if>
<if test="log.createTime != null">
#{log.createTime},
</if>
</trim>
</insert>
Log.java
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Log {
private Long id;
private String userId;
private String username;
private String applicationName;
private Integer code;
private String method;
private String requestURI;
private String requestData;
private String respondData;
private String msg;
private String errorMsg;
private Date createTime;
}
application.yml
#忽略写入日志的URI
spring:
jackson:
date-format: yyyy-MM-dd hh:mm:ss
time-zone: GMT+8
task:
execution:
#异步线程名称
thread-name-prefix: log-async
pool:
core-size: 8
max-size: 16
queue-capacity: 500
keep-alive: 60
datasource:
#可以换成其它数据库驱动
driver-class-name: org.mariadb.jdbc.Driver
url: jdbc:mariadb://localhost:3306/db_log?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
mybatis:
mapper-locations: classpath:mapper/*.xml
build.gradle(和maven的pom.xml差不多)
dependencies {//类似于maven的dependencies
implementation 'org.springframework.boot:spring-boot-starter-web'//格式为groupId:artifactId:version
compileOnly 'org.projectlombok:lombok'//仅编译时使用,不参与打包
annotationProcessor 'org.projectlombok:lombok'
implementation 'org.springframework.boot:spring-boot-starter-aop'
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
runtimeClasspath 'org.mariadb.jdbc:mariadb-java-client'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2'
}
注意事项
@Async异步不生效
1、标注@Async注解的方法必须是public void的(也就是公有的、无返回值的)
2、调用@Async注解的方法不建议在同一个方法内(即:A类调用B类的异步方法,AB不建议在同一个方法中,如需要在同一个类,需要手动获取代理)
调用方法和异步方法都在同一个类(手动获取代理)
import cn.hutool.extra.spring.SpringUtil;
import org.springframework.stereotype.Component;
@Component
public class myClass {
//同类方法a调用异步方法b
public String a(){
System.out.println("主线程开始执行......");
//手动获取代理,调用异步方法b
SpringUtil.getApplicationContext().getBean(myClass.class).a(String str);
System.out.println("主线程执行结束......");
}
@Async
public void b(String str){
Thread.sleep(3000);//休眠3秒,判断是否会阻塞主线程执行。
System.out.println("因为我是异步的,所以与主线程无关"+str);
}
}