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…