1. The background

Log almost exists in all systems, the development of debug log record we have log4j, Logback and so on to achieve, but for the log to show the user, I did not find a simple universal implementation scheme. So you decided to provide a common operation log component for future development projects.

2. System logs and operation logs

All systems have logs, but we distinguish between system logs and operation logs

  • System logs: mainly used for debugging and troubleshooting system problems, do not require fixed format and readability

  • Operation logs: User logs that can be easily understood and reflect user actions.

An operation log can be traced back to when someone did something, for example:

3. What features are needed

3.1 claim:

  1. Fast access based on SpringBoot

  2. Low invasiveness to business code

3.2 Solution:

Based on these two points, let’s think about how to do that.

Spingboot fast access, we need to define the Spring Boot starter;

Business invasion is low, the first thought of AOP, the general operation log is in the add, delete, change, check methods, so we can use annotations on these methods, through AOP interception of these methods.

3.3 To be realized:

Therefore, we need to implement the following functions:

  • Customize the Spring Boot starter

  • Defining log annotations

  • AOP intercepts log annotation methods

  • Define log dynamic content templates

The template also needs to implement:

  • Dynamic template expression parsing: Use the powerful SpEL to parse expressions

  • Custom functions: Custom functions that support prefixes/postfixes to target methods

3.4 show

So what we expect in the end is something like this:

@easylog (module = "user module ", type =" new ", content = "test {functionName{# userto.name}}", condition = "#userDto.name == 'easylog'") public String test(UserDto userDto) { return "test"; }Copy the code

4. Implementation steps

4.1 Defining log annotations

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EasyLog {
    String tenant() default "";
    String operator() default "";
    String module() default "";
    String type() default "";
    String bizNo() default "";
    String content();
    String fail() default "";
    String detail() default "";
    String condition() default "";
}
Copy the code

4.2 Custom Functions

A custom function in SpEL is not a custom function in SpEL, because a custom function in SpEL must be a static method to be registered in SpEL, because static methods are not as convenient as our own methods, so the custom function here refers to a normal method that we define.

Public interface ICustomFunction {/** * if @return is a pre-function */ Boolean executeBefore(); /** * custom functionName * @return custom functionName */ String functionName(); /** * custom function * @param param * @return execution result */ String apply(String param); }Copy the code

We define the custom function interface and hand the implementation to the user. The consumer gives the implementation class to the Spring container to manage, which we can get from when parsing.

4.3 SpEL Expression parsing

The following core classes are involved:

  • ExpressionParser, used to convert string expressions to Expression Expression objects.

  • Expression, and finally evaluates the Expression through its getValute method.

  • Context EvaluationContext, a context object combined with an expression to evaluate the final result.

    ExpressionParser parser =new SpelExpressionParser(); / / create an expression parser StandardEvaluationContext ex = new StandardEvaluationContext (); Ex.setvariables (“name”, “easylog”); // Create context ex.setvariables (“name”, “easylog”); // Add custom parameters to context Expression exp = parseExpression(“‘ Welcome! ‘+ #name”); String val = exp. GetValue (ex, string.class); / / get the value

We just need to take the dynamic template in the log annotation and use SpEL to parse it.

4.4 Analysis of user-defined Functions

We use {functionName {param}} in the template in the form of show a custom function, parse the entire template before, we first to parse the custom function, replace the parsed values in the template string.

if (template.contains("{")) { Matcher matcher = PATTERN.matcher(template); while (matcher.find()) { String funcName = matcher.group(1); String param = matcher.group(2); if (customFunctionService.executeBefore(funcName)) { String apply = customFunctionService.apply(funcName, param); }}}Copy the code

4.5 Obtaining operator information

We usually store the user information in the application context, so we don’t have to specify it in the log annotations every time. We can set up a unified access interface that the user implements.

Public interface IOperatorService {// Get the current operator String getOperator(); // Current tenant String getTenant(); }Copy the code

4.6 Defining log Content receiving

We need to send the parsed log content entity information to our users, so we need to define an interface for receiving logs and leave the implementation to the user, whether he receives logs stored in the database,MQ or wherever, for the user to decide.

Public interface ILogRecordService {/** * save log * @param easyLogInfo log entity */ void record(easyLogInfo easyLogInfo); }Copy the code

4.7 Defining AOP interception

@Aspect @Component @AllArgsConstructor public class EasyLogAspect { @Pointcut("@annotation(**.EasyLog)") public void @around ("pointCut() &&@annotation (easyLog)") public Object Around(ProceedingJoinPoint joinPoint, EasyLog EasyLog) throws Throwable {// Prefix custom function parse try {result = joinpoint.proceed (); } catch (Throwable e) {} //SpEL return result; }}Copy the code

4.8 Customizing the Spring Boot Starter

Create auto-configuration classes and hand some of the definitions over to the Spring container for management:

@Configuration
@ComponentScan("**")
public class EasyLogAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(ICustomFunction.class)
    @Role(BeanDefinition.ROLE_APPLICATION)
    public ICustomFunction customFunction(){
        return new DefaultCustomFunction();
    }

    @Bean
    @ConditionalOnMissingBean(IOperatorService.class)
    @Role(BeanDefinition.ROLE_APPLICATION)
    public IOperatorService operatorGetService() {
        return new DefaultOperatorServiceImpl();
    }

    @Bean
    @ConditionalOnMissingBean(ILogRecordService.class)
    @Role(BeanDefinition.ROLE_APPLICATION)
    public ILogRecordService recordService() {
        return new DefaultLogRecordServiceImpl();
    }
}
Copy the code

The last article I have a complete introduction to how to customize the Spring Boot starter, you can see: How to customize the Spring Boot Starter?

5. What can we learn?

You can pull the easy log source code, for learning, with easy log you can learn:

  • Definition and use of annotations

  • The application of AOP

  • Parsing SpEL expressions

  • Customize the Spring Boot starter

  • Design patterns

6. The source code

  • Making: github.com/flyhero/eas…

  • Gitee: gitee.com/flyhero/eas…