Journalisation sélective du trafic pour les services SOAP

Presque dans tous nos projets qui s'intègrent aux services clients, les services SOAP sont utilisés. Et dans chacun de ces projets, il est nécessaire de consigner les informations. Nous sommes particulièrement intéressés par les demandes de journalisation liées aux opérations commerciales de la solution. Par exemple, un employé a entré des données incorrectes et le service a renvoyé une erreur. Nous voulons connaître les détails de cette erreur et la corriger dans les plus brefs délais ou la soumettre pour discussion avec le client.

Nous aimerions voir les demandes de notre client et les réponses du service inchangées, car cela aide à résoudre les problèmes qui se posent.

Dans cet article, nous allons montrer comment nous avons configuré la journalisation sélective du trafic pour les services SOAP.



Le problème


Pour commencer, Spring a intégré la journalisation des demandes et des réponses qui est activée via la configuration

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

Le problème est que ces paramètres incluent la journalisation de tout le trafic SOAP. Nous n'avons besoin que de quelques méthodes, puis pas complètement. Par exemple, nous ne voulons pas voir les demandes de téléchargement de fichiers dans les journaux, car il s'agit d'une grande quantité de données.

Dans Spring Framework, la norme de facto pour la création d'un client soap est d'utiliser WebServiceGatewaySupport, dans lequel vous pouvez également ajouter le traitement des demandes et des réponses via ClientInterceptor. Mais dans ses méthodes, il est difficile de comprendre quelle méthode de notre client a initié l'appel. Et il n'est pas clair s'il est nécessaire d'enregistrer uniquement une demande, une réponse ou tout à la fois.

Solution


Nous utiliserons ClientInterceptor standard, mais nous lui fournirons les informations manquantes sur la signature de la méthode et les indicateurs «entrée», «sortie», grâce auxquels vous pourrez déterminer si la demande et la réponse doivent être enregistrées.

Nous réservons tout de suite qu'une telle solution ne fonctionnera pas avec la sérialisation / désérialisation en streaming. Mais nous utilisons Axiom, qui a l'option «mise en cache de la charge utile» activée par défaut et cette solution fonctionne.

Structure de stockage des informations:

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

Contexte de stockage des informations:

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

Pour placer les informations dans un contexte, nous utiliserons l'approche AOP avec une «tranche» pour les méthodes annotées et quelques «conseils»: avant et après l'appel de la méthode.

Annotation pour la méthode:

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

L'aspect lui-même a été mis sous chat.

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


Prenons à part


Une tranche est une expression qui signifie «toutes les méthodes annotées par SoapLogable». Nous utilisons les capacités d'AspectJ:

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

Il y a des conseils appelés avant une méthode qui est tombée sous une tranche:

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

Dans cette méthode, nous prenons une annotation, extrayons la signature de la méthode et les métadonnées de l'annotation, formons un objet pour ClientInterceptor et le mettons en contexte.

Il existe une astuce appelée après avoir appelé une méthode tombée sous une tranche:

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

Cela efface simplement le contexte. En fait, se connecter sous le chat:

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


Nous utilisons la méthode standard d '«interception» et de traitement des demandes SOAP, mais nous utilisons les informations du contexte pour enregistrer de manière sélective uniquement les méthodes dont nous avons besoin.

Profit!


L'utilisation de cette approche est très simple.

Vous ne voulez pas voir la demande avec le fichier joint? Ok!

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

Vous voulez tout voir? C’est encore plus simple.

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

Conclusion


Cet article a partagé leurs expériences sur la façon de configurer la journalisation sélective du trafic pour les services SOAP. Grâce à cela, nous surveillons rapidement les opérations commerciales et à tout moment utilisons des journaux pour analyser les problèmes. De plus, nous pouvons utiliser le même mécanisme pour suivre le temps passé à exécuter une demande SOAP et localiser rapidement la cause de l'erreur.

Nous partageons également un lien utile vers ce sujet: un bref ensemble d'exemples d'utilisation de tranches et des conseils sur AspectJ .

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


All Articles