记录操作日志的一般套路

记录操作日志是web系统做安全审计和系统维护的重要手段,这里总结笔者在用java和python开发web系统过程中总结出来的、具有普遍意义的方法。

在java体系下,网络上搜索了一下,几乎一边倒的做法是用AOP,通过注解的方式记录操作日志,在此,笔者并不是很认同这种做法,原因如下:

  1. AOP的应用场景是各种接口中可以抽象出普遍的行为,且切入点选择需要在各接口中比较统一。
  2. 记录审计日志除了ip、用户等共同的信息外,还需要记录很多个性化的东西,比如一次修改操作,一般来讲需要记录对象标识、修改前后的值等等。有的值甚至并不能从request参数中直接获取,有可能需要一定的逻辑判断或者运算,使用AOP并不合适。
  3. 当然,有人说AOP中也可以传递参数,这里且不说有些日志信息需要从request参数计算而来的问题,就是是可以直接获取,在注解中传递一大堆的参数也失去了AOP简单的好处。

当然这主要还是看需求,如果你的操作日志仅仅是需要记录ip、用户等与具体接口无关的信息,那就无所谓。

接下来记录操作日志就比较简单了,无非就是在接口返回之前记录一些操作信息,这些信息可能从request参数中获取,也可能用request参数经过一些运算获取,都无所谓,但是有一点需要注意,你得确保成功或者失败场景都有记录。

那么问题来了,现在的web框架,REST接口调用失败普遍的做法是业务往外抛异常,由一个“统一异常处理”模块来处理异常并构造返回体,Java的String Boot(ExceptionHandler)、Python的flask(装饰器里make_response)、pecan(hook)等莫不是如此。那么接口调用失败的时候如何记录审计日志呢?肯定不可能在业务每个抛异常的地方去记录,这太麻烦,解决方法当然是在前面说的这个“统一异常处理”模块去处理,那么记录的参数如何传递给这个模块呢?方法就是放在本地线程相关的变量里,java接口可以在入口处整理操作日志信息存放在ThreadLocal变量里,成功或者失败的时候设置一个status然后记录入库即可;python下,flask接口可以放在app_context的g里,pecan可以放在session里。另外如果是异步任务,还需要给任务写个回调来更新状态。

可见,不管是用java还是python开发操作日志,都是相同的套路,总结如下图:

记录操作日志的一般套路_第1张图片

 

还有一点要注意,如果java接口是用的@Valid注解来进行参数校验,那么在校验失败时会抛出MethodArgumentNotValidException,问题在于,这个Valid发生在请求进入接口之前,也就是说,出现参数校验失败抛出MethodArgumentNotValidException的时候还没有进入接口里面的代码,自然也就没有往本地线程中记录操作日志需要的信息,那怎么办呢?方法就是在接口的请求入参中加一个BindingResult binding类型的参数,这个参数会截获参数校验的接口而不是抛出异常,然后在代码中(已经往线程上下文中写入了操作日志需要的信息以后的代码中)判断当binding中有错误,就抛出MethodArgumentNotValidException,此时就可以获取到操作日志需要的信息了,代码如下:

// 先往threadlocal变量中存入操作日志需要的信息

...

你可能感兴趣的:(Java)