如何优雅地记录日志操作日志?

前言

  1. 本文是通过阅读美团的《如何优雅地记录操作日志?》后,整理总结为自己项目落地的方案
  2. 由于美团的那篇文章没有给出完整的代码,没有采取文档中自定义函数的实现机制,直接依赖SPEL的bean引用
  3. 参考资料
    3.1 spring cache:https://docs.spring.io/spring-framework/docs/5.3.9/reference/html/integration.html#cache
    3.2 SPEL官网:https://docs.spring.io/spring-framework/docs/5.3.9/reference/html/core.html#expressions

系统日志和操作日志的区别

  1. 系统日志:系统日志主要是为开发排查问题提供依据,一般打印在日志文件中;系统日志的可读性要求没那么高,日志中会包含代码的信息,比如在某个类的某一行打印了一个日志
  2. 操作日志:主要是对某个对象进行新增操作或者修改操作后记录下这个新增或者修改,操作日志要求可读性比较强,因为它主要是给用户看的,比如订单的物流信息,用户需要知道在什么时间发生了什么事情。

方法注解实现操作日志

注解记录操作日志的优势

private OnesIssueDO updateAddress(updateDeliveryRequest request) {
    DeliveryOrder deliveryOrder = deliveryQueryService.queryOldAddress(request.getDeliveryOrderNo());
    // 更新派送信息,电话,收件人,地址
    doUpdate(request);
    String logContent = getLogContent(request, deliveryOrder);
    LogUtils.logRecord(request.getOrderNo(), logContent, request.getOperator);
    return onesIssueDO;
}

private String getLogContent(updateDeliveryRequest request, DeliveryOrder deliveryOrder) {
    String template = "用户%s修改了订单的配送地址:从“%s”修改到“%s”";
    return String.format(tempalte, request.getUserName(), deliveryOrder.getAddress(), request.getAddress);
}
  1. 可以看到上面的例子使用了两个方法代码,外加一个 getLogContent 的函数实现了操作日志的记录
  2. 当业务变得复杂后,记录操作日志放在业务代码中会导致业务的逻辑比较繁杂,最后导致 LogUtils.logRecord() 方法的调用存在于很多业务的代码中
  3. 类似 getLogContent() 这样的方法也散落在各个业务类中,对于代码的可读性和可维护性来说是一个灾难。下面介绍下如何避免这个灾难

优雅地支持 AOP 生成动态的操作日志

@LogRecord(
     content = "修改了订单的配送地址:从“#oldAddress”, 修改到“#request.address”",
     operator = "#request.userName", bizNo="#request.deliveryOrderNo")
public void modifyAddress(updateDeliveryRequest request, String oldAddress){
    // 更新派送信息 电话,收件人、地址
    doUpdate(request);
}
  1. operator 参数设置为非必填,如果用户不填写我们就取 UserContext的user
  2. 自定义函数解决了操作日志模板上使用方法参数以外变量的问题
  3. SpEL支持使用“@”符号来引用Bean,在引用Bean时需要使用BeanResolver接口实现来查找Bean
       @Test
        public void test(){
        	//模拟Spring容器中注册order的bean实例 
            DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
            Order order = new Order();
            order.setPurchaseName("purchaseName");
            factory.registerSingleton("order", order);
    
            StandardEvaluationContext context = new StandardEvaluationContext();
            context.setBeanResolver(new BeanFactoryResolver(factory));
            context.setVariable("aaaa","bbbb");
    
            ExpressionParser parser = new SpelExpressionParser();
            String expressionString = "修改了订单的配送员:从#{@order.getName()}, 修改到#{@order.purchaseName},#{#aaaa}";
            String content = parser.parseExpression(expressionString,new TemplateParserContext()).getValue(context,String.class);
            System.out.println(content);
        }
    

代码实现

代码结构

在这里插入图片描述
1.1 上面的操作日志主要是通过一个 AOP 拦截器实现的,整体主要分为 AOP 模块、日志解析模块、日志保存模块、Starter 模块
1.2 组件提供了默认处理人扩展点

拦截逻辑的流程:

在这里插入图片描述

日志上下文实现
  1. 把方法的参数和 LogRecordContext 中的变量都放到 SpEL 的 getValue 方法的 Object 中才可以顺利的解析表达式的值
  2. LogRecordContext 的实现,这个类里面通过一个 ThreadLocal 变量保持了一个栈,栈里面是个 Map,Map 对应了变量的名称和变量的值
    2.1 如果支持线程池可以使用阿里巴巴开源的 TTL 框架
  3. 为什么不直接设置一个 ThreadLocal<Map<String, Object>> 对象,而是要设置一个 Stack 结构呢?
    3.1 当方法二执行了释放变量后,继续执行方法一的 logRecord 逻辑,此时解析的时候 ThreadLocal<Map<String, Object>>的 Map 已经被释放掉
    3.2 方法一和方法二共用一个变量 Map 还有个问题是:如果方法二设置了和方法一相同的变量两个方法的变量就会被相互覆盖
  4. LogRecordContext 每执行一个方法都会压栈一个 Map,方法执行完之后会 Pop 掉这个 Map,从而避免变量共享和覆盖问题
    在这里插入图片描述
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 的异常处理机制可以说非常重要,它能够帮助我们优雅地处理程序中的异常情况。以下是一些关于如何在 Java 中实现优雅异常处理的建议: 1. 使用适当的异常类型:Java 提供了许多内置的异常类型,你应该根据具体情况选择合适的异常类型。这样可以让代码更具可读性,并且方便其他开发人员理解你的代码。 2. 不要捕获所有异常:捕获异常意味着你要对其进行处理,但并不是所有的异常都需要你来处理。只捕获那些你能够处理或者你有必要处理的异常,对于其他异常,可以通过在方法签名中声明 throws 关键字来传递给上层调用者处理。 3. 使用 try-with-resources:对于实现了 AutoCloseable 接口的资源对象,可以使用 try-with-resources 语句来自动关闭资源。这样可以避免资源泄漏,并且能够更好地管理资源。 4. 使用自定义异常:除了使用内置的异常类型,你还可以根据具体需求创建自定义异常。自定义异常可以提供更加具体的异常信息,帮助你更好地定位问题。 5. 记录和处理异常信息:在捕获到异常时,不仅仅是简单地打印异常堆栈信息,你还应该考虑记录异常信息,并根据具体情况进行适当的处理。可以将异常信息写入日志文件,或者返回给用户友好的错误提示。 6. 避免空指针异常:空指针异常是 Java 开发中最常见的异常之一。为了避免空指针异常,你可以使用 null 检查、空对象模式、Optional 类型等方式来处理可能为 null 的对象。 7. 分层处理异常:在程序中可以使用多层的异常处理机制,将底层的异常转换为更高级别的异常,从而能够更好地管理和处理异常。 总之,优雅的异常处理需要根据具体情况进行合理选择和实践。通过合适的异常类型、适当的捕获和处理、记录和传递异常信息,以及避免常见的错误,你能够使你的代码更加健壮和可维护。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值