SOAP服务的选择性流量记录

在我们与客户服务集成的任何项目中,几乎都使用SOAP服务。 在每个这样的项目中,都有一个记录信息的任务。 我们对记录与解决方案的业务操作相关的请求特别感兴趣。 例如,某员工输入了错误的数据,而服务返回了错误。 我们想知道此错误的详细信息,并尽快予以纠正或将其提交给与客户讨论。

我们希望客户的要求和服务的答复保持不变,因为这有助于解决出现的问题。

在本文中,我们将展示如何为SOAP服务设置选择性的流量日志记录。



问题


首先,Spring具有通过配置启用的内置请求和响应日志记录

logging.level.org.springframework.ws.client.MessageTracing.sent=TRACE logging.level.org.springframework.ws.client.MessageTracing.received=TRACE 

问题在于这些设置包括记录所有SOAP通信。 我们只需要一些方法,然后就不需要了。 例如,我们不想在日志中看到下载文件的请求,因为这是大量数据。

在Spring框架中,构建客户端肥皂的事实上的标准是使用WebServiceGatewaySupport,您还可以在其中通过ClientInterceptor添加请求和响应处理。 但是用他的方法很难理解我们客户的哪种方法发起了呼叫。 尚不清楚是否仅需要记录一个请求,一个答案或一次全部记录。

解决方案


我们将使用标准的ClientInterceptor,但向其提供有关方法签名和“输入”,“输出”标志的缺失信息,通过它们可以确定是否应记录请求和响应。

我们立即保留了这样的解决方案不适用于流序列化/反序列化的功能。 但是我们使用Axiom,它默认情况下启用了“有效负载缓存”选项,并且该解决方案有效。

信息存储结构:

 import lombok.Data; @Data public class SoapLoggingInfo { private String method; private boolean input = false; private boolean output = false; } 

存储信息的上下文:

 public class SoapLoggingContext { private static final ThreadLocal<SoapLoggingInfo> holder = new ThreadLocal<>(); public static void set(SoapLoggingInfo value) { holder.set(value); } public static SoapLoggingInfo get() { return holder.get(); } } 

为了将信息放置在上下文中,我们将使用AOP方法,并使用带注释的方法和一些“提示”的“切片”:在调用该方法之前和之后。

该方法的注释:

 @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Component public @interface SoapLoggable { boolean value() default true; boolean input() default true; boolean output() default true; } 

方面本身放在猫之下。

方面
 @Aspect @Component @Slf4j public class SoapLoggingAspect { @Value("${logging.soap.request.enabled:false}") private Boolean enabled; @Pointcut("execution(@ru.trueengineering.agentapi.logging.SoapLoggable * *(..))") public void soapLoggableMethod() {} @Before("soapLoggableMethod()") public void beforeSoapLoggable(JoinPoint joinPoint) { if (!enabled) { return; } SoapLoggable soapRequestLogger = getAnnotation(joinPoint); if (soapRequestLogger.value()) { SoapLoggingInfo loggingInfo = new SoapLoggingInfo(); loggingInfo.setInput(soapRequestLogger.input()); loggingInfo.setOutput(soapRequestLogger.output()); final Class<?> aClass = joinPoint.getTarget().getClass(); final Signature signature = joinPoint.getSignature(); final String name = signature.getName(); loggingInfo.setMethod(aClass.getSimpleName() + "." + name); SoapLoggingContext.set(loggingInfo); } } @After("soapLoggableMethod()") public void afterSoapLoggable(JoinPoint joinPoint) { SoapLoggingContext.set(null); } private SoapLoggable getAnnotation(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); return method.getAnnotation(SoapLoggable.class); } } 


让我们分开


切片是一个表达式,表示“所有由SoapLogable注释的方法”。 我们使用AspectJ的功能:

 @Pointcut("execution(@ru.trueengineering.agentapi.logging.SoapLoggable * *(..))") public void soapLoggableMethod() {} 

在属于某个方法的方法之前,有一些建议被调用:

 @Before("soapLoggableMethod()") public void beforeSoapLoggable(JoinPoint joinPoint) {} 

在此方法中,我们采用注释,从注释中提取方法签名和元信息,形成ClientInterceptor的对象并将其置于上下文中。

在调用某个方法之后,有一个提示被调用:

 @After("soapLoggableMethod()") public void afterSoapLoggable(JoinPoint joinPoint) {} 

它只是清除上下文。 实际上,在猫下记录:

记录中
 @Component @Slf4j public class SoapLoggingInterceptor extends ClientInterceptorAdapter { @Override public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException { SoapLoggingInfo info = SoapLoggingContext.get(); if (info != null && info.isInput()) { ByteArrayOutputStream xml = new ByteArrayOutputStream(); try { messageContext.getRequest().writeTo(xml); log.debug(": " + info.getMethod() + ", :" + xml.toString(StandardCharsets.UTF_8)); } catch (IOException e) { log.error("  SOAP request", e); } } return true; } @Override public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException { return handleResponseOrFault(messageContext); } @Override public boolean handleFault(MessageContext messageContext) throws WebServiceClientException { return handleResponseOrFault(messageContext); } private boolean handleResponseOrFault(MessageContext messageContext) { SoapLoggingInfo info = SoapLoggingContext.get(); if (info != null && info.isOutput()) { ByteArrayOutputStream xml = new ByteArrayOutputStream(); try { messageContext.getResponse().writeTo(xml); log.debug(": " + info.getMethod() + ", :" + xml.toString(StandardCharsets.UTF_8)); } catch (IOException e) { log.error("  SOAP response", e); } } return true; } } 


我们使用“拦截”和处理SOAP请求的标准方法,但是我们使用上下文中的信息来选择性地仅记录所需的方法。

赢利!


使用这种方法非常简单。

不想看到带有附件的请求? 好吧

 @SoapLoggable(input = false) public Optional<Osago2Response<ArrayOfKeyValuePairOfstringstring>> attachFile( final AttachFileRequest attachFileRequest) { return send(new WsAttachFileRequest(attachFileRequest)); } 

想看一切吗? 甚至更容易。

 @SoapLoggable public Optional<Osago2Response<CalcResult>> calculate(final CalcRequest calcRequest) { } 

结论


本文分享了他们有关如何为SOAP服务配置选择性流量日志记录的经验。 因此,我们可以快速监控业务运营,并随时使用日志来分析问题。 另外,我们可以使用相同的机制来跟踪执行SOAP请求所花费的时间,并快速定位错误原因。

我们还共享了一个指向该主题的有用链接: 简要说明如何在AspectJ上使用切片和技巧

Source: https://habr.com/ru/post/zh-CN456712/


All Articles