Hampir di setiap proyek kami yang terintegrasi dengan layanan pelanggan, layanan SOAP digunakan. Dan di setiap proyek semacam itu ada tugas mencatat informasi. Kami terutama tertarik pada permintaan logging yang terkait dengan operasi bisnis dari solusi. Misalnya, seorang karyawan memasukkan data yang salah dan layanan mengembalikan kesalahan. Kami ingin mengetahui detail kesalahan ini dan memperbaikinya sesegera mungkin atau mengirimkannya untuk diskusi dengan pelanggan.
Kami ingin melihat permintaan klien kami dan jawaban layanan tidak berubah, karena ini membantu untuk menyelesaikan masalah yang muncul.
Dalam artikel ini, kami akan menunjukkan bagaimana kami menyiapkan pendataan lalu lintas selektif untuk layanan SOAP.

Masalah
Untuk memulainya, Spring memiliki pendataan permintaan dan respons bawaan yang diaktifkan melalui konfigurasi
logging.level.org.springframework.ws.client.MessageTracing.sent=TRACE logging.level.org.springframework.ws.client.MessageTracing.received=TRACE
Masalahnya adalah bahwa pengaturan ini termasuk mencatat semua lalu lintas SOAP. Kami hanya membutuhkan beberapa metode, dan kemudian tidak sepenuhnya. Misalnya, kami tidak ingin melihat permintaan untuk mengunduh file dalam log, karena ini adalah sejumlah besar data.
Dalam Kerangka Kerja Musim Semi, standar de facto untuk membangun klien sabun adalah dengan menggunakan WebServiceGatewaySupport, di mana Anda juga dapat menambahkan pemrosesan permintaan dan tanggapan melalui ClientInterceptor. Tetapi dalam metodenya sulit untuk memahami metode mana dari klien kami yang memulai panggilan. Dan tidak jelas apakah perlu mencatat hanya permintaan, jawaban, atau sekaligus.
Solusi
Kami akan menggunakan ClientInterceptor standar, tetapi menyediakannya dengan informasi yang hilang tentang tanda tangan metode dan bendera "input", "output", dimana Anda dapat menentukan apakah permintaan dan respons harus dicatat.
Kami segera memesan bahwa solusi seperti itu tidak akan berfungsi dengan streaming serialisasi / deserialisasi. Tapi kami menggunakan Aksioma, yang memiliki opsi "payload caching" diaktifkan secara default dan solusi ini berfungsi.
Struktur untuk menyimpan informasi:
import lombok.Data; @Data public class SoapLoggingInfo { private String method; private boolean input = false; private boolean output = false; }
Konteks untuk menyimpan informasi:
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(); } }
Untuk menempatkan informasi dalam suatu konteks, kita akan menggunakan pendekatan AOP dengan "slice" untuk metode yang beranotasi dan beberapa "tips": sebelum dan sesudah metode dipanggil.
Penjelasan untuk metode ini:
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Component public @interface SoapLoggable { boolean value() default true; boolean input() default true; boolean output() default true; }
Aspek itu sendiri diletakkan di bawah kucing.
Aspek @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); } }
Mari kita berpisah
Sepotong adalah ekspresi yang berarti "semua metode yang dijelaskan oleh SoapLogable." Kami menggunakan kemampuan AspectJ:
@Pointcut("execution(@ru.trueengineering.agentapi.logging.SoapLoggable * *(..))") public void soapLoggableMethod() {}
Ada saran yang dipanggil sebelum metode yang jatuh di bawah irisan:
@Before("soapLoggableMethod()") public void beforeSoapLoggable(JoinPoint joinPoint) {}
Dalam metode ini, kami mengambil anotasi, mengekstrak tanda tangan metode, dan informasi meta dari anotasi, membentuk objek untuk ClientInterceptor dan meletakkannya dalam konteks.
Ada tip yang dipanggil setelah memanggil metode yang jatuh di bawah irisan:
@After("soapLoggableMethod()") public void afterSoapLoggable(JoinPoint joinPoint) {}
Itu hanya membersihkan konteksnya. Sebenarnya, masuk di bawah kucing:
Penebangan @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; } }
Kami menggunakan metode standar "mencegat" dan memproses permintaan SOAP, tetapi kami menggunakan informasi dari konteks untuk secara selektif mencatat hanya metode yang kami butuhkan.
Untung!
Menggunakan pendekatan ini sangat sederhana.
Tidak ingin melihat permintaan dengan file terlampir? Ok!
@SoapLoggable(input = false) public Optional<Osago2Response<ArrayOfKeyValuePairOfstringstring>> attachFile( final AttachFileRequest attachFileRequest) { return send(new WsAttachFileRequest(attachFileRequest)); }
Ingin melihat semuanya? Itu bahkan lebih mudah.
@SoapLoggable public Optional<Osago2Response<CalcResult>> calculate(final CalcRequest calcRequest) { }
Kesimpulan
Artikel ini berbagi pengalaman mereka tentang cara mengkonfigurasi pendataan lalu lintas selektif untuk layanan SOAP. Berkat ini, kami dengan cepat memantau operasi bisnis dan kapan saja menggunakan log untuk menganalisis masalah. Selain itu, kita dapat menggunakan mekanisme yang sama untuk melacak waktu yang dihabiskan untuk mengeksekusi permintaan SOAP dan untuk dengan cepat melokalisasi penyebab kesalahan.
Kami juga membagikan tautan yang bermanfaat ke topik ini:
serangkaian contoh penggunaan irisan dan tip di AspectJ .