文章目录
1.作用:
springboot接口请求日志自动生成,实现接口日志自动埋点生成
1.统一日志生成格式;---方便查看
2.汇总请求参数和请求结果一次性输出日志数据 ,方便查询问题,节省查询请求问题时间;---很直观的日志,前后端问题排查快
3.通过日志自动生成减少编写日志时间,减少人力成本;---省编码时间
4.记录用户行为轨迹,记录接口时间,为后续风险监控,用户行为统计分析做铺垫;---记录数据
2.原理:
通过面向切面编程的形式,在不影响原有项目的业务(包括加解密)的同时,进行日志埋点
代码
配置模块
3.代码:
一.config层
1.bean类型调度
/**
* 获取spring bean
*
*/
@Component
public class SpringContextAware implements ApplicationContextAware {
/**
* Spring应用上下文
*/
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextAware.context = applicationContext;
}
/**
* 获取Spring应用上下文
* @return
*/
public static ApplicationContext getApplicationContext() {
return context;
}
/**
* 获取对象
* @param name
* @return
* @throws BeansException
*/
public static Object getBean(String name) throws BeansException {
return context.getBean(name);
}
}
2.核心逻辑
ps:其中的ApiOperation 是swagger的注解,可以自定义注解实现自己的参数配置,
这里只是为了方便使用已有的swagger的ApiOperation 注解 的value(接口说明)和notes(接口发布说明)的数据
/**
*
* 监控埋点
* 打印请求和响应信息
*/
@Aspect
@Component
public class WebLogAspect {
@Autowired
private RedisTokenLoader redisTokenLoader;
@Autowired
private MqProducer mqProducer;
// 拿到日志对象
// slf4j的日志对象
private final Logger log = LoggerFactory.getLogger(WebLogAspect.class);
@Pointcut("execution( * com.jt.saas..*Controller.*(..))")
public void webLog() {
}
/**
* 读取会话中的token去取redis缓存信息
* 有效期受控于redis.timeout
*
* @return
*/
protected LoginInfoVo readLoginInfo(HttpServletRequest request) {
String token = request.getHeader(HeaderConstant.HEADER_MINI_TOKEN);
if (token == null) {
// 非拦截接口的token校验
String authorization = request.getHeader(HttpConstant.AUTHORIZATION);
if (authorization != null) {
if (authorization.length() > 7) {
token = authorization.substring(7);
}
}
}
if (StringUtils.isBlank(token)) {
return null;
}
return redisTokenLoader.readToken(token);
}
// 切点实现
@Around("webLog()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
// 记录开始时间
long start = System.currentTimeMillis();
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
assert servletRequestAttributes != null;
HttpServletRequest request = servletRequestAttributes.getRequest();
LoginInfoVo loginInfo = readLoginInfo(request);
// 获取方法名
String className = pjp.getTarget().getClass().getName();
// 获取执行的方法名称
String methodName = pjp.getSignature().getName();
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
// 定义返回参数
Object result = null;
// 获取方法入参
// Object[] param = pjp.getArgs();
// String requestBody = convertFormToString(request);
// 执行目标方法
result = pjp.proceed();
ObjectMapper objectMapper = new ObjectMapper();
String bodyData = null;
try {
bodyData = objectMapper.writeValueAsString(result);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
JSONObject param= convertFormToJson(request);
LogVo logVo=new LogVo();
logVo.setUserId(loginInfo != null&&loginInfo.getMemberId()!=null ? loginInfo.getMemberId() : null);
logVo.setUserName(loginInfo != null&&loginInfo.getNickname2()!=null ? loginInfo.getNickname2() : null);
logVo.setMobile(loginInfo != null&&loginInfo.getMobile()!=null ? loginInfo.getMobile() : null);
logVo.setModelName(apiOperation != null&&apiOperation.value()!=null ?apiOperation.value(): null);
logVo.setRemark(apiOperation != null&&apiOperation.notes()!=null ?apiOperation.notes(): null);
logVo.setUsedTime(System.currentTimeMillis() - start);
logVo.setParamData(param);
logVo.setResultData(bodyData.length() > 2000 ? "数据太大截取2000数据:"+bodyData.substring(0, 2000) :result);
logVo.setMethodName(className+"."+methodName);
logVo.setIp(request.getRemoteAddr());
logVo.setUrlData(request.getRequestURL().toString());
// 日志输出
log.info("userRequest:{}",JSON.toJSONString(logVo));
//每个项目自定义自己的mq去处理自定义日志统计或者分析行为
mqProducer.sendDelay("MINI_LOGVO",JSON.toJSONString(logVo),100);
return result;
}
private String convertFormToString(HttpServletRequest request) {
Map<String, String> result = new HashMap<>(8);
Enumeration<String> parameterNames = request.getParameterNames();
while (parameterNames.hasMoreElements()) {
String name = parameterNames.nextElement();
result.put(name, request.getParameter(name));
}
try {
return JSON.toJSONString(result);
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
private JSONObject convertFormToJson(HttpServletRequest request) {
try {
JSONObject jsonObject = new JSONObject();
Enumeration<String> parameterNames = request.getParameterNames();
while (parameterNames.hasMoreElements()) {
String name = parameterNames.nextElement();
jsonObject.put(name, request.getParameter(name));
}
return jsonObject;
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
}
二. mq层 :
异步处理日志通用逻辑,使用单例模式实现一个工厂实例,用Spring来获取具体的跟踪服务实现,以此来提供高度解耦和可扩展的服务注册与获取机制去处理指定接口自定义服务
@Component
public class TrackingMq {
@Autowired
private TrackingCommonService trackingCommonService;
@RabbitListener(bindings = {@QueueBinding(value = @Queue(value = "MINI_LOGVO", durable = "true"), exchange =
@Exchange(value = "saasExchange", type = "x-delayed-message"), key = "MINI_LOGVO")},containerFactory = "firstFactory")
@RabbitHandler
public void process(Message message) throws Exception {
String msg = new String(message.getBody(), "UTF-8");
LogVo logVo = JSON.parseObject(msg, LogVo.class);
//通用的分析功能
trackingCommonService.commonTracking(logVo);
//自定义分析模块》按MethodName执行自定义功能;按需对接口进行自定义处理
TrackingService trackingService= TrackingFactory.getInstance().get(logVo.getMethodName());
if(trackingService!=null){
trackingService.changeByTrackLog(logVo);
}
}
}
三.service层:
通用处理,自定义处理,单例工厂
1.单例工厂
**
* 单例工厂类
*
* 追踪日志,触发自定义模块功能
*/
public class TrackingFactory {
private Map<String, TrackingService> map= new HashMap<>();
public static class Holder {
public static TrackingFactory instance = new TrackingFactory();
}
public static TrackingFactory getInstance() {
return Holder.instance;
}
public TrackingService get(String methodName) {
return map.get(methodName);
}
public TrackingFactory() {
TrackingService listRankActivityRuleTrackingServiceImpl = (ListRankActivityRuleTrackingServiceImpl) SpringContextAware.getBean("listRankActivityRuleTrackingServiceImpl");
map.put("com.jt.saas.mini.controller.activity.ActivityController.listRankActivityRule", listRankActivityRuleTrackingServiceImpl);
}
}
2.通用日志处理
public interface TrackingCommonService {
public void commonTracking(LogVo logVo);
}
@Service
public class TrackingCommonServiceImpl implements TrackingCommonService{
private final Logger log = LoggerFactory.getLogger(TrackingCommonServiceImpl.class);
public void outTimeCount(LogVo logVo){
if(logVo.getUsedTime()>1000){
log.error("日志分析:超时1s的接口:{},{}",logVo.getModelName(),logVo.getUsedTime());
}
}
@Override
public void commonTracking(LogVo logVo) {
outTimeCount(logVo);
}
}
3.自定义指定接口日志处理
public interface TrackingService {
/**
* 日志触发自定义内容:数据分析,数据统计,风险监控,发送mq,保存数据入库等等
* @param logVo
*/
void changeByTrackLog(LogVo logVo);
}
可以增加无数个自定义指定接口日志处理实现自己的处理日志方式
@Service
public class ListRankActivityRuleTrackingServiceImpl implements TrackingService {
private final Logger log = LoggerFactory.getLogger(ListRankActivityRuleTrackingServiceImpl.class);
private Map<String, Integer> map= new HashMap<>();
@Override
public void changeByTrackLog(LogVo logVo) {
//发送mq
//发送kafaka
//发送数据库
//统计用户访问次数
Integer i=map.get(logVo.getMethodName());
if(i==null){
i=1;
}else{
i=i+1;
}
map.put(logVo.getMethodName(), i);
log.info("日志分析:{}:访问次数:{}",logVo.getModelName(),i);
}
}
4.效果图
5.声明
本文内容均为Bright_ Chen原创,版权均属于Bright_ Chen所有,未经授权,不得转载、摘编、复制或建立镜像。我们尊重他人知识版权,如有引用、摘录,请务必注明来源及作者,否则我们将依法追究其侵权责任。
根据《中华人民共和国著作权法》、《信息网络传播权保护条例》等相关法律法规,任何单位或个人不得擅自复制、转载、链接、抓取或以其他方式使用本站内容;已授权或合作的单位或个人,应在授权范围内使用,并注明来源。
对于违反本声明者,Bright_ Chen保留追究其法律责任的权利。同时,对于恶意抄袭、盗版等侵犯版权的个人和组织,我们将采取包括但不限于法律诉讼、公开曝光等措施维护合法权益。
我们鼓励和支持合法合规的信息共享,对于希望合法使用本文内容的机构或个人,请事先通过以下联系方式与我们取得联系,获取合法授权:
联系邮箱:1024347104@qq.com
感谢您对原创内容的尊重与支持!