Design a general log audit scheme

1. Demand

1. Record detailed log information with the least code and without changing the business logic code

2. Support log output in file form or database storage, and switch flexibly through configuration file.

2. Implementation

To achieve this, annotation and aop must be used to intercept and record logs, so as to minimize changes to business code.

You only need one line of code to use:

@RequestMapping(value="/test", method = RequestMethod.GET)

@AuditLog(moduleName = "system module", operation = "'test '+" username ")

public Result list(String username) { return Result.ok(username); }

Aop interception class:

//Determine whether the log audit function is enabled. spring.audit.log.enabled is defined in application.properties
@ConditionalOnProperty(name = "spring.audit.log.enabled", havingValue = "true")
//Indicates that this bean will be built only when HttpServletRequest or RequestContextHolder is referenced in the middle of the project
@ConditionalOnClass({ HttpServletRequest.class, RequestContextHolder.class })

@Component
@Slf4j
@Aspect
// Judge whether the log audit function is enabled
@ConditionalOnProperty(name = "spring.audit.log.enabled", havingValue = "true")
// Indicates that this bean will be built only when HttpServletRequest or RequestContextHolder is referenced in the middle of the project
@ConditionalOnClass({ HttpServletRequest.class, RequestContextHolder.class })
public class AuditLogAspect {
    public static final String USERID_HEADER = "userid-header";
    private static final String AUDIT_OBJ = "audit";
    private static ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<>();
    private static final String START_TIME = "startTime";
 
    @Autowired
    private AuditService auditService;
    /**
     * Used for SpEL expression parsing
     */
    private SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
    /**
     * Used to get the method parameter definition name
     */
    private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

    @Pointcut("@annotation(com.xxx.log.annoation.AuditLog)")
    public void ServiceAspect() {

    }

    public AuditLogAspect() {
	super();
    }

    @Before("ServiceAspect()")
    public void beforeMethod(JoinPoint joinPoint) {
	long startTime = System.currentTimeMillis();

	MethodSignature signature = (MethodSignature) joinPoint.getSignature();
	Method method = signature.getMethod();
	AuditLog auditLog = method.getAnnotation(AuditLog.class);
	Audit audit = recordInfo(joinPoint, auditLog);
	Map<String, Object> threadInfo = new HashMap<>();
	threadInfo.put(START_TIME, startTime);
	threadInfo.put(AUDIT_OBJ, audit);
	threadLocal.set(threadInfo);

    }
 

    @AfterReturning(value = "ServiceAspect()", returning = "returnValue")
    public void afterReturning(JoinPoint point, Object returnValue) {

	Map<String, Object> threadInfo = threadLocal.get();
	long takeTime = System.currentTimeMillis()
		- (long) threadInfo.getOrDefault(START_TIME, System.currentTimeMillis());
	Audit audit = (Audit) threadInfo.get(AUDIT_OBJ);
	audit.setElapsedTime(takeTime);
	audit.setError((byte) 0);
	threadLocal.remove();
	saveLogInfo(audit);

    }

    @AfterThrowing(value = "ServiceAspect()", throwing = "throwable")
    public void doAfterThrowing(JoinPoint point, Throwable throwable) {

	Map<String, Object> threadInfo = threadLocal.get();
	long takeTime = System.currentTimeMillis()
		- (long) threadInfo.getOrDefault(START_TIME, System.currentTimeMillis());
	Audit audit = (Audit) threadInfo.get(AUDIT_OBJ);
	audit.setElapsedTime(takeTime);
	audit.setError((byte) 1);

	/**
	 * Convert stack information to string
	 */
	StringWriter sw = new StringWriter();
	PrintWriter pw = new PrintWriter(sw);
	throwable.printStackTrace(pw);
	/**
	 * Stack information
	 */
	audit.setStatck(sw.toString());
	threadLocal.remove();
	saveLogInfo(audit);

    }

    private void saveLogInfo(Audit audit) {

	auditService.save(audit);

    }

    /**
     * Parse spEL expression
     */
    private String getValBySpEL(String spEL, MethodSignature methodSignature, Object[] args) {
	// Get method parameter name array
	String[] paramNames = nameDiscoverer.getParameterNames(methodSignature.getMethod());
	if (paramNames != null && paramNames.length > 0) {
	    Expression expression = spelExpressionParser.parseExpression(spEL);
	    // Expression context object for spring
	    EvaluationContext context = new StandardEvaluationContext();
	    // Assign a value to the context
	    for (int i = 0; i < args.length; i++) {
		context.setVariable(paramNames[i], args[i]);
	    }
	    return expression.getValue(context).toString();
	}
	return "";
    }

    /**
     * Construction of audit object
     */
    private Audit recordInfo(JoinPoint joinPoint, AuditLog auditLog) {
	Audit audit = new Audit();
	audit.setTimestamp(LocalDateTime.now());
	audit.setModuleName(auditLog.moduleName());
	MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
	audit.setClassName(methodSignature.getDeclaringTypeName());
	audit.setMethodName(methodSignature.getName());
	HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
		.getRequest();
	String queryStr = request.getQueryString();
	// userId is stored in request header
	String userId = request.getHeader(USERID_HEADER);
	audit.setUserId(userId);
	if (StringUtils.isNotEmpty(queryStr)) {
	    audit.setRequestUrl(request.getRequestURI() + "?" + queryStr);
	} else {
	    audit.setRequestUrl(request.getRequestURI());
	}

	String operation = auditLog.operation();
	if (operation.contains("#")) {
	    // Get method parameter value
	    Object[] args = joinPoint.getArgs();
	    if (!ObjectUtils.isEmpty(args)) {
		operation = getValBySpEL(operation, methodSignature, args);
	    }
	}
	audit.setOperation(operation);

	return audit;
    }
}

Database logging implementation, only when spring. Audit. Log. Log type = DB

@Service
@Slf4j
//Indicates that this spring bean is executed only when spring. Audit. Log. Log type = DB
@ConditionalOnProperty(name = "spring.audit.log.log-type", havingValue = "db")
@ConditionalOnClass(JdbcTemplate.class)
public class DbAuditServiceImpl implements AuditService {
    private static final String INSERT_SQL = " insert into sys_logger " +
            " (module_name, class_name, method_name, elapsed_time, request_url, error, statck, operation, timestamp,user_id) " +
            " values (?,?,?,?,?,?,?,?,?,?)";
    @Autowired
    private  JdbcTemplate jdbcTemplate;
    @PostConstruct
    public void init() {
        String sql = "CREATE TABLE IF NOT EXISTS `sys_logger`  (\n" +
                "  `id` int(11) NOT NULL AUTO_INCREMENT,\n" +
                "  `module_name` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT 'Module name',\n" +
                "  `class_name` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'Class name',\n" +
                "  `method_name` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'Method name',\n" +
                "  `elapsed_time` int(11) NULL COMMENT 'time consuming',\n" +
                "  `request_url` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT 'request Url',\n" +
                "  `error` int(11) NULL COMMENT 'Error flag',\n" +
                "  `statck` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT 'error message',\n" +
                "  `operation` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'Operation information',\n" +
                "  `timestamp` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'Creation time',\n" +
                "  `user_id` int(11) NULL COMMENT 'user ID',\n" +
                "  PRIMARY KEY (`id`) USING BTREE\n" +
                ") ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;";
        this.jdbcTemplate.execute(sql);
    }
    /**
     * @Async Indicates abnormal insertion
     */
    @Async
    @Override
    public void save(Audit audit) {
        this.jdbcTemplate.update(INSERT_SQL
                , audit.getModuleName(), audit.getClassName(), audit.getMethodName(),audit.getElapsedTime()
                , audit.getRequestUrl(), audit.getError(), audit.getStatck() 
                , audit.getOperation(), audit.getTimestamp(),audit.getUserId());
    }
}

Log file recording method

@Slf4j
@Service
@ConditionalOnProperty(name = "spring.audit.log.log-type", havingValue = "logger", matchIfMissing = true)
public class LogAuditServiceImpl implements AuditService {
    private static final DateTimeFormatter OF_PATTERN = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
    private static final String MSG_PATTERN = "{}|{}|{}|{}|{}|{}|{}";
    @Override
    public void save(Audit audit) {
	if (1 == audit.getError()) {
	    log.error(MSG_PATTERN, audit.getTimestamp().format(OF_PATTERN),
		    audit.getModuleName(),audit.getUserId(), audit.getRequestUrl(), audit.getOperation(), audit.getElapsedTime(),
		    audit.getStatck());
	} else {
	    log.debug(MSG_PATTERN, audit.getTimestamp().format(OF_PATTERN),
		    audit.getModuleName(), audit.getUserId(),audit.getRequestUrl(), audit.getOperation(), audit.getElapsedTime(),
		    audit.getError());
	}
    }

}

Tags: Programming Spring Database SQL

Posted on Sun, 10 May 2020 07:46:16 -0700 by azul85