Scene:
In projects, system operation records are usually saved as logs in order to record user operation behaviors and analyze system problems when system failures occur. In order to maintain the low intrusion of code and the standardization of log data between microservices, You can use custom log annotations to achieve the above purpose, and cooperate with Redis’s publish and subscribe model to achieve decoupling between applications (message middleware MQ, Spring Event, Spring asynchronous processing and other mechanisms can also be used).
- Log custom annotations
Can be defined such as title, moduleName (module name), operationType (operation type), etc.
@Target({<!-- -->ElementType.PARAMETER, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Log {<!-- --> String title() default ""; ModuleEnum moduleName() default ModuleEnum.OTHER; OperationTypeEnum operationType() default OperationTypeEnum.OTHER; boolean isSaveRequestData() default true; boolean isSaveResponseData() default true; }
- module name enum
public enum ModuleEnum {<!-- --> RBAC("RBAC", "RBAC basic service"), OTHER("OTHER","Other"); private final String type; private final String desc; ModuleEnum(String type, String desc) {<!-- --> this.type = type; this.desc = desc; } public String getType() {<!-- --> return type; } public String getDesc() {<!-- --> return desc; } }
- Business operation type enumeration
public enum OperationTypeEnum {<!-- --> OTHER("OTHER","Other operations"), INSERT("INSERT","New"), UPDATE("UPDATE","Update"), DELETE("DELETE","Delete"), GRANT("GRANT","Authorization"), EXPORT("EXPORT","Export"), IMPORT("IMPORT","import"), LOGIN("LOGIN","Login"), LOGOUT("LOGOUT","Exit"); private final String type; private final String desc; OperationTypeEnum(String type, String desc) {<!-- --> this.type = type; this.desc = desc; } public String getType() {<!-- --> return type; } public String getDesc() {<!-- --> return desc; } }
- Log annotation aspect
@Slf4j @Aspect @Component @RequiredArgsConstructor public class LogAspect {<!-- --> private final MessagePublisher messagePublisher; /** Exclude sensitive attribute fields */ public static final String[] EXCLUDE_PROPERTIES = {<!-- --> "password", "oldPassword", "newPassword", "confirmPassword" }; /** * Executed after processing the request * * @param joinPoint cut point */ @AfterReturning(pointcut = "@annotation(log)", returning = "result") public void doAfterReturning(JoinPoint joinPoint, Log log, Object result) {<!-- --> handleLog(joinPoint, log, null, result); } /** * Intercept abnormal operations * * @param joinPoint cut point * @param e exception */ @AfterThrowing(value = "@annotation(log)", throwing = "e") public void doAfterThrowing(JoinPoint joinPoint, Log log, Exception e) {<!-- --> handleLog(joinPoint, log, e, null); } protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) {<!-- --> try {<!-- --> // Get the current user IUser loginUser = UserThreadLocal.get(); SysLogBo sysLogBo = new SysLogBo(); sysLogBo.setStatus(StatusEnum.SUCCESS.getStatus()); //requested address String ip = NetWorkUtil.getIpAddr(HttpServletUtil.getRequest()); sysLogBo.setIp(ip); sysLogBo.setUrl(StringUtils.substring(HttpServletUtil.getRequest().getRequestURI(), 0, 255)); if (loginUser != null) {<!-- --> sysLogBo.setOperatorName(loginUser.getUsername()); } if (e != null) {<!-- --> sysLogBo.setStatus(StatusEnum.FAIL.getStatus()); sysLogBo.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000)); } //Set method name String className = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); sysLogBo.setMethod(className + "." + methodName + "()"); //Set request method sysLogBo.setRequestMethod(HttpServletUtil.getRequest().getMethod()); // Process the parameters on the setting annotation getControllerMethodDescription(joinPoint, controllerLog, sysLogBo, jsonResult); //Message occurs messagePublisher.publishMessage("yyou-log",GsonUtil.bean2json(sysLogBo)); } catch (Exception exp) {<!-- --> //Record local exception log log.error("Exception message:{}", exp.getMessage()); exp.printStackTrace(); } } /** * Get the description information of the method in the annotation for Controller layer annotation * * @param log log * @param sysLogBo operation log * @throwsException */ public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysLogBo sysLogBo, Object jsonResult) throws Exception {<!-- --> //Set action action sysLogBo.setOperationType(log.operationType().name()); //Set the operator category sysLogBo.setModuleName(log.moduleName().getDesc()); // Do you need to save the request, parameters and values? if (log.isSaveRequestData()) {<!-- --> // Get parameter information and pass it into the database. setRequestValue(joinPoint, sysLogBo); } // Do you need to save the response, parameters and values? if (log.isSaveResponseData() & amp; & amp; Objects.nonNull(jsonResult)) {<!-- --> sysLogBo.setResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000)); } } /** * Get the requested parameters and put them in the log * * @param sysLogBo operation log * @throws Exception exception */ private void setRequestValue(JoinPoint joinPoint, SysLogBo sysLogBo) throws Exception {<!-- --> String requestMethod = sysLogBo.getRequestMethod(); if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {<!-- --> String params = argsArrayToString(joinPoint.getArgs()); sysLogBo.setParam(StringUtils.substring(params, 0, 2000)); } else {<!-- --> Map<?, ?> paramsMap = HttpServletUtil.getParamMap(HttpServletUtil.getRequest()); sysLogBo.setParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter()), 0, 2000)); } } /** * Parameter assembly */ private String argsArrayToString(Object[] paramsArray) {<!-- --> String params = ""; if (paramsArray != null & amp; & amp; paramsArray.length > 0) {<!-- --> for (Object o : paramsArray) {<!-- --> if (Objects.nonNull(o) & amp; & amp; !isFilterObject(o)) {<!-- --> try {<!-- --> String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter()); params + = jsonObj.toString() + " "; } catch (Exception e) {<!-- --> } } } } return params.trim(); } /** * Ignore sensitive attributes */ public PropertyPreExcludeFilter excludePropertyPreFilter() {<!-- --> return new PropertyPreExcludeFilter().addExcludes(EXCLUDE_PROPERTIES); } /** * Determine whether filtering is required. * * @param o Object information. * @return If it is an object that needs to be filtered, return true; otherwise, return false. */ @SuppressWarnings("rawtypes") public boolean isFilterObject(final Object o) {<!-- --> Class<?> clazz = o.getClass(); if (clazz.isArray()) {<!-- --> return clazz.getComponentType().isAssignableFrom(MultipartFile.class); } else if (Collection.class.isAssignableFrom(clazz)) {<!-- --> Collection collection = (Collection) o; for (Object value : collection) {<!-- --> return value instanceof MultipartFile; } } else if (Map.class.isAssignableFrom(clazz)) {<!-- --> Map map = (Map) o; for (Object value : map.entrySet()) {<!-- --> Map.Entry entry = (Map.Entry) value; return entry.getValue() instanceof MultipartFile; } } return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse || o instanceof BindingResult; } }
- Redis message producer service
@Component public class MessagePublisher {<!-- --> private final RedisTemplate<String, String> redisTemplate; public MessagePublisher(RedisTemplate<String, String> redisTemplate) {<!-- --> this.redisTemplate = redisTemplate; } public void publishMessage(String topic,String message) {<!-- --> redisTemplate.convertAndSend(topic, message); } }
- Redis message configuration
@Configuration public class RedisMessageConfig {<!-- --> @Bean public ChannelTopic topic() {<!-- --> return new ChannelTopic("yyou-log"); // Define the channel name of the message queue } @Bean public MessageListenerAdapter messageListenerAdapter(MessageConsumer messageConsumer) {<!-- --> return new MessageListenerAdapter(messageConsumer); } @Bean public RedisMessageListenerContainer redisContainer(RedisConnectionFactory redisConnectionFactory, MessageListenerAdapter messageListenerAdapter, ChannelTopic topic) {<!-- --> RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(redisConnectionFactory); container.addMessageListener(messageListenerAdapter, topic); return container; } }
- Redis message consumer service
@Slf4j @Component @RequiredArgsConstructor(onConstructor_ = @Autowired) public class MessageConsumer implements MessageListener {<!-- --> private final SysLogService sysLogService; @Override public void onMessage(Message message, byte[] pattern) {<!-- --> String data = message.toString(); log.info("Received message: " + data); sysLogService.add(GsonUtil.json2bean(data, SysLogBo.class)); } }