2828import javax .xml .transform .TransformerConfigurationException ;
2929import javax .xml .transform .TransformerException ;
3030
31+ import io .micrometer .observation .Observation ;
32+ import io .micrometer .observation .ObservationConvention ;
33+ import io .micrometer .observation .ObservationRegistry ;
3134import org .apache .commons .logging .Log ;
3235import org .apache .commons .logging .LogFactory ;
3336import org .jspecify .annotations .Nullable ;
4447import org .springframework .ws .client .WebServiceIOException ;
4548import org .springframework .ws .client .WebServiceTransformerException ;
4649import org .springframework .ws .client .WebServiceTransportException ;
50+ import org .springframework .ws .client .core .observation .ClientWebServiceObservationContext ;
51+ import org .springframework .ws .client .core .observation .ClientWebServiceObservationConvention ;
52+ import org .springframework .ws .client .core .observation .ClientWebServiceObservationDocumentation ;
53+ import org .springframework .ws .client .core .observation .DefaultClientWebServiceObservationConvention ;
4754import org .springframework .ws .client .support .WebServiceAccessor ;
4855import org .springframework .ws .client .support .destination .DestinationProvider ;
4956import org .springframework .ws .client .support .interceptor .ClientInterceptor ;
@@ -134,6 +141,8 @@ public class WebServiceTemplate extends WebServiceAccessor implements WebService
134141 protected static final Log receivedMessageTracingLogger = LogFactory
135142 .getLog (WebServiceTemplate .MESSAGE_TRACING_LOG_CATEGORY + ".received" );
136143
144+ private static final ClientWebServiceObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultClientWebServiceObservationConvention ();
145+
137146 private @ Nullable Marshaller marshaller ;
138147
139148 private @ Nullable Unmarshaller unmarshaller ;
@@ -148,6 +157,10 @@ public class WebServiceTemplate extends WebServiceAccessor implements WebService
148157
149158 private @ Nullable DestinationProvider destinationProvider ;
150159
160+ private ObservationRegistry observationRegistry = ObservationRegistry .NOOP ;
161+
162+ private @ Nullable ClientWebServiceObservationConvention observationConvention ;
163+
151164 /** Creates a new {@code WebServiceTemplate} using default settings. */
152165 public WebServiceTemplate () {
153166 initDefaultStrategies ();
@@ -360,6 +373,50 @@ public final void setInterceptors(ClientInterceptor[] interceptors) {
360373 this .interceptors = interceptors ;
361374 }
362375
376+ /**
377+ * Configure an {@link ObservationRegistry} for collecting spans and metrics for
378+ * request execution. By default, {@link Observation observations} are no-ops.
379+ * @param observationRegistry the observation registry to use
380+ * @since 5.1.0
381+ */
382+ public void setObservationRegistry (ObservationRegistry observationRegistry ) {
383+ Assert .notNull (observationRegistry , "observationRegistry must not be null" );
384+ this .observationRegistry = observationRegistry ;
385+ }
386+
387+ /**
388+ * Return the configured {@link ObservationRegistry}.
389+ * @since 5.1.0
390+ */
391+ public ObservationRegistry getObservationRegistry () {
392+ return this .observationRegistry ;
393+ }
394+
395+ /**
396+ * Configure an {@link ObservationConvention} that sets the name of the
397+ * {@link Observation observation} as well as its
398+ * {@link io.micrometer.common.KeyValues} extracted from the
399+ * {@link ClientWebServiceObservationContext}. If none set, the
400+ * {@link DefaultClientWebServiceObservationConvention default convention} will be
401+ * used.
402+ * @param observationConvention the observation convention to use
403+ * @since 5.1.0
404+ * @see #setObservationRegistry(ObservationRegistry)
405+ */
406+ public void setObservationConvention (ClientWebServiceObservationConvention observationConvention ) {
407+ Assert .notNull (observationConvention , "observationConvention must not be null" );
408+ this .observationConvention = observationConvention ;
409+ }
410+
411+ /**
412+ * Return the configured {@link ClientWebServiceObservationConvention}, or
413+ * {@code null} if not set.
414+ * @since 5.1.0
415+ */
416+ public @ Nullable ClientWebServiceObservationConvention getObservationConvention () {
417+ return this .observationConvention ;
418+ }
419+
363420 /**
364421 * Initialize the default implementations for the template's strategies:
365422 * {@link SoapFaultMessageResolver},
@@ -622,7 +679,12 @@ public boolean sendAndReceive(String uri, WebServiceMessageCallback requestCallb
622679 @ Nullable WebServiceMessageCallback requestCallback , WebServiceMessageExtractor <T > responseExtractor )
623680 throws IOException {
624681 int interceptorIndex = -1 ;
625- try {
682+ ClientWebServiceObservationContext observationContext = new ClientWebServiceObservationContext (connection );
683+ Observation observation = ClientWebServiceObservationDocumentation .WEB_SERVICE_CLIENT_EXCHANGES
684+ .observation (this .observationConvention , DEFAULT_OBSERVATION_CONVENTION , () -> observationContext ,
685+ this .observationRegistry )
686+ .start ();
687+ try (Observation .Scope scope = observation .openScope ()) {
626688 if (requestCallback != null ) {
627689 requestCallback .doWithMessage (messageContext .getRequest ());
628690 }
@@ -647,6 +709,9 @@ public boolean sendAndReceive(String uri, WebServiceMessageCallback requestCallb
647709 return (T ) fallback ;
648710 }
649711 WebServiceMessage response = connection .receive (getMessageFactory ());
712+ if (response != null ) {
713+ observationContext .setResponse (response );
714+ }
650715 messageContext .setResponse (response );
651716 }
652717 logResponse (messageContext );
@@ -678,6 +743,9 @@ public boolean sendAndReceive(String uri, WebServiceMessageCallback requestCallb
678743 triggerAfterCompletion (interceptorIndex , messageContext , ex );
679744 throw ex ;
680745 }
746+ finally {
747+ observation .stop ();
748+ }
681749 }
682750
683751 /** Sends the request in the given message context over the connection. */
0 commit comments