Registro de tráfego seletivo para serviços SOAP

Quase em qualquer um dos nossos projetos que se integra ao atendimento ao cliente, os serviços SOAP são usados. E em cada projeto, existe uma tarefa de registrar informações. Estamos especialmente interessados ​​em registrar solicitações relacionadas às operações comerciais da solução. Por exemplo, um funcionário inseriu dados incorretos e o serviço retornou um erro. Queremos conhecer os detalhes desse erro e corrigi-lo o mais rápido possível ou enviá-lo para discussão com o cliente.

Gostaríamos de ver as solicitações do nosso cliente e as respostas do serviço inalteradas, pois isso ajuda a resolver os problemas que surgem.

Neste artigo, mostraremos como configuramos o log de tráfego seletivo para serviços SOAP.



O problema


Para começar, o Spring possui logs de solicitação e resposta integrados, habilitados por meio da configuração

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

O problema é que essas configurações incluem o log de todo o tráfego SOAP. Precisamos de apenas alguns métodos e depois não completamente. Por exemplo, não queremos ver solicitações para baixar arquivos nos logs, porque essa é uma grande quantidade de dados.

No Spring Framework, o padrão de fato para a construção de um sabão para cliente é usar o WebServiceGatewaySupport, no qual você também pode adicionar processamento de solicitação e resposta por meio do ClientInterceptor. Mas, nos métodos dele, é difícil entender qual método do nosso cliente iniciou a chamada. E não está claro se é necessário registrar apenas uma solicitação, uma resposta ou de uma só vez.

Solução


Usaremos o ClientInterceptor padrão, mas forneceremos as informações ausentes sobre a assinatura do método e os sinalizadores "entrada", "saída", pelos quais você pode determinar se a solicitação e a resposta devem ser registradas.

Fazemos uma reserva imediata de que tal solução não funcionará com serialização / desserialização de streaming. Mas usamos o Axiom, que tem a opção "cache de carga útil" ativada por padrão e esta solução funciona.

Estrutura para armazenar informações:

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

Contexto para armazenar informações:

 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(); } } 

Para colocar as informações em um contexto, usaremos a abordagem AOP com uma "fatia" para métodos anotados e algumas "dicas": antes e depois do método ser chamado.

Anotação para o método:

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

O aspecto em si foi colocado sob gato.

Aspecto
 @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); } } 


Vamos desmontar


Uma fatia é uma expressão que significa "todos os métodos anotados por SoapLogable". Usamos os recursos do AspectJ:

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

Há conselhos chamados antes de um método que tenha caído em uma fatia:

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

Nesse método, fazemos uma anotação, extraímos a assinatura do método e as meta informações da anotação, formamos um objeto para ClientInterceptor e colocamos no contexto.

Existe uma dica chamada após chamar um método que caiu em uma fatia:

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

Apenas limpa o contexto. Na verdade, registrando sob o gato:

Registo
 @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; } } 


Usamos o método padrão de "interceptar" e processar solicitações SOAP, mas usamos informações do contexto para registrar seletivamente apenas os métodos de que precisamos.

Lucro!


Usar esta abordagem é muito simples.

Não deseja ver a solicitação com o arquivo anexado? Ok!

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

Quer ver tudo? É ainda mais fácil.

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

Conclusão


Este artigo compartilhou suas experiências sobre como configurar o log de tráfego seletivo para serviços SOAP. Graças a isso, monitoramos rapidamente as operações comerciais e a qualquer momento usamos logs para analisar problemas. Além disso, podemos usar o mesmo mecanismo para rastrear o tempo gasto na execução de uma solicitação SOAP e localizar rapidamente a causa do erro.

Também compartilhamos um link útil para este tópico: um breve conjunto de exemplos de uso de fatias e dicas no AspectJ .

Source: https://habr.com/ru/post/pt456712/


All Articles