Selektive Verkehrsprotokollierung für SOAP-Dienste

Fast in jedem unserer Projekte, die in den Kundenservice integriert sind, werden SOAP-Services verwendet. Und in jedem solchen Projekt gibt es die Aufgabe, Informationen zu protokollieren. Wir sind besonders an der Protokollierung von Anfragen interessiert, die sich auf den Geschäftsbetrieb der Lösung beziehen. Beispielsweise hat ein Mitarbeiter falsche Daten eingegeben und der Service hat einen Fehler zurückgegeben. Wir möchten die Details dieses Fehlers kennen und ihn so schnell wie möglich korrigieren oder zur Diskussion mit dem Kunden einreichen.

Wir möchten, dass die Anfragen unseres Kunden und die Antworten des Dienstes unverändert bleiben, da dies hilft, die auftretenden Probleme zu lösen.

In diesem Artikel zeigen wir, wie wir die selektive Verkehrsprotokollierung für SOAP-Dienste einrichten.



Das Problem


Zunächst verfügt Spring über eine integrierte Anforderungs- und Antwortprotokollierung, die durch die Konfiguration aktiviert wird

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

Das Problem ist, dass diese Einstellungen die Protokollierung des gesamten SOAP-Verkehrs umfassen. Wir brauchen nur wenige Methoden und dann nicht vollständig. Beispielsweise möchten wir keine Anforderungen zum Herunterladen von Dateien in den Protokollen sehen, da dies eine große Datenmenge ist.

Im Spring Framework besteht der De-facto-Standard zum Erstellen eines Seifenclients darin, WebServiceGatewaySupport zu verwenden, in dem Sie auch die Anforderungs- und Antwortverarbeitung über ClientInterceptor hinzufügen können. Bei seinen Methoden ist es jedoch schwierig zu verstehen, welche Methode unseres Kunden den Anruf initiiert hat. Und es ist nicht klar, ob es notwendig ist, nur eine Anfrage, eine Antwort oder alles auf einmal zu protokollieren.

Lösung


Wir werden den Standard-ClientInterceptor verwenden, ihm jedoch die fehlenden Informationen über die Methodensignatur und die Flags "Eingabe", "Ausgabe" bereitstellen, anhand derer Sie bestimmen können, ob die Anforderung und die Antwort protokolliert werden sollen.

Wir reservieren sofort, dass eine solche Lösung bei der Streaming-Serialisierung / Deserialisierung nicht funktioniert. Wir verwenden jedoch Axiom, bei dem die Option "Payload Caching" standardmäßig aktiviert ist und diese Lösung funktioniert.

Struktur zum Speichern von Informationen:

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

Kontext zum Speichern von Informationen:

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

Um Informationen in einen Kontext zu stellen, verwenden wir den AOP-Ansatz mit einem „Slice“ unter Verwendung kommentierter Methoden und einiger „Tipps“: vor und nach dem Aufruf der Methode.

Anmerkung zur Methode:

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

Der Aspekt selbst wurde unter Katze gestellt.

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


Nehmen wir auseinander


Ein Slice ist ein Ausdruck, der "alle von SoapLogable kommentierten Methoden" bedeutet. Wir nutzen die Funktionen von AspectJ:

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

Es gibt Ratschläge, die vor einer Methode genannt werden, die unter eine Scheibe gefallen ist:

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

Bei dieser Methode nehmen wir die Annotation, extrahieren die Methodensignatur und die Metainformationen aus der Annotation, bilden das Objekt für den ClientInterceptor und setzen es in den Kontext.

Nach dem Aufrufen einer Methode, die unter ein Slice gefallen ist, wird ein Tipp aufgerufen:

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

Es löscht nur den Kontext. Eigentlich unter der Katze anmelden:

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


Wir verwenden die Standardmethode zum Abfangen und Verarbeiten von SOAP-Anforderungen, verwenden jedoch Informationen aus dem Kontext, um nur die von uns benötigten Methoden selektiv zu protokollieren.

Gewinn!


Die Verwendung dieses Ansatzes ist sehr einfach.

Möchten Sie die Anfrage mit der angehängten Datei nicht sehen? Ok!

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

Willst du alles sehen? Es ist noch einfacher.

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

Fazit


In diesem Artikel wurden ihre Erfahrungen zum Konfigurieren der selektiven Verkehrsprotokollierung für SOAP-Dienste geteilt. Dank dessen überwachen wir schnell den Geschäftsbetrieb und verwenden jederzeit Protokolle, um Probleme zu analysieren. Darüber hinaus können wir denselben Mechanismus verwenden, um die für die Ausführung einer SOAP-Anforderung aufgewendete Zeit zu verfolgen und die Fehlerursache schnell zu lokalisieren.

Wir teilen auch einen nützlichen Link zu diesem Thema: eine kurze Reihe von Beispielen für die Verwendung von Slices und Tipps zu AspectJ .

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


All Articles