入参出参日志
我们日常开发中日志是不可缺少的一部分, 如mini-cloud架构图所示,大型系统一般可用elk 等进行日志收集 中小型系统也可以用spring-boot-admin 等进行收集,但我们业务场景经常 会有一种需求,就是一些重要入参出参接口希望按照url 进行收集并便于以后排查分析 比较典型的就是金融产品或者银行产品扣款,出账,转账,扣款等
期望效果
我们可能会希望通过一个url 或者关联参数定位查询某接口入参出参,比如转账例子,我们希望单独看转账接口得入出参日志
url: /transfer args: {"transfer":1,"amount":200,"to":2} response: {"status":200,"msg":"转账成功"} keyword:{"转账"} description:"转账接口记录"
架构图
源码
共通部分aop+自定义注解
我们在共同中添加common-log模块,并添加spring.factories 自动注入,目录结构如下:
pom.xml
<dependencies>
<!--web 模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!--rocketmq依赖-->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
</dependencies>
IOLogRecordDTO.java
@Builder
@Getter
@Setter
public class IOLogRecordDTO implements Serializable {
private Long timestamp ;
private String method;
private String url ;
private String contentType;
private String args ;
private Object response ;
private String dateTime;
private String keyword ;
private String description;
}
IOLogRecorder.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface IOLogRecorder {
String keyword() default "";
String descrition() default "";
}
IOLogAspect.java
@Aspect
public class IOLogAspect {
@Autowired
RocketMQTemplate rocketMQTemplate;
@Pointcut("@annotation(com.minicloud.common.log.annotation.IOLogRecorder)")
public void pointCut() {
}
@Around("pointCut()")
public Object record(ProceedingJoinPoint joinPoint) throws Throwable {
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
String url = request.getRequestURI();
String contentType= request.getHeader("content-type");
String method = request.getMethod();
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
IOLogRecorder ioLogRecorder = methodSignature.getMethod().getAnnotation(IOLogRecorder.class);
System.out.println(methodSignature.getMethod().getName());
Object[] args = joinPoint.getArgs();
String inArgs = JSONUtil.toJsonStr(args);
Object response = joinPoint.proceed();
long timestamp = System.nanoTime();
IOLogRecordDTO ioLogRecordDTO = IOLogRecordDTO.builder().keyword(ioLogRecorder.keyword()).description(ioLogRecorder.descrition()).url(url).contentType(contentType).method(method).args(inArgs).response(response).dateTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))).timestamp(timestamp).build();
rocketMQTemplate.send("iolog", MessageBuilder.withPayload(ioLogRecordDTO).build());
return response;
}
}
IOLogConfigration.java
@Configuration
public class IOLogConfigration {
@Bean
public IOLogAspect ioLogAspect(){
return new IOLogAspect();
}
}
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.minicloud.common.log.config.IOLogConfigration
rockemq搭建
本篇暂时不提及rocketmq搭建,因为涉及内容太多,会有单独篇幅介绍
日志数据库创建
日志存储可以是数据库,文件,缓存等,本篇使用的是mysql数据库
CREATE TABLE `iolog` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '住建',
`timestamp` bigint(20) DEFAULT NULL COMMENT '时间戳',
`method` varchar(10) DEFAULT '' COMMENT '请求方式',
`url` varchar(100) DEFAULT NULL COMMENT '请求url',
`content_type` varchar(30) DEFAULT '' COMMENT '数据类型',
`args` varchar(500) DEFAULT '' COMMENT '请求参数',
`response` varchar(1000) DEFAULT '' COMMENT '响应',
`data_time` varchar(30) DEFAULT '' COMMENT '日志时间',
`keyword` varchar(50) DEFAULT '' COMMENT '关键字',
`description` varchar(100) DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8mb4;
mq 消费端搭建
mq消费端是一个独立的mq服务,因为以后还需要集成别的消费业务,所以独立消费端便于扩展,不与具体某业务服务耦合,具体代码如下:
pom.xml
<dependencies>
<!--注册中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--配置中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--日志拦截依赖-->
<dependency>
<groupId>org.mini-cloud</groupId>
<artifactId>mini-cloud-common-log</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.mini-cloud</groupId>
<artifactId>mini-cloud-common-fegin</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<!--common data 依赖 -->
<dependency>
<groupId>org.mini-cloud</groupId>
<artifactId>mini-cloud-common-data</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.mini-cloud</groupId>
<artifactId>mini-cloud-common-auth</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
IOLogConsumer.java
@Component
@RocketMQMessageListener(consumerGroup = "group-a", topic = "iolog",consumeMode = ConsumeMode.ORDERLY)
public class IOLogConsumer implements RocketMQListener<String> {
@Autowired
private IOLogRecordDao ioLogRecordDao;
@Override
public void onMessage(String message) {
IOLogRecordDTO ioLogRecordDTO = JSONUtil.toBean(message,IOLogRecordDTO.class);
ioLogRecordDao.insert(ioLogRecordDTO);
}
}
MiniCloudLogConsumerApplication.java
@SpringCloudApplication
@EnableCaching
@EnableMiniCloudFeignClients
@EnableMiniCloudResourceServer
public class MiniCloudLogConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(MiniCloudLogConsumerApplication.class, args);
}
}
nacos 中 mini-cloud-log-consumer-dev.yml
server:
port: 6600
tomcat:
uri-encoding: UTF-8
max-threads: 500
max-connections: 10000
accept-count: 500
spring:
shardingsphere:
props:
sql:
show: true
datasource:
names: master,slave0,slave1
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://${MYSQL_HOST:192.168.1.2}:${MYSQL_PORT:3306}/${MYSQL_DB:mini_cloud_log}?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&allowMultiQueries=true&allowPublicKeyRetrieval=true
username: root
password: root
slave0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://${MYSQL_HOST:192.168.1.2}:${MYSQL_PORT:3306}/${MYSQL_DB:mini_cloud_log}?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&allowMultiQueries=true&allowPublicKeyRetrieval=true
username: root
password: root
slave1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://${MYSQL_HOST:192.168.1.2}:${MYSQL_PORT:3306}/${MYSQL_DB:mini_cloud_log}?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&allowMultiQueries=true&allowPublicKeyRetrieval=true
username: root
password: root
sharding:
master-slave-rules:
master:
master-data-source-name: master
slave-data-source-names: slave0,slave1
服务日志发送方集成
日志发送方其实就是实际的各个业务端服务,本文在upms 服务的一个角色分页接口加上日志注解,只要添加 @IOLogRecorder 变可自动收集入参出参发送到mq,如下:
重启服务以及查看结果
我们在业务端请求一个角色分页列表接口,来看看效
业务消费端接收到消息
来看看response
{
"headers": { },
"statusCodeValue": 200,
"body": {
"data": [
{
"roleId": 33,
"roleDesc": "最大权限",
"roleCode": "SUPER_ADMIN",
"roleName": "超级管理员",
"tenantId": 1,
"upmsPermDTOS": [ ]
},
{
"roleId": 34,
"roleDesc": "普通用户1",
"roleCode": "USER1",
"roleName": "普通用户1",
"tenantId": 1,
"upmsPermDTOS": [ ]
},
{
"roleId": 35,
"roleDesc": "普通用户2",
"roleCode": "USER2",
"roleName": "普通用户2",
"tenantId": 1,
"upmsPermDTOS": [ ]
}
],
"total": 3,
"size": 10,
"page": 1
},
"statusCode": "OK"
}
看结果已经通过mq消费端保存到数据库里了,以后可以通过各个字段进行查询操作