springboot自定义日志starter

279 阅读3分钟

设计原理

在我们开始编写开发日志starter之前,我们需要先整理好这个starter的应用方式,首先我们需要对日志工具类做一个需求设计,考虑到代码的侵入性、封装性,我们需要使用到AOP、注解,然后通过日志记录的方式(例如:存入数据库、记录到日志文件中),我们可以在配置文件中配置使用的方式,然后通过 @ConditionalOnProperty注解来获取配置文件中的值来判断记录的日志方式。

微信图片_20231102105837.png

导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
</dependency>

注解开发

首先我们需要开发一个注解类,该注解主要传入的参数是opera,及用户对应的操作

package com.fengchuang.log.annotation;

import java.lang.annotation.*;

/**
 * @author fengchuang
 * @date 2023/11/3
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface BussinessLog {
    /**
     * 操作信息
     */
    String opera();
}

starter配置信息

package com.fengchuang.log.properties;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;

/**
 * 审计日志配置
 *
 * @author fengchuang
 * @date 2023/11/3
 */
@Setter
@Getter
@ConfigurationProperties(prefix = "fc.business-log")
@RefreshScope
public class BusinessLogProperties {
    /**
     * 是否开启审计日志
     */
    private Boolean enabled = false;
    /**
     * 日志记录类型(log/db)
     */
    private String logType;
}

@RefreshScope:微服务体系中刷新远程配置 @ConfigurationProperties(prefix = "fc.bussiness-log"):获取配置文件中fc.bussiness-log的配置

创建日志实体类

package com.fengchuang.pojo;

import lombok.Getter;
import lombok.Setter;

import java.time.LocalDateTime;

/**
 * 审计日志
 *
 * @author fengchuang
 * @date 2023/11/3
 */
@Setter
@Getter
public class Business {
    /**
     * 操作时间
     */
    private LocalDateTime timestamp;
    /**
     * 应用名
     */
    private String applicationName;
    /**
     * 类名
     */
    private String className;
    /**
     * 方法名
     */
    private String methodName;
    /**
     * 用户id
     */
    private String userId;
    /**
     * 用户名
     */
    private String userName;
    /**
     * 租户id
     */
    private String tenantId;
    /**
     * 操作信息
     */
    private String opera;
}

AOP日志切面

接下来我们需要开发的是AOP日志切面类

package com.fengchuang.aspect;

import com.fengchuang.annotation.BusinessLog;
import com.fengchuang.pojo.Business;
import com.fengchuang.properties.BusinessLogProperties;
import com.fengchuang.service.ILogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;

/**
 * 业务日志切面
 *
 * @author fengchuang
 * @date 2023/11/3
 */
@Slf4j
@Aspect
@Component
public class BusinessLogAspect {
    @Value("${spring.application.name}")
    private String applicationName;

    private BusinessLogProperties businessLogProperties;
    private ILogService logService;

    public BusinessLogAspect(BusinessLogProperties businessLogProperties, ILogService logService) {
        this.businessLogProperties = businessLogProperties;
        this.logService = logService;
    }

    @Before("@within(businessLog) || @annotation(businessLog)")
    public void beforeMethod(JoinPoint joinPoint, BusinessLog businessLog) {
        //判断功能是否开启
        if (businessLogProperties.getEnabled()) {
            if (logService == null) {
                log.warn("AuditLogAspect - auditService is null");
                return;
            }
            if (businessLog == null) {
                // 获取类上的注解
                businessLog = joinPoint.getTarget().getClass().getDeclaredAnnotation(BusinessLog.class);
            }
            Business business = getAudit(businessLog, joinPoint);
            logService.save(business);
        }
    }

    /**
     * 构建审计对象
     */
    private Business getAudit(BusinessLog businessLog, JoinPoint joinPoint) {
        Business business = new Business();
        business.setTimestamp(LocalDateTime.now());
        business.setApplicationName(applicationName);

        MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
        business.setClassName(methodSignature.getDeclaringTypeName());
        business.setMethodName(methodSignature.getName());
        String operation = businessLog.opera();
        business.setOpera(operation);
        return business;
    }
}

@Aspect:定义切面类 @Value("${spring.application.name}"):获取配置类中的应用名 @Before("@within(bussinessLog) || @annotation(bussinessLog)"):@Before表示的是方法执行前 @within和@annotation主要是在切面前判断方法是否有@BussinessLog的注解,如果有则在方法执行前切面。

实现LogService实现类

1.我们可以通过@ConditionalOnProperty来判断我们配置文件中设置的文件类型选择的日志记录方式 name表示获取配置文件中fc.business-log.type属性值,借助于hadingValue属性,清楚地表明,仅当fc.business-log.type设置为log时,才希望加载LogServiceImpl。同时我们也可以通过该方法拓展出别的日志记录方法(比如存储数据库等)

package com.fengchuang.service.impl;

import com.fengchuang.pojo.Business;
import com.fengchuang.service.ILogService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;

import java.time.format.DateTimeFormatter;

/**
 * 审计日志实现类-打印日志
 *
 * @author fengchuang
 * @date 2023/11/3
 */
@Slf4j
@Service
@ConditionalOnProperty(name = "fc.business-log.log-type", havingValue = "log", matchIfMissing = true)
public class LogServiceImpl implements ILogService {
    private static final String MSG_PATTERN = "{}|{}|{}|{}|{}|{}|{}|{}";

    /**
     * 格式为:{时间}|{应用名}|{类名}|{方法名}|{用户id}|{用户名}|{租户id}|{操作信息}
     */
    @Override
    public void save(Business businessMsg) {
        log.debug(MSG_PATTERN
                , businessMsg.getTimestamp().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))
                , businessMsg.getApplicationName(), businessMsg.getClassName(), businessMsg.getMethodName()
                , businessMsg.getUserId(), businessMsg.getUserName(), businessMsg.getTenantId()
                , businessMsg.getOpera());
    }
}

springboot自动配置类

/**
 * 日志自动配置
 *
 * @author fengchuang
 * @date 2023/11/3
 */
@ComponentScan
@EnableConfigurationProperties({AuditLogProperties.class})
public class LogAutoConfigure {
}

@EnableConfigurationProperties:让使用@ConfigurationProperties 注解的类生效。

在resources目录下新建META-INF文件夹 创建spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.fengchuang.log.LogAutoConfigure

至此我们制作的基于AOP日志切面的stater就已经做好了,我们可以在别的项目中直接依赖他或者微服务的形式将stater放入公共方法中,供别的模块插拔式使用,当别的模块依赖我们的starter后

fc:
  #日志
  business-log:
    enabled: true
    logType: log

在对应的方法加上

@Business(opera = "对应操作")

这样我们就封装好了一个日志的starter。 git源码地址