如何使用Spring Cloud Sleuth对Spring-WS编写的SOAP服务进行埋点?
Absolutely! You can implement distributed tracing with Spring Cloud Sleuth for SOAP web services built with Spring-WS—you just need to extend a few key components since it’s not out-of-the-box like REST services. Here’s a practical, step-by-step approach to make it work:
1. Create a Server-Side SOAP Interceptor for Span Management
Spring-WS uses EndpointInterceptor to hook into the request lifecycle. We’ll build an interceptor to initialize, manage, and close Sleuth spans as SOAP requests flow through your service.
Example Server Interceptor
@Component public class SleuthSoapServerInterceptor implements EndpointInterceptor { private final Tracer tracer; // Inject Sleuth's core Tracer component via constructor public SleuthSoapServerInterceptor(Tracer tracer) { this.tracer = tracer; } @Override public boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception { // Extract trace context from incoming SOAP headers (if passed from upstream services) SoapMessage soapRequest = (SoapMessage) messageContext.getRequest(); SoapHeader soapHeader = soapRequest.getSoapHeader(); String incomingTraceId = null; String incomingSpanId = null; if (soapHeader != null) { Iterator<SoapHeaderElement> headerElements = soapHeader.examineAllHeaderElements(); while (headerElements.hasNext()) { SoapHeaderElement element = headerElements.next(); String headerName = element.getName().getLocalPart(); if ("X-B3-TraceId".equals(headerName)) { incomingTraceId = element.getText(); } else if ("X-B3-SpanId".equals(headerName)) { incomingSpanId = element.getText(); } } } // Create or continue a span Span span; if (incomingTraceId != null && incomingSpanId != null) { // Continue the trace from upstream span = tracer.nextSpan(Span.contextBuilder() .traceId(incomingTraceId) .spanId(incomingSpanId) .build()); } else { // Start a new trace span = tracer.nextSpan().name("soap-server-request"); } // Activate the span for the current request context tracer.withSpan(span.start()); return true; } @Override public boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception { // Close the span when the response is sent finishCurrentSpan(null); return true; } @Override public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception { // Mark the span as errored if a SOAP fault occurs finishCurrentSpan(new RuntimeException("SOAP Fault encountered")); return true; } @Override public void afterCompletion(MessageContext messageContext, Object endpoint, Exception ex) throws Exception { // Fallback to ensure the span is always closed, even if an unhandled exception occurs finishCurrentSpan(ex); } private void finishCurrentSpan(Exception ex) { Span currentSpan = tracer.currentSpan(); if (currentSpan != null) { if (ex != null) { currentSpan.error(ex); } currentSpan.end(); } } }
Register the Server Interceptor
Add the interceptor to your Spring-WS configuration to activate it:
@Configuration public class SoapWebServiceConfig implements WsConfigurer { private final SleuthSoapServerInterceptor sleuthInterceptor; public SoapWebServiceConfig(SleuthSoapServerInterceptor sleuthInterceptor) { this.sleuthInterceptor = sleuthInterceptor; } @Override public void addInterceptors(List<EndpointInterceptor> interceptors) { interceptors.add(sleuthInterceptor); } // Add other Spring-WS configs (e.g., MessageDispatcherServlet, DefaultWsdl11Definition) here }
2. Propagate Trace Context to Downstream Services (If Needed)
If your SOAP service calls other services (SOAP or REST), you need to pass the Sleuth trace context downstream. For SOAP calls, we’ll use a client-side interceptor to inject trace headers into outgoing SOAP requests.
Example Client Interceptor
@Component public class SleuthSoapClientInterceptor implements ClientInterceptor { private final Tracer tracer; public SleuthSoapClientInterceptor(Tracer tracer) { this.tracer = tracer; } @Override public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException { SoapMessage soapRequest = (SoapMessage) messageContext.getRequest(); SoapHeader soapHeader = soapRequest.getSoapHeader(); if (soapHeader != null) { Span currentSpan = tracer.currentSpan(); if (currentSpan != null) { // Add B3 protocol headers to the SOAP request QName traceIdQName = new QName("https://your-service-namespace.com", "X-B3-TraceId"); soapHeader.addHeaderElement(traceIdQName).setText(currentSpan.context().traceId()); QName spanIdQName = new QName("https://your-service-namespace.com", "X-B3-SpanId"); soapHeader.addHeaderElement(spanIdQName).setText(currentSpan.context().spanId()); QName sampledQName = new QName("https://your-service-namespace.com", "X-B3-Sampled"); soapHeader.addHeaderElement(sampledQName).setText("1"); } } return true; } @Override public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException { return true; } @Override public boolean handleFault(MessageContext messageContext) throws WebServiceClientException { // Mark the current span as errored if a fault is received if (tracer.currentSpan() != null) { tracer.currentSpan().error(new RuntimeException("Downstream SOAP Fault received")); } return true; } @Override public void afterCompletion(MessageContext messageContext, Exception ex) throws WebServiceClientException { if (ex != null && tracer.currentSpan() != null) { tracer.currentSpan().error(ex); } } }
Attach the Client Interceptor to WebServiceTemplate
@Bean public WebServiceTemplate webServiceTemplate(SleuthSoapClientInterceptor clientInterceptor) { WebServiceTemplate webServiceTemplate = new WebServiceTemplate(); // Set your default URI, message factories, etc. webServiceTemplate.setInterceptors(new ClientInterceptor[]{clientInterceptor}); return webServiceTemplate; }
3. Update Logging to Include Trace IDs
Make sure your logging configuration includes Sleuth’s MDC variables so you can correlate logs across the trace. For Logback, update your logback.xml:
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg | traceId: %X{traceId:-} | spanId: %X{spanId:-}%n</pattern> </encoder> </appender>
Key Notes
- B3 Protocol: We’re using the standard B3 trace headers (
X-B3-TraceId,X-B3-SpanId) for compatibility with other tracing systems (like Zipkin). You can adjust the header names if needed, but sticking to B3 is recommended. - Error Handling: The interceptors ensure spans are marked as errored when exceptions or SOAP faults occur, which helps with debugging in tracing UIs.
- Customization: You can extend the interceptors to add additional span tags (e.g., SOAP operation name, request payload metadata) for richer trace data.
内容的提问来源于stack exchange,提问作者Ian Cccc

