前言:
为什么要用biz-log,接口需要记录谁在什么时间做了什么事,并在日志模块进行展示,每个接口的接口描述不一样。需要接口写不同描述。
提前了解:SpEL(Spring Expression Language)语句
注意:该文档只是记录作者使用过程中用到的!!!
biz-log基本使用
-
导包(最新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}} 中的innerLogInfoDTO为putVariable的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;
}