通用操作日志组件biz-log 及基本使用

前言:

        为什么要用biz-log,接口需要记录谁在什么时间做了什么事,并在日志模块进行展示,每个接口的接口描述不一样。需要接口写不同描述。
        提前了解:SpEL(Spring Expression Language)语句

注意:该文档只是记录作者使用过程中用到的!!!

biz-log基本使用

Gitee地址:mzt-biz-log: Springboot-注解-通用操作日志组件美团技术博客:https://tech.meituan.com/2021/09/16/operational-logbook.html源码实现

  • 导包(最新3.0.6)
        <!-- https://mvnrepository.com/artifact/io.github.mouzt/bizlog-sdk -->
        <dependency>
            <groupId>io.github.mouzt</groupId>
            <artifactId>bizlog-sdk</artifactId>
            <version>3.0.6</version>
        </dependency>
  • 启动类添加 @EnableLogRecord注解

        tenant可以作为系统的标识。

@EnableLogRecord(tenant = "XXX系统")
  • 注解@LogRecord 简单使用
    接口上添加@LogRecord注解 (源码里面截取)
@LogRecord(success = "更新了订单{{#newOrder}}",
            type = LogRecordType.ORDER, bizNo = "{{#newOrder.orderNo}}",
            extra = "{{#newOrder.toString()}}")
