首先需要创建一个日志收集 starter,如果对自定义starter不了解的可以参考自定starter实现
log的starter的目录结构:
首先引入依赖
<dependencies>
<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>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
创建日志starter的properties的配置类——LogProperties.java
package org.mystarter.logstringbootstarter.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
*
* properties配置文件类
*
*/
@ConfigurationProperties(prefix = "spring.log")
@Data
public class LogProperties {
//默认的请求地址
private String host = "http://127.0.0.1:9000";
}
LogService.java :
package org.mystarter.logstringbootstarter.server;
import org.mystarter.logstringbootstarter.properties.LogProperties;
/**
* service 将properties 注入到ioc 需要用到
*/
public class LogService {
// service 定义 LogProperties 的属性
public LogProperties dp;
}
?LogProxy :
package org.mystarter.logstringbootstarter.proxy;
import lombok.extern.slf4j.Slf4j;
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.mystarter.logstringbootstarter.properties.LogProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.Executor;
/**
* AOP 切面
*/
@Aspect
@Slf4j
public class LogProxy {
@Autowired
RestTemplate rt;
@Autowired
LogProperties lp;
/**
* 使用ioc的自定义线程,单独处理日志上传服务器
* logExecutor 线程的名字
*/
@Qualifier("logExecutor")
@Autowired
Executor executor;
/**
* 切点
* 扫描所有 @RequestMapping 注解类 (因为每个控制层都会有@RequestMapping注解,所以直接拦截被这个注解标注的类)
*/
@Pointcut("@within(org.springframework.web.bind.annotation.RequestMapping)")
public void request(){
}
/**
* 环绕通知
* @param point
* @return
*/
@Around("request()")
public Object logAround(ProceedingJoinPoint point) {
Object proceed = null;
try {
// 通过ProceedingJoinPoint获取Request
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
/**
* 通过HttpServletRequest获取请求url、方法本身
* 还可以获取很多-常见的有
* getMethod() :获取请求提交的方式
* getProtocol() :获取请求的协议
* getContextPath() :获取项目名称
* getServletPath() :获取servlet路径
* getRequestURI()、getRequestURL() :获取请求路径
*/
String getRequestURL = request.getRequestURL().toString();
// 这里需要我们执行拦截方法本身的代码(最后返回)
proceed = point.proceed();
// 声明一个map 放入日志信息
LinkedMultiValueMap<String, Object> mv = new LinkedMultiValueMap<String, Object>();
mv.add("RequestURL",getRequestURL);
mv.add("return",proceed);
// 打印map
log.info(String.valueOf(mv));
/**
* 异步上传服务器
* 上传的路径是LogProperties的host 值
*/
executor.execute(() -> rt.postForObject(lp.getHost()+"log",mv,String.class));
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return proceed;
}
}
LogAutoConfiguration:
package org.mystarter.logstringbootstarter.configuration;
import lombok.extern.slf4j.Slf4j;
import org.mystarter.logstringbootstarter.properties.LogProperties;
import org.mystarter.logstringbootstarter.proxy.LogProxy;
import org.mystarter.logstringbootstarter.server.LogService;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.client.RestTemplate;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 自动配置类
*/
@Configuration
@EnableConfigurationProperties({LogProperties.class})
@Slf4j
public class LogAutoConfiguration {
/**
* 服务对象
* @param logProperties
* @return
*/
@Bean
LogService logBean(LogProperties logProperties){
LogService logService = new LogService();
logService.dp = logProperties;
return logService;
}
/**
* 将RestTemplate对象 放入ioc
* @return
*/
@Bean
RestTemplate restTemplate(){
return new RestTemplate();
}
/**
* 将日志AOP切面对象放入ioc
* @return
*/
@Bean
LogProxy logProxy(){
return new LogProxy();
}
/**
* 定义一个线程
* 放入ioc
* @return
*/
@Bean("logExecutor")
Executor executor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数:线程池创建时候初始化的线程数
executor.setCorePoolSize(5);
// 最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(10);
// 缓冲队列:用来缓冲执行任务的队列
executor.setQueueCapacity(50);
// 允许线程的空闲时间60秒:当超过了核心线程之外的线程在空闲时间到达之后会被销毁
executor.setKeepAliveSeconds(60);
// 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
executor.setThreadNamePrefix("do-something-");
// 缓冲队列满了之后的拒绝策略:由调用线程处理(一般是主线程)
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
executor.initialize();
return executor;
}
@Bean
public void afterPropertiesSet(){
log.info("日志准备就绪");
}
}
最后打包成启动器(日志收集starter完成)
准备日志服务(springboot项目)
项目结构:
引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
日志服务只提供一个接收日志的方法(既然都可以接收到日志,后续就可以对这些日志为所欲为,比如放入redis中)
修改运行端口:
????????application.properties
# 应用服务 WEB 访问端口
server.port=9000
LogServiceController :
package com.example.logservice.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* 接收日志 controller
* */
@RestController
@RequestMapping("log")
@Slf4j
public class LogServiceController {
@PostMapping
public String logService(@RequestParam Map<String,Object> m){
// 这里我只打印接收到的日志
log.info("接收的日志为"+m);
return "ok";
}
}
到此日志服务就准备好了
创建测试项目:
项目结构:
引入依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入日志starter -->
<dependency>
<groupId>org.my</groupId>
<artifactId>log-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
application.properties中配置:
# 日志服务器的地址 (因为我的logService端口是9000并且在本地所以不用改配置)
spring.log.host=http://127.0.0.1:9000
编写测试controller
package com.example.test.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Test controller
*/
@RestController
@RequestMapping("demos")
public class TestController {
@GetMapping
List<Map<String,Object>> test(String msg){
// 封装一个返回值并返回
List l=new ArrayList();
Map m=new HashMap();
m.put("key",msg);
l.add(m);
return l;
}
}
到这测试项目也完成了
测试项目:运行日志服务和测试项目,访问测试项目的API,在日志服务控制台中就能看到打印的日志信息
到这里就完成了
设计这个日志收集实现流程为:
??????? 请求项目资源,被LogStarter的AOP拦截器拦截,通过AOP环绕通知中ProceedingJoinPoint获取HttpServletRequest然后拿到请求信息,发送给日志服务器logService,日志服务器收到日志后就可以做相应处理了。
源码:https://gitee.com/feiliangren/log-service.git
|