In the previous chapter, Managing logs with LogBack, we covered in detail how to store log generation files. However, in the actual development, it is not the most convenient to use the file to store logs for quick query. In addition to log files, an excellent system also needs to persist operation logs to monitor the operation records of the platform. Today we are going to learn how to log using APO.

To make logging more flexible, we will use custom annotations for logging important operations.

Log record table

The log table contains the following fields: service module, operation type, interface address, processing status, error information, and operation time. The database design is as follows:

CREATE TABLE `sys_oper_log` (
   `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Log primary key',
   `title` varchar(50) CHARACTER SET utf8 DEFAULT ' ' COMMENT 'Module title',
   `business_type` int(2) DEFAULT '0' COMMENT 'Business Type (0 others 1 New 2 Modified 3 Deleted)',
   `method` varchar(255) CHARACTER SET utf8 DEFAULT ' ' COMMENT 'Method name',
   `status` int(1) DEFAULT '0' COMMENT 'Operating status (0 normal 1 Abnormal)',
   `error_msg` varchar(2000) CHARACTER SET utf8 DEFAULT ' ' COMMENT 'Error message',
   `oper_time` datetime DEFAULT NULL COMMENT 'Operation time'.PRIMARY KEY (`id`)
 ) ENGINE=InnoDB CHARSET=utf8mb4 CHECKSUM=1 COMMENT='Operation Logging'
Copy the code

The corresponding entity class is as follows:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysOperLog implements Serializable {
    private static final long serialVersionUID = 1L;

    /** Log primary key */
    private Long id;

    /** Operation module */
    private String title;

    /** Service type (0 other 1 Added 2 Modified 3 Deleted) */
    private Integer businessType;

    /** Request method */
    private String method;

    /** Error message */
    private String errorMsg;

    private Integer status;

    /** Operation time */
    private Date operTime;
}
Copy the code

Custom annotation and processing

The custom annotation contains two attributes, business module title and operation type businessType.

@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
    /** * module */
    String title(a) default "";

    /** * function */
    BusinessType businessType(a) default BusinessType.OTHER;
}
Copy the code

Use AOP to handle custom annotations

@Aspect
@Component
@Slf4j
public class LogAspect {

    @Autowired
    private AsyncLogService asyncLogService;

    // Configure the weave point
    @Pointcut("@annotation(com.javatrip.aop.annotation.Log)")
    public void logPointCut(a) {}

    /** * execute ** after the request is processed@paramJoinPoint tangent point * /
    @AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
        handleLog(joinPoint, null, jsonResult);
    }

    /** * Intercepts the exception operation **@paramJoinPoint tangent point *@paramE * /
    @AfterThrowing(value = "logPointCut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        handleLog(joinPoint, e, null);
    }

    protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) {
        try {
            // Get comments
            Log controllerLog = getAnnotationLog(joinPoint);
            if (controllerLog == null) {
                return;
            }

            SysOperLog operLog = new SysOperLog();
            operLog.setStatus(0);
            if(e ! =null) {
                operLog.setStatus(1);
                operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0.2000));
            }
            // Set method name
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            operLog.setMethod(className + "." + methodName + "()");
            // Handle setting the parameters on the annotations
            getControllerMethodDescription(joinPoint, controllerLog, operLog);
            // Save the database
            asyncLogService.saveSysLog(operLog);
        } catch (Exception exp) {
            log.error("== Pre-notification exception ==");
            log.error("Log Exception {}", exp); }}/** * Get the method description in the annotation for the Controller layer annotation **@paramThe log log *@paramOperLog Operation log *@throws Exception
     */
    public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog) {
        // Set the action
        operLog.setBusinessType(log.businessType().ordinal());
        // Set the title
        operLog.setTitle(log.title());
    }

    /** * if there are annotations, if so, get */
    private Log getAnnotationLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if(method ! =null) {
            return method.getAnnotation(Log.class);
        }
        return null; }}Copy the code

Enumeration classes for operation types:

public enum BusinessType {
    /** * other */
    OTHER,

    /** * add */
    INSERT,

    /** * modify */
    UPDATE,

    /** * delete */
    DELETE,
}
Copy the code

The operation logs are saved asynchronously. For convenience, I use the jdbcTemplate directly to save the operation in the service.

@Service
public class AsyncLogService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /** * Save system logs */
    @Async
    public void saveSysLog(SysOperLog log) {

        String sql = "INSERT INTO sys_oper_log(title,business_type,method,STATUS,error_msg,oper_time) VALUES(? ,? ,? ,? ,? ,?) ";
        jdbcTemplate.update(sql,new Object[]{log.getTitle(),log.getBusinessType(),log.getMethod(),log.getStatus(),log.getErrorMsg(),newDate()}); }}Copy the code

Write interface tests

Test the effect by writing custom annotations on business methods

@RestController
@RequestMapping("person")
public class PersonController {
    @GetMapping("/{name}")
    @Log(title = "system",businessType = BusinessType.OTHER)
    public Person getPerson(@PathVariable("name") String name, @RequestParam int age){
        return new Person(name,age);
    }

    @PostMapping("add")
    @Log(title = "system",businessType = BusinessType.INSERT)
    public int addPerson(@RequestBody Person person){
        if(StringUtils.isEmpty(person)){
            return -1;
        }
        return 1;
    }

    @PutMapping("update")
    @Log(title = "system",businessType = BusinessType.UPDATE)
    public int updatePerson(@RequestBody Person person){
        if(StringUtils.isEmpty(person)){
            return -1;
        }
        return 1;
    }

    @DeleteMapping("/{name}")
    @Log(title = "system",businessType = BusinessType.DELETE)
    public int deletePerson(@PathVariable(name = "name") String name){
        if(StringUtils.isEmpty(name)){
            return -1;
        }
        return 1; }}Copy the code

Of course, you can also store the request parameters and response results in the database, so that you can see the operation record of the specific interface.


The sample code for this article has been uploaded togithub, point astarSupport!

Spring Boot series tutorial directory

Spring-boot-route (I) Several ways for Controller to receive parameters

Spring-boot-route (2) Several methods of reading configuration files

Spring-boot-route (3) Upload multiple files

Spring-boot-route (4) Global exception processing

Spring-boot-route (5) Integrate Swagger to generate interface documents

Spring-boot-route (6) Integrate JApiDocs to generate interface documents

Spring-boot-route (7) Integrate jdbcTemplate operation database

Spring-boot-route (8) Integrating mybatis operation database

Spring-boot-route (9) Integrate JPA operation database

Spring-boot-route (10) Switching between multiple data sources

Spring-boot-route (11) Encrypting database configuration information

Spring-boot-route (12) Integrate REDis as cache

Spring-boot-route RabbitMQ

Spring-boot-route Kafka

Spring-boot-route (15) Integrate RocketMQ

Spring-boot-route (16) Use logback to produce log files

Spring-boot-route (17) Use AOP to log operations

Spring-boot-route (18) Spring-boot-adtuator monitoring applications

Spring-boot-route (19) Spring-boot-admin Monitoring service

Spring-boot-route (20) Spring Task Implements simple scheduled tasks

Spring-boot-route (21) Quartz Implements dynamic scheduled tasks

Spring-boot-route (22) Enables email sending

Spring-boot-route (23) Developed wechat official accounts

Spring-boot-route (24) Distributed session consistency processing

Spring-boot-route (25) two lines of code to achieve internationalization

Spring-boot-route (26) Integrate webSocket

This series of articles are frequently used in the work of knowledge, after learning this series, to cope with daily development more than enough. If you want to know more, just scan the qr code below and let me know. I will further improve this series of articles!