1.作用: springboot接口请求日志自动生成,实现接口日志自动埋点生成 1.统一日志生成格式;—方便查看 2.汇总请求参数和请求结果一次性输出日志数据 ,方便查询问题,节省查询请求问题时间;—很直观的日志,前后端问题排查快 3.通过日志自动生成减少编写日志时间,减少人力成本;—省编码时间 4.记录用户行为轨迹,记录接口时间,为后续风险监控,用户行为统计分析做铺垫;—记录数据
2.原理: 通过面向切面编程的形式,在不影响原有项目的业务(包括加解密)的同时,进行日志埋点
代码 配置模块
3.代码: 3.1.config层 1.bean类型调度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 /** * 获取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); } }
3.2核心逻辑 ps:其中的ApiOperation 是swagger的注解,可以自定义注解实现自己的参数配置, 这里只是为了方便使用已有的swagger的ApiOperation 注解 的value(接口说明)和notes(接口发布说明)的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 /** * * 监控埋点 * 打印请求和响应信息 */ @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); } } }
3.3 mq层 : 异步处理日志通用逻辑,使用单例模式实现一个工厂实例,用Spring来获取具体的跟踪服务实现,以此来提供高度解耦和可扩展的服务注册与获取机制去处理指定接口自定义服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @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); } } }
3.4service层: 通用处理,自定义处理,单例工厂 1.单例工厂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 ** * 单例工厂类 * * 追踪日志,触发自定义模块功能 */ 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.通用日志处理
1 2 3 4 public interface TrackingCommonService { public void commonTracking(LogVo logVo); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @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.自定义指定接口日志处理
1 2 3 4 5 6 7 public interface TrackingService { /** * 日志触发自定义内容:数据分析,数据统计,风险监控,发送mq,保存数据入库等等 * @param logVo */ void changeByTrackLog(LogVo logVo); }
可以增加无数个自定义指定接口日志处理实现自己的处理日志方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @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.效果图