Source: blog.csdn.net/weixin_43954303/article/details/113781801

  • use

  • The basic use

  • The source code

This component solves the problem of “who” did “what” to “what” when.

This component currently does Autoconfig for Spring-Boot, or if you are SpringMVC, you can initialize beans in XML yourself

use

The basic use

Maven dependencies add SDK dependencies

< the dependency > < groupId > IO. Making. Mouzt < / groupId > < artifactId > bizlog - SDK < / artifactId > < version > 1.0.1 < / version > </dependency>Copy the code

Open the SpringBoot entry switch and add the @enablelogRecord annotation

Tenant is an identification that represents a tenant. Generally, you can write a Tenant for a service or multiple services in a 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); }}Copy the code

Log buried point

1. Common logs
  • Pefix: is an identifier concatenated to bizNo as a log. Ensure that bizNo ids are not the same as those in other services. Such as order IDS, user ids, and so on

  • BizNo: refers to the business ID, such as the order ID. When we query, we can query the operation logs related to bizNo

  • Success: The success of the method call is recorded in the contents of the log

  • SpEL expression: Enclosed in double braces (for example: {{# order.purchasename}}) # Order.purchasename is an SpEL expression. It supports everything that is supported in Spring. Such as calling static methods, ternary expressions. SpEL can use any parameter in a method

    @logRecordannotation (success = “{{# order.productName}} “, order result :{{#_ret}}”, prefix = LogRecordType.ORDER, BizNo = “{{#order.orderNo}}”) public Boolean createOrder(order order) {log.info(” orderNo={}”, order.getOrderNo()); // db insert order return true; }

At this point, the operation log will be printed: “Zhang SAN placed an order to buy the product” super value discount braised pork set “, order result :true”

2. Logs about failure are expected. If exceptions are thrown, logs about FAIL are recorded
@logRecordannotation (fail = "Failed to create order because: "{{# _errorMsg}}", "success =" {{# order. PurchaseName}} down an order, buy goods "{{# order. ProductName}}", order results: {{# _ret}} ", prefix = LogRecordType.ORDER, BizNo = "{{#order.orderNo}}") public Boolean createOrder(order order) {log.info(" orderNo={}", order.getOrderNo()); // db insert order return true; }Copy the code

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

3. Log types

An order operation logs, for example, some operating log is the user’s own operations, have modified some operation is the operating personnel system operation log, we don’t want to put the operation of the operating system logs exposed to the user to see, but operating expect can see user logs and operating their own operation log, the operation log bizNo are order number, Therefore, the type field is added for expansion, mainly to classify logs, facilitate query, and support more services.

@logRecordannotation (fail = "Failed to create order because: {{#_errorMsg}} "", category = "MANAGER", Success = "{{#order. productName}} ", order result :{{#_ret}}", prefix = logRecordType. order, BizNo = "{{#order.orderNo}}") public Boolean createOrder(order order) {log.info(" orderNo={}", order.getOrderNo()); // db insert order return true; }Copy the code
4. Record operation details or additional information

If an operation changes many fields, but the success log template is too long to display all the details of the changes, you need to save the details to the detail field. The detail field is a String and needs to be serialized by itself. Here # order.tostring () is the toString() method that calls order. To save JSON, rewrite the Order toString() method yourself.

@logRecordannotation (fail = "Failed to create order because: "{{# _errorMsg}}", "the category =" MANAGER_VIEW ", the detail = "{{# order. The toString ()}}", Success = "{{#order. productName}} ", order result :{{#_ret}}", prefix = logRecordType. order, BizNo = "{{#order.orderNo}}") public Boolean createOrder(order order) {log.info(" orderNo={}", order.getOrderNo()); // db insert order return true; }Copy the code
5. How to specify the operator of operation logs? The framework provides two approaches
  • The first: manually specify it on a LogRecord annotation. This requires an operator on the method argument

    @logRecordannotation (fail = "Failed to create order because: "{{# _errorMsg}}", "the category =" MANAGER_VIEW ", the detail = "{{# order. The toString ()}}," operator = "{{# currentUser}}", Success = "{{#order. productName}} ", order result :{{#_ret}}", prefix = logRecordType. order, bizNo = "{{#order.orderNo}}") public boolean createOrder(Order order, String currentUser) {log.info(" orderNo={}", order.getorderno ()); // db insert order return true; }Copy the code

This method is specified manually, either with an operator parameter on the method argument or by calling a static method through SpEL to get the current user.

  • The second: Automatically by the default implementation class to the retrieval of the people, because the current user in most web applications are stored in a thread context, so each note is added an operator retrieval people seem to be some duplication of effort, so provides an extension to the retrieval framework provides an extended interface, Businesses that use frameworks can implement the interface’s own implementation of retrieving the current user’s logic, while those that use Springboot just implement the IOperatorGetService interface. This Service is then placed in the context of Spring as a singleton. Those using Spring Mvc need to assemble these beans by hand.

    @Configurationpublic class LogRecordConfiguration { @Bean public IOperatorGetService operatorGetService() { return () -> Optional.of(OrgUserUtils.getCurrentUser()) .map(a -> new OperatorDO(a.getMisId())) .orElseThrow(() -> new IllegalArgumentException(“user is null”)); }}// @Servicepublic class DefaultOperatorGetServiceImpl implements IOperatorGetService { @Override public OperatorDO getUser() { OperatorDO operatorDO = new OperatorDO(); operatorDO.setOperatorId(“SYSTEM”); return operatorDO; }}

6. Adjust log copy

The order {{#orderId}} is updated, and the update content is… , which is difficult to understand for operations or products, so the ability to customize functions is introduced. This is done by adding a function name between the braces of the original variable such as “{ORDER{#orderId}}” where ORDER is a function name. A function name is not enough; you need to add the definition and implementation of the function. A custom function needs to implement the IParseFunction interface in the framework. It needs to implement two methods:

  • The functionName() method returns the functionName above the annotation;

  • The apply() function takes the value of the #orderId parsed by SpEL in “{ORDER{#orderId}}”, which is a number 1223110, and then converts the ID into a readable string in the implementation class. Generally, the name and ID are displayed to facilitate troubleshooting, for example, in the form of “order name (ID)”.

Here’s the problem: how can the framework call a custom function when it’s added? A: For Spring Boot applications, it’s easy. Just expose it in the context of Spring. You can add @Component or @service to Spring for convenience 😄. Spring MVC applications need to assemble beans themselves.

@logRecordannotation (success = "updated order {{#orderId}}, updated to...." , prefix = LogRecordType.ORDER, bizNo = "{{#order.orderNo}}", detail = "{{#order.toString()}}") public boolean update(Long orderId, Order order) { return false; {{#orderId}}} functionName@logRecordannotation (success = "Update ORDER{#orderId}} with the update content..." , prefix = LogRecordType.ORDER, bizNo = "{{#order.orderNo}}", detail = "{{#order.toString()}}") public boolean update(Long orderId, Order order) { return false; @component public class OrderParseFunction implements IParseFunction {@resource @lazy // To avoid class loading order OrderQueryService OrderQueryService OrderQueryService @override public String functionName() {return "ORDER"; } @override // the value of the Order object can be passed, Public String apply(String value) {if(stringutils.isempty (value)){return value; } Order order = orderQueryService.queryOrder(Long.parseLong(value)); Return order.getproductName ().concat("(").concat(value).concat(")"); return order.getproductName ().concat(value). }}Copy the code
7. Log text adjustment using SpEL ternary expression
@LogRecordAnnotation(prefix = LogRecordTypeConstant.CUSTOM_ATTRIBUTE, bizNo = "{{#businessLineId}}", success = "{{#disable ? 'deactivate' : ATTRIBUTE{#attributeId}}") public CustomAttributeVO disableAttribute(Long businessLineId, Long attributeId, boolean disable) { return xxx; }Copy the code

Extension points for the framework

  • Override OperatorGetServiceImpl to get the user’s extension from the context, as shown in the following example

    @Servicepublic class DefaultOperatorGetServiceImpl implements IOperatorGetService { @Override public Operator getUser() { return Optional.ofNullable(UserUtils.getUser()) .map(a -> new Operator(a.getName(), a.getLogin())) .orElseThrow(()->new IllegalArgumentException(“user is null”)); }}

  • ILogRecordService is an example of saving/querying logs to a suitable storage medium, such as a database or ES, depending on the amount of data. Save and delete by yourself

You can also implement only the query interface. After all, it has been stored in the service storage. The query service can be implemented by itself without using the ILogRecordService interface, because the product manager will ask for some strange query requirements.

@Servicepublic class DbLogRecordServiceImpl implements ILogRecordService { @Resource private LogRecordMapper logRecordMapper; @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void record(LogRecord logRecord) { Log.info (" [logRecord] log={}", logRecord); LogRecordPO logRecordPO = LogRecordPO.toPo(logRecord); logRecordMapper.insert(logRecordPO); } @Override public List<LogRecord> queryLog(String bizKey, Collection<String> types) { return Lists.newArrayList(); } @Override public PageDO<LogRecord> queryLogByBizNo(String bizNo, Collection<String> types, PageRequestDO pageRequestDO) { return logRecordMapper.selectByBizNoAndCategory(bizNo, types, pageRequestDO); }}Copy the code
  • IParseFunction custom conversion function interface, can implement IParseFunction implementation used in the LogRecord annotation function extension example:

    @Componentpublic class UserParseFunction implements IParseFunction { private final Splitter splitter = Splitter.on(“,”).trimResults(); @Resource @Lazy private UserQueryService userQueryService; @Override public String functionName() { return “USER”; } @override // 11,12 returns 11, 12(三 三) public String apply(String value) {if (stringutils.isEmpty (value)) {return value; } List userIds = Lists.newArrayList(splitter.split(value)); List misDOList = userQueryService.getUserList(userIds); Map<String, User> userMap = StreamUtil.extractMap(misDOList, User::getId); StringBuilder stringBuilder = new StringBuilder(); for (String userId : userIds) { stringBuilder.append(userId); if (userMap.get(userId) ! = null) { stringBuilder.append(“(“).append(userMap.get(userId).getUsername()).append(“)”); } stringBuilder.append(“,”); } return stringBuilder.toString().replaceAll(“,$”, “”); }}

Variables related to

The LogRecordAnnotation notation can use a variable with an argument or use the return value #_ret variable with an error message #_errorMsg. It can also call static methods using SpEL T

To extend

Implementing a Log Context can solve the problem of using variables that do not exist in the method parameters. The initial idea is to implement this by adding variables to the method, which will soon be implemented 😄

Note:

⚠️ Global logging interception is recorded after the method is executed, so the SpEL in the LogRecordAnnotation annotation takes the value of the variable after modifying the method arguments inside the method

The source code

Github.com/mouzt/mzt-b…

Author

mail : [email protected]