接口日志重构

开通系统大多数功能需要通过接口交互完成,开通系统具有“接口数目多,接口类型复杂,接口协议不统一”的特点。由于接口交互在开通系统中处于非常重要的地位“完整的记录接口日志,方便的查看接口日志”是系统使用人员的主要诉求。

原接口日志记录方式

原接口日志记录采用的是切面拦截特定接口的方法,通过方法入参组装日志关键字,打印日志时再通过接口的方法实现获取特定接口交互的上下文信息输出到logstash,再通过logstash存储到es中。

这种方式存在以下缺点:

  1. 代码入侵严重。
    每个客户端和服务端的接口都必须实现特定的接口方法,加大了接口实现的难度,代码易读性差,部分接口有削足适履的感觉。
  2. 存在部分日志丢失。
    日志未从源头开始记录,存在部分日志丢失:收到一个请求->解析请求中的报文->截取关键字/从数据库查询关键字->调用特定接口的方法(此处接收报文接口日志)->方法返回(此处记录接口返回报文的日志)。从过程中可以看出在记录日志前会做很多的处理,一旦出错日志将无法记录下来
  3. 部分接口入参为对象时需要将对象转成字符串。
    客户端请求发送时有时候需要的请求体是一个对象,接口日志记录的是文本信息,所以需要将对象序列化。接口返回时有时候是一个对象,而特定接口返回的必须是一个字符串,因此也需要转换。

这种方式并不是没有任何优点,该方式将接口日志记录嵌入到业务逻辑中通过特定的接口限定所有的客户端和服务端都实现该接口,将接口统一,这样能应对所有类型的接口日志记录诉求。

重构后的接口日志记录方式

最近对原有接口日志记录进行了重构,新的方式采用spring ws原生的日志记录,通过全局拦截器和spring ws自带的日志打印记录接口日志。接口关键字通过配置文件和客户端包装类入参获取,接口上下文信息通过MDC设置到请求的线程上下文中记录日志。

新的方式存在以下缺点:

  1. 限定只有通过spring ws的服务端和客户端接口能被记录日志
  2. 接口关键字提取依然复杂可能会出现无法提取的情况
    接口关键字提取情况很多,有的关键字是报文内直接获取,有的需要报文内获取后截取出关键字,有的需要查询数据库,还有的根本不存在关键字(派单接口服开系统工单号还未生成)。
  3. 使用线程存储信息可能导致信息混乱,当存在多处代码设置相同的信息时需要注意是否导致信息混乱。

第一个问题暂时通过保留原有日志记录方式解决,若接口非spring ws实现则采用原来切面记录日志的方式记录接口日志。第二个问题目前并未完全解决,需要根据情况单独处理。新的方式基本解决了原来日志记录的缺点。

  • 代码入侵降低,服务端接口只通过修改配置文件实现日志记录,客户端仍需要设置关键字和上下文信息,但是不再限定特定方法。
  • 存在部分日志丢失,通过原生日志记录最大限度的保证了日志记录的可靠性。
  • 部分接口入参为对象时需要将对象转成字符串,原生日志记录避免了请求的二次序列化。

新方式的实现原理

服务端接口日志实现

服开系统作为服务端需要在配置文件中配置服务端对应的beanName与接口关键字在报文中字段映射,若beanName对应唯一的接口则无需报文中字段beanName就是接口的ID。然后再配置接口ID对应的接口上下文信息,接口上下文信息包括:接口名称、发送方、接收方、以及报文关键字提取信息,两个映射处理完毕服务端接口日志就记录完成了。

配置项解读:

interfaceconfig:

是服务端接口上下文信息的配置顶级节点,配置类中含两个映射beanName2keyCodekey2Context:

  • beanName2keyCode,是服务端@Endpoint发布的服务beanName与唯一标识一个接口的接口ID之间的映射
    我们接口项目发布一个@Endpoint给其他服务调用,在业务逻辑上可能存在多个接口共用一个@Endpoint的情况,此时接口唯一标识还需要通过请求中的某个字段来确定。
    如果一个@Endpoint在业务逻辑上就只是一个接口,那么@Endpoint对应的beanName就是接口ID,此时beanName2KeyCode无需配置映射;当一个@Endpoint对应到多个接口时,我们需要配置beanName与对应报文中可以区分多个接口之间的节点Name作映射关系。
  • key2Context,是接口ID与businessKey对应报文节点的映射
    每一个接口都应该能从请求报文中获取到业务关键字,将关键字记录到日志中,记录下来的日志才能被搜索到,因此所有接口都应该配置这个配置项,若未配置则日志虽然记录下来但是无法通过businesskey查询到。

    pboss2SgServiceImpl_replyWorkSheetConfirm: #接口ID

    interfaceName: 售中/售前归档确认 #接口名称
    sender: PBOSS #接口发送方
    receive: 业务开通 #接收方
    businessKeyCode: orderId #业务关键字在报文中的节点
    

无法映射到businessKeyCode的情况可以使用ORDERLOG进行手工记录,代码示例如下:

...手工获取到businessKey...
ORDERLOG.serverSentLog(businessKey, request);
...业务代码...
ORDERLOG.serverReceivedLog(response);
return response;

服开作为客户端实现

服开系统通过客户端请求外系统的情况,我们先开发一个spring ws的客户端client,然后为client编写一个clientWrap类,这个类只是对client做了一层封装,在clientWrap类中我们通过MDC的方式记录接口上下文信息,以及报文关键字信息。

若客户端非spring ws实现,则需要在真实客户端调用的前后采用手工记录日志的方式实现,示例代码如下:

ORDERLOG.clientSentLog("请求的报文");
String result = client.call("请求报文或对象").getReturn();
ORDERLOG.clientReceivedLog(result);

接口日志的查看

接口日志通过logger_name和报文关键字信息从es中过滤出来展示,日志的消息字段格式:
接口名称,发送方:发送方名称,接收方:接收方名称,发送消息!/收到消息!/接收方返回消息!
通过点开详情查看接口消息的具体内容。