Casi en cualquiera de nuestros proyectos que se integra con los servicios al cliente, se utilizan los servicios SOAP. Y en cada proyecto de este tipo hay una tarea de registrar información. Estamos especialmente interesados en registrar solicitudes relacionadas con las operaciones comerciales de la solución. Por ejemplo, un empleado ingresó datos incorrectos y el servicio devolvió un error. Queremos conocer los detalles de este error y corregirlo lo antes posible o enviarlo para su discusión con el cliente.
Nos gustaría ver las solicitudes de nuestro cliente y las respuestas del servicio sin cambios, ya que esto ayuda a resolver los problemas que surgen.
En este artículo, mostraremos cómo configuramos el registro de tráfico selectivo para los servicios SOAP.

El problema
Para empezar, Spring tiene un registro incorporado de solicitudes y respuestas que se habilita a través de la configuración
logging.level.org.springframework.ws.client.MessageTracing.sent=TRACE logging.level.org.springframework.ws.client.MessageTracing.received=TRACE
El problema es que estas configuraciones incluyen el registro de todo el tráfico SOAP. Solo necesitamos unos pocos métodos, y luego no completamente. Por ejemplo, no queremos ver solicitudes de descarga de archivos en los registros, porque esta es una gran cantidad de datos.
En Spring Framework, el estándar de facto para crear un cliente de jabón es usar WebServiceGatewaySupport, en el que también puede agregar el procesamiento de solicitudes y respuestas a través de ClientInterceptor. Pero en sus métodos es difícil entender qué método de nuestro cliente inició la llamada. Y no está claro si es necesario registrar solo una solicitud, una respuesta o todas a la vez.
Solución
Utilizaremos el ClientInterceptor estándar, pero le proporcionaremos la información que falta sobre la firma del método y los indicadores de "entrada", "salida", mediante los cuales puede determinar si la solicitud y la respuesta deben registrarse.
Hacemos una reserva de inmediato para que dicha solución no funcione con la serialización / deserialización de transmisión. Pero usamos Axiom, que tiene la opción "almacenamiento en caché de carga útil" habilitada de forma predeterminada y esta solución funciona.
Estructura para almacenar información:
import lombok.Data; @Data public class SoapLoggingInfo { private String method; private boolean input = false; private boolean output = false; }
Contexto para almacenar información:
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 la información en un contexto, utilizaremos el enfoque AOP con un "corte" para los métodos anotados y un par de "consejos": antes y después de que se llame al método.
Anotación para el método:
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Component public @interface SoapLoggable { boolean value() default true; boolean input() default true; boolean output() default true; }
El aspecto en sí fue puesto bajo 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); } }
Desarmemos
Un segmento es una expresión que significa "todos los métodos anotados por SoapLogable". Utilizamos las capacidades de AspectJ:
@Pointcut("execution(@ru.trueengineering.agentapi.logging.SoapLoggable * *(..))") public void soapLoggableMethod() {}
Hay un consejo llamado antes de un método que ha caído bajo una rebanada:
@Before("soapLoggableMethod()") public void beforeSoapLoggable(JoinPoint joinPoint) {}
En este método, tomamos la anotación, extraemos la firma del método y la metainformación de la anotación, formamos el objeto para ClientInterceptor y lo ponemos en contexto.
Hay un consejo llamado después de llamar a un método que ha caído bajo una porción:
@After("soapLoggableMethod()") public void afterSoapLoggable(JoinPoint joinPoint) {}
Simplemente aclara el contexto. En realidad, iniciar sesión debajo del gato:
Registro @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 el método estándar de "interceptar" y procesar solicitudes SOAP, pero usamos información del contexto para registrar selectivamente solo los métodos que necesitamos.
Beneficio!
Usar este enfoque es muy simple.
¿No quieres ver la solicitud con el archivo adjunto? Ok!
@SoapLoggable(input = false) public Optional<Osago2Response<ArrayOfKeyValuePairOfstringstring>> attachFile( final AttachFileRequest attachFileRequest) { return send(new WsAttachFileRequest(attachFileRequest)); }
¿Quieres verlo todo? Es aún más fácil.
@SoapLoggable public Optional<Osago2Response<CalcResult>> calculate(final CalcRequest calcRequest) { }
Conclusión
Este artículo compartió sus experiencias sobre cómo configurar el registro de tráfico selectivo para los servicios SOAP. Gracias a esto, monitoreamos rápidamente las operaciones comerciales y en cualquier momento usamos registros para analizar problemas. Además, podemos usar el mismo mecanismo para realizar un seguimiento del tiempo dedicado a ejecutar una solicitud SOAP y localizar rápidamente la causa del error.
También compartimos un enlace útil a este tema: un
breve conjunto de ejemplos sobre el uso de sectores y consejos sobre AspectJ .