Springboot Annotation Implementation of Operation Log

  • 2021-08-31 08:11:46
  • OfStack

This component addresses the following issues:

"Who" did "what" to "what" at "what time"

At present, this component has made Autoconfig for Spring-boot. If it is SpringMVC, you can initialize bean in xml by yourself

Usage

Basic use

maven dependencies add SDK dependencies


  <dependency>
   <groupId>io.github.mouzt</groupId>
   <artifactId>bizlog-sdk</artifactId>
   <version>1.0.1</version>
  </dependency>

SpringBoot entry on switch, add @ EnableLogRecord annotation

tenant is the identity representing the tenant. One tenant can be written to one service or multiple services under one business


@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableTransactionManagement
@EnableLogRecord(tenant = "com.mzt.test")
public class Main {
 public static void main(String[] args) {
  SpringApplication.run(Main.class, args);
 }
}

Log embedding point

1. Ordinary logging

pefix: It is a logo spliced on bizNo as log. Avoid duplication of ID in other services when bizNo is an integer ID. For example, order ID, user ID, etc.

bizNo: This is the business ID, such as the order ID. When we query, we can query the related operation logs according to bizNo

success: Record success in the contents of the log after the method call is successful

SpEL expression: The one enclosed in double braces (for example: {{# order. purchaseName}}) # order. purchaseName is an SpEL expression. What is supported in Spring is supported by it. For example, calling static methods and 3-purpose expressions. SpEL can use any parameter in the method


 @LogRecordAnnotation(success = "{{#order.purchaseName}} Down 1 Orders , Buy goods " {{#order.productName}} " , Ordering result :{{#_ret}}",
    prefix = LogRecordType.ORDER, bizNo = "{{#order.orderNo}}")
 public boolean createOrder(Order order) {
  log.info(" "Create Order" orderNo={}", order.getOrderNo());
  // db insert order
  return true;
 }

At this time, the operation log "Zhang 3 placed an order and purchased the product" Super Value Preferential Braised Pork Package ", and the order result: true" will be printed

2. It is expected to record the failed log. If an exception is thrown, the log of fail will be recorded, but the log of success will not be thrown


 @LogRecordAnnotation(
   fail = " Failed to create order for: " {{#_errorMsg}} " ",
   success = "{{#order.purchaseName}} Down 1 Orders , Buy goods " {{#order.productName}} " , Ordering result :{{#_ret}}",
   prefix = LogRecordType.ORDER, bizNo = "{{#order.orderNo}}")
 public boolean createOrder(Order order) {
  log.info(" "Create Order" orderNo={}", order.getOrderNo());
  // db insert order
  return true;
 }

Where # _errorMsg is the errorMessage of the exception after the exception is thrown by the method taken.

3. Log support types

For example, the operation log of an order, some operation logs are operated by users themselves, and some operations are generated by modification by system operators. Our system does not want to expose the operation log to users.

However, the operation expects to see the user's log and the operation log of the operation itself. The bizNo of these operation logs are all order numbers, so the type field is added for expansion, mainly for the purpose of classifying the logs, making it convenient to query and supporting more services.


 @LogRecordAnnotation(
   fail = " Failed to create order for: " {{#_errorMsg}} " ",
   category = "MANAGER",
   success = "{{#order.purchaseName}} Down 1 Orders , Buy goods " {{#order.productName}} " , Ordering result :{{#_ret}}",
   prefix = LogRecordType.ORDER, bizNo = "{{#order.orderNo}}")
 public boolean createOrder(Order order) {
  log.info(" "Create Order" orderNo={}", order.getOrderNo());
  // db insert order
  return true;
 }

4. Support recording operation details or additional information

If one operation modifies many fields, but the log template of success is too long to show all the modification details, it is necessary to save the modification details to detail fields.

detail is an String that needs to be serialized itself. Here # order. toString () is the toString () method that calls Order.

If you save JSON, you can override the toString () method of Order under 1 yourself.


 @LogRecordAnnotation(
   fail = " Failed to create order for: " {{#_errorMsg}} " ",
   category = "MANAGER_VIEW",
   detail = "{{#order.toString()}}",
   success = "{{#order.purchaseName}} Down 1 Orders , Buy goods " {{#order.productName}} " , Ordering result :{{#_ret}}",
   prefix = LogRecordType.ORDER, bizNo = "{{#order.orderNo}}")
 public boolean createOrder(Order order) {
  log.info(" "Create Order" orderNo={}", order.getOrderNo());
  // db insert order
  return true;
 }

5. How do I specify who will operate the log? The framework provides two methods

Type 1: Manually specified on the annotation of LogRecord. This requires operator on the method parameter


 @LogRecordAnnotation(
   fail = " Failed to create order for: " {{#_errorMsg}} " ",
   category = "MANAGER_VIEW",
   detail = "{{#order.toString()}}",
   operator = "{{#currentUser}}",
   success = "{{#order.purchaseName}} Down 1 Orders , Buy goods " {{#order.productName}} " , Ordering result :{{#_ret}}",
   prefix = LogRecordType.ORDER, bizNo = "{{#order.orderNo}}")
 public boolean createOrder(Order order, String currentUser) {
  log.info(" "Create Order" orderNo={}", order.getOrderNo());
  // db insert order
  return true;
 }

This method is manually specified and requires either an operator parameter on the method parameter or a static method call via SpEL to get the current user.

The second is to get the operator automatically through the default implementation class. Because the current user is saved in one thread context in most web applications, adding one operator to get the operator in each annotation seems to be a bit of repetitive work, so an extended interface is provided to get the operator

The framework provides an extended interface, and the business using the framework can realize the logic of obtaining the current user by itself with implements interface.

For those who use Springboot, they only need to implement the IOperatorGetService interface, and then put this Service as a singleton in the context of Spring. Those who use Spring Mvc need to assemble these bean by hand.


@Configuration
public class LogRecordConfiguration {
 @Bean
 public IOperatorGetService operatorGetService() {
  return () -> Optional.of(OrgUserUtils.getCurrentUser())
    .map(a -> new OperatorDO(a.getMisId()))
    .orElseThrow(() -> new IllegalArgumentException("user is null"));
 }
}
// You can also do this: 
@Service
public class DefaultOperatorGetServiceImpl implements IOperatorGetService {
 @Override
 public OperatorDO getUser() {
  OperatorDO operatorDO = new OperatorDO();
  operatorDO.setOperatorId("SYSTEM");
  return operatorDO;
 }
}

6. Log copy adjustment

For methods such as update, most of the parameters of the method are order ID or product ID,

For example, the following example: the success content of the log record is: "The order was updated {{# orderId}}, and the updated content was...", which is difficult for operations or products to understand, so the function of custom function is introduced.

This is done by adding a function name between the curly braces of the original variable, such as "{ORDER {# orderId}}" where ORDER is a function name. Only one function name is not enough, you need to add the definition and implementation of this function. Look at the following example

Custom functions need to implement the interface of IParseFunction in the framework, and two methods need to be implemented:

The functionName () method returns the function name above the annotation;

The apply () function parameter is the value of # orderId parsed by SpEL in "{ORDER {# orderId}}", here a number 1223110, and then you only need to convert ID into a readable string in the implemented class.

In order to facilitate troubleshooting, both the name and ID need to be displayed, for example, in the form of "order name (ID)".

Here's a question: How can the framework be called after adding custom functions?

A: The application of Spring boot is very simple, just expose it to the context of Spring, and add @ Component or @ Service of Spring for


Related articles: