The operation log

Operation logs record what interfaces users request and what they do. A common and simple implementation is through Spring’s AOP + custom annotations.

Identify the custom annotation on the HandlerMethod method and set some custom base properties on the annotation, such as the string property operation: “Deleted user information”. In the Aop aspect, the annotation is captured, the predefined information is read, and the current user’s Token is used to identify the user performing the operation and the operation being performed. You can create an Operation log.

Not enough detailed

One of the obvious disadvantages of this approach is that the contents of the log are fixed. We only know “who deleted users”, but not which users were deleted. The “deleted user” request information is contained in the request body or query parameters. In the Aop aspect, the parameters of request/ Response /Handler and other objects can be obtained, but the parameters of different business interfaces are generally different, which leads to the same day logger cannot be shared. Write a logging Aop for each interface? This is obviously not a good idea.

SpEL

Spring Expression Language (SpEL) : Spring Expression Language. Popular understanding, is can pass some string expression, complete some “programming” function. For example, read/set properties of some objects.

With this in hand, we can set up some string “expressions” in the log annotations to read certain attributes (body/header/param). This makes it very flexible. Different interfaces and expressions can read different information and generate different logs.

Learn more about SpEL in the official documentation, which won’t cover much here.

Docs. Spring. IO/spring – fram…

demo

OperationLog

Custom log annotations

package io.springcloud.web.log;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


/** ** Access log annotations *@author KevinBlandy
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {
	
	/** * SpEL template expression * All arguments to the current HandlerMethod are put into the SpEL Context. The name is the method parameter name. *@return* /
	String expression(a);
}

Copy the code

OperationLogAop

Aop implementation of logging

package io.springcloud.web.log;

import java.lang.reflect.Method;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import lombok.extern.slf4j.Slf4j;

@Aspect  
@Component 
@Order(-1)
@Slf4j
public class OperationLogAop {

	// Template prefix and suffix to be resolved by SpEl {{expression}}
	public static final TemplateParserContext TEMPLATE_PARSER_CONTEXT = new TemplateParserContext({{" "."}}");

	// SpEL parser
	public static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
	
	
	@Pointcut(value="@annotation(io.springcloud.web.log.OperationLog)")
	public void controller(a) {};
	
	@Before(value = "controller()")
	public void actionLog (JoinPoint joinPoint) throws Throwable {
		
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();

		/ / parameters
		Object[] args = joinPoint.getArgs();
		
		// Parameter name
		String[] parameterNames = signature.getParameterNames();

		// Target method
		Method targetMethod = signature.getMethod();

		// Log annotations on methods
		OperationLog operationLog = targetMethod.getAnnotation(OperationLog.class);

		// request
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

		// response
		// HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
		
		try {
			/** * SpEL parses the context, adds the parameters of the HandlerMethod to the context, and uses the parameter name as the KEY */
	    	EvaluationContext evaluationContext = new StandardEvaluationContext();
			for (int i = 0; i < args.length; i ++) {
				evaluationContext.setVariable(parameterNames[i], args[i]);
			}
			
	    	String logContent = EXPRESSION_PARSER.parseExpression(operationLog.expression(), TEMPLATE_PARSER_CONTEXT).getValue(evaluationContext, String.class);
	    	
			// TODO stores logs asynchronously
			
			log.info("operationLog={}", logContent);
		} catch (Exception e) {
			log.error(Operation log SpEL expression parsing exception: {}, e.getMessage()); }}}Copy the code

Controller

package io.springcloud.web.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import lombok.Data;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import io.springcloud.web.log.OperationLog;

import java.util.List;

@Data
class Payload {
	@NotNull
	private Integer id;
	@NotBlank
	private String userName;
	@Size(min = 1, max = 5)
	private List<String> hobby;
}

@RestController
@RequestMapping("/test")
@Validated
public class TestController {

    /** * expression, {{expression}}, '# variable name 'to access the Handler parameter properties, methods. * Handler parameter name, which is the variable name in the expression */
	@PostMapping
	@operationlog (expression = "update User" + ", userAgent = {{#request. GetHeader (' user-agent ')}}" + ", action = {{ #action }}" + ", id = {{ #payload.id }}" + ", userName = {{ #payload.userName }}" + ", hobbyLength = {{ #payload.hobby.size() }}")
	public Object test (HttpServletRequest request,
						HttpServletResponse response,
						@RequestParam("action") String action,
						@RequestBody @Validated Payload payload) {
		returnResponseEntity.ok(payload); }}Copy the code

test

Request/Response

POST /test? Action = HTTP / 1.1 update the content-type: application/json the user-agent: PostmanRuntime / 7.28.4 Accept: * / * Postman - Token: c9564e75-03ad-42f0-8fea-5e34912bcfb8 Host: localhost Accept-Encoding: gzip, deflate, br Connection: keep-alive Content-Length: 80 { "id": "1", "userName": "cxk", "hobby": HTTP/1.1 200 OK x-response-time: 2 Connection: keep-alive Server: PHP/7.3.1 x-request-id: HTTP/1.1 200 OK x-response-time: 2 Connection: keep-alive Server: PHP/7.3.1 x-request-id: 553b4c5d-5363-4e53-9978-a0671e9aa16d Content-Type: application/json; charset=UTF-8 Content-Length: 84 Date: Tue, 19 Oct 2021 04:28:46 GMT { "id": 1, "userName": "cxk", "hobby": [" sing ", "dance ", "Rap"]}Copy the code

The log

The corresponding property was successfully read

The 2021-10-19 12:28:46. 2692-831 the INFO IO [task XNIO - 1-1]. Springcloud. Web. Log. OperationLogAop: UserAgent = PostmanRuntime/7.28.4, Action = update, ID = 1, userName = CXK, hobbyLength = 3Copy the code

Starting: springboot. IO/topic / 424 / t…