boolean diff2(Order newOrder);

        注解字段解释

        success:执行成功后后的日志模版

        type: 类型

        bizNo:业务标识

        extra:补充说明,额外信息

        如果方法执行成功,日志系统会根据 success 属性的值来记录日志。

        在记录日志时,日志系统会解析字符串模板,并替换其中的占位符为实际的值。{{#newOrder}} 可能会被替换为 newOrder 对象的某个属性或其字符串表示形式,{{#newOrder.orderNo}} 会被替换为 newOrder 对象的 orderNo 属性值。

        日志系统会记录一条包含实际值的日志消息。

看完这部分其实就可以简单使用了。接下来就是存储 以及自定义复杂日志
1. 日志存储
        a. 添加 LogRecordPO,LogRecordDao

LogRecordPO字段包含源码里面的LogRecord类(com.mzt.logapi.beans) 添加部分业务字段

@Data
@NoArgsConstructor
@TableName(value = "tab_log_record")
@Accessors(chain = true)
public class LogRecordPO {
    /**
     * id
     */
    @TableId(type = IdType.AUTO)
    private Long id;
    /**
     * 租户
     */
    private String tenant;
    
    /**
     * 保存的操作日志的类型,比如:订单类型、商品类型
     *
     * @since 2.0.0 从 prefix 修改为了type
     */
    @NotBlank(message = "type required")
    @Length(max = 200, message = "type max length is 200")
    private String type;
    /**
     * 日志的子类型,比如订单的C端日志,和订单的B端日志,type都是订单类型,但是子类型不一样
     * @since 2.0.0 从 category 修改为 subtype
     */
    private String subType;
    
    /**
     * 日志绑定的业务标识
     */
    @NotBlank(message = "bizNo required")
    @Length(max = 200, message = "bizNo max length is 200")
    private String bizNo;
    /**
     * 操作人
     */
    @NotBlank(message = "operator required")
    @Length(max = 63, message = "operator max length 63")
    private String operator;
    
    private String ip;
    
    private String orgId;
    private String orgName;
    /**
     * 日志内容
     */
    @NotBlank(message = "opAction required")
    @Length(max = 511, message = "operator max length 511")
    private String action;
    /**
     * 记录是否是操作失败的日志
     */
    private boolean fail;
    /**
     * 日志的创建时间
     */
    private LocalDateTime createTime;
    /**
     * 日志的额外信息
     *
     * @since 2.0.0 从detail 修改为extra
     */
    private String extra;
    
  
}
@Mapper
public interface LogRecordDao extends BaseMapper<LogRecordPO> {
}
b.实现ILogRecordService  重写record,获取到LogRecord 并入库
//ILogRecordService源码
public interface ILogRecordService {
    void record(LogRecord logRecord);

    List<LogRecord> queryLog(String bizNo, String type);

    List<LogRecord> queryLogByBizNo(String bizNo, String type, String subType);
}
@Service
@RequiredArgsConstructor
public class LogRecordServiceImpl implements ILogRecordService{
    
    private final LogRecordConvert logRecordConvert;
    
    private final LogRecordDao logRecordDao;   
     
    @Override
    public void record(LogRecord logRecord) {
        LogRecordPO po = logRecordConvert.convertToPo(logRecord);
        //需要处理的业务字段
    
        logRecordDao.insert(po);
    }
        @Override
    public List<LogRecord> queryLog(String bizNo, String type) {
        return null;
    }
    
    @Override
    public List<LogRecord> queryLogByBizNo(String bizNo, String type, String subType) {
        return null;
    }
}
c.指定操作人 实现IOperatorGetService

LogRecord中有operator 用来标识操作人,可以实现IOperatorGetService

@Service
public class DefaultOperatorGetServiceImpl implements IOperatorGetService {

    @Override
    public Operator getUser() {
        //获取用户
        UserBean userInfo = UserThreadLocal.getUserBean();
        Operator operatorDO = new Operator();
        operatorDO.setOperatorId(userInfo.getUsername());
        return operatorDO;
    }
}
2.复杂日志
   a.三元表达式
@LogRecord(
            fail = "统计模块,{{#timeRangeParam.receiverType==0? '测试' : '测试1'}}完成率导出失败。",
            success = "统计模块,{{#timeRangeParam.receiverType==0? '测试' : '测试1'}}完成率导出。共{{#timeRangeParam.size}}" 
            type = "统计模块",
            subType = "{{#timeRangeParam.receiverType==0? '测试' : '测试1'}}完成率导出",
            bizNo = "",
            extra = "入参:{{#timeRangeParam.toJson()}}")
List<StatisticsExportRt> finishStateExport(TimeRangeParam timeRangeParam);

type: 用来标识模块

subType: 模块某个接口

extra: 入参信息 TimeRangeParam中写了一个toJson

所有SpEL语句 获取到的TimeRangeParam 里面的参数 都是整个方法执行完后的。

假如:TimeRangeParam 里面 有一个size ,最开始赋值为0. 方法内部 使size=10,则

成功的描述里面 共10条,而不是0条。

b.模版中使用方法参数之外的变量

接口:导出用户信息

日志描述:用户模块,导出用户信息从2024年X月X日到2024年X月X日,共X条

时间一般为LocalDateTime形式 与产品所需2024年X月X日格式不同。

解决思路:

        第一种:size也可以加到param里面查出来之后 set一下。这样的话 我们需要什么字段就需要添加到param中 如果需要记录日志的接口过多,改动了很大

        第二种:使用 LogRecordContext.putVariable 添加变量。

代码如下:

添加InnerLogInfoDTO类
@Data
@NoArgsConstructor
public class InnerLogInfoDTO {
    /**
     * 数量
     */
    private Integer size;
    /**
     * 开始时间
     */
    private String startTimeStr;
    /**
     * 结束时间
     */
    private String endTimeStr;
    /**
     * 开始时间
     */
    private LocalDateTime startTime;
    /**
     * 结束时间
     */
    private LocalDateTime endTime;
}
添加 @LogRecord注解 部分使用innerLogInfoDTO

@LogRecord(
            fail = "用户模块,导出失败。",
            success = "用户模块,导出用户信息," +
                    "{{#innerLogInfoDTO.startTimeStr != null && #innerLogInfoDTO.endTimeStr != null ? #innerLogInfoDTO.startTimeStr +'到'+#innerLogInfoDTO.endTimeStr+',' :''}}" +
                    "共{{#innerLogInfoDTO.size}}条",
            type = "用户模块",
            subType = "用户信息导出",
            bizNo = "",
            extra = "入参:{{#param.toJson()}}")
    File exportUserInfo(Param param) throws Exception;
exportUserInfo接口添加如下代码
@Override
    public File exportUserInfo(Param param) throws Exception {
       //处理日志描述
        InnerLogInfoDTO innerLogInfoDTO=new InnerLogInfoDTO();
        innerLogInfoDTO.setSize(size);
        if(Objects.nonNull(commandParam.getStartTime())){
            innerLogInfoDTO.setStartTime(commandParam.getStartTime());
        }
        if(Objects.nonNull(commandParam.getEndTime())){
            innerLogInfoDTO.setEndTime(commandParam.getStartTime());
        }
        innerLogInfoManagement.handleInnerLogInfo(innerLogInfoDTO);
}
public void handleInnerLogInfo(InnerLogInfoDTO innerLogInfoDTO) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
        if(Objects.nonNull(innerLogInfoDTO.getStartTime())){
            String startTime = innerLogInfoDTO.getStartTime().format(formatter);
            innerLogInfoDTO.setStartTimeStr(startTime);
        }
        if(Objects.nonNull(innerLogInfoDTO.getEndTime())){
            String endTime = innerLogInfoDTO.getEndTime().format(formatter);
            innerLogInfoDTO.setEndTimeStr(endTime);
        }
        //使用LogRecordContext.putVariable添加变量
        LogRecordContext.putVariable("innerLogInfoDTO", innerLogInfoDTO);
    }

通过 LogRecordContext.putVariable(variableName, Object) 方法添加变量,第一个对象为变量名称,后面为变量的对象, 使用 SpEL 使用这个变量了

@LogRecord 里面{{#innerLogInfoDTO.size}} 中的innerLogInfoDTOputVariable的variableName。

跨方法使用

通过LogRecordContext.putGlobalVariable(variableName, Object) 放入上下文中,此优先级为最低,若方法上下文中存在相同的变量,则会覆盖

@Override
    @LogRecord(
            success = "{{#order.purchaseName}}下了一个订单,购买商品「{{#order.productName}}」,测试变量「{{#innerOrder.productName}}」,下单结果:{{#_ret}}",
            type = LogRecordType.ORDER, bizNo = "{{#order.orderNo}}")
    public boolean createOrder(Order order) {
        log.info("【创建订单】orderNo={}", order.getOrderNo());
        // db insert order
        Order order1 = new Order();
        order1.setProductName("内部变量测试");
        LogRecordContext.putVariable("innerOrder", order1);
        return true;
    }

  • 16
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值