33
44package com .azure .messaging .servicebus ;
55
6+ import com .azure .core .amqp .implementation .TracerProvider ;
7+ import com .azure .core .util .Context ;
68import com .azure .core .util .logging .ClientLogger ;
9+ import com .azure .core .util .tracing .ProcessKind ;
710import com .azure .messaging .servicebus .implementation .models .ServiceBusProcessorClientOptions ;
811import org .reactivestreams .Subscriber ;
912import org .reactivestreams .Subscription ;
13+ import reactor .core .publisher .Signal ;
1014import reactor .core .scheduler .Schedulers ;
1115
16+ import java .io .Closeable ;
17+ import java .io .IOException ;
18+ import java .util .Locale ;
1219import java .util .Objects ;
20+ import java .util .Optional ;
1321import java .util .concurrent .Executors ;
1422import java .util .concurrent .ScheduledExecutorService ;
1523import java .util .concurrent .TimeUnit ;
1624import java .util .concurrent .atomic .AtomicBoolean ;
1725import java .util .concurrent .atomic .AtomicReference ;
1826import java .util .function .Consumer ;
1927
28+ import static com .azure .core .util .tracing .Tracer .AZ_TRACING_NAMESPACE_KEY ;
29+ import static com .azure .core .util .tracing .Tracer .DIAGNOSTIC_ID_KEY ;
30+ import static com .azure .core .util .tracing .Tracer .ENTITY_PATH_KEY ;
31+ import static com .azure .core .util .tracing .Tracer .HOST_NAME_KEY ;
32+ import static com .azure .core .util .tracing .Tracer .MESSAGE_ENQUEUED_TIME ;
33+ import static com .azure .core .util .tracing .Tracer .SCOPE_KEY ;
34+ import static com .azure .core .util .tracing .Tracer .SPAN_CONTEXT_KEY ;
35+ import static com .azure .messaging .servicebus .implementation .ServiceBusConstants .AZ_TRACING_NAMESPACE_VALUE ;
36+ import static com .azure .messaging .servicebus .implementation .ServiceBusConstants .AZ_TRACING_SERVICE_NAME ;
37+
2038/**
2139 * The processor client for processing Service Bus messages. {@link ServiceBusProcessorClient
2240 * ServiceBusProcessorClients} provides a push-based mechanism that invokes the message processing callback when a
@@ -44,6 +62,7 @@ public final class ServiceBusProcessorClient implements AutoCloseable {
4462 private final AtomicReference <Subscription > receiverSubscription = new AtomicReference <>();
4563 private final AtomicReference <ServiceBusReceiverAsyncClient > asyncClient = new AtomicReference <>();
4664 private final AtomicBoolean isRunning = new AtomicBoolean ();
65+ private final TracerProvider tracerProvider ;
4766 private ScheduledExecutorService scheduledExecutor ;
4867
4968 /**
@@ -65,6 +84,7 @@ public final class ServiceBusProcessorClient implements AutoCloseable {
6584 this .processorOptions = Objects .requireNonNull (processorOptions , "'processorOptions' cannot be null" );
6685 this .asyncClient .set (sessionReceiverBuilder .buildAsyncClientForProcessor ());
6786 this .receiverBuilder = null ;
87+ this .tracerProvider = processorOptions .getTracerProvider ();
6888 }
6989
7090 /**
@@ -84,6 +104,7 @@ public final class ServiceBusProcessorClient implements AutoCloseable {
84104 this .processorOptions = Objects .requireNonNull (processorOptions , "'processorOptions' cannot be null" );
85105 this .asyncClient .set (receiverBuilder .buildAsyncClient ());
86106 this .sessionReceiverBuilder = null ;
107+ this .tracerProvider = processorOptions .getTracerProvider ();
87108 }
88109
89110 /**
@@ -164,12 +185,22 @@ public void onNext(ServiceBusMessageContext serviceBusMessageContext) {
164185 if (serviceBusMessageContext .hasError ()) {
165186 handleError (serviceBusMessageContext .getThrowable ());
166187 } else {
188+ Context processSpanContext = null ;
167189 try {
168190 ServiceBusReceivedMessageContext serviceBusReceivedMessageContext =
169191 new ServiceBusReceivedMessageContext (receiverClient , serviceBusMessageContext );
192+
193+ processSpanContext =
194+ startProcessTracingSpan (serviceBusMessageContext .getMessage (),
195+ receiverClient .getEntityPath (), receiverClient .getFullyQualifiedNamespace ());
196+ if (processSpanContext .getData (SPAN_CONTEXT_KEY ).isPresent ()) {
197+ serviceBusMessageContext .getMessage ().addContext (SPAN_CONTEXT_KEY , processSpanContext );
198+ }
170199 processMessage .accept (serviceBusReceivedMessageContext );
200+ endProcessTracingSpan (processSpanContext , Signal .complete ());
171201 } catch (Exception ex ) {
172202 handleError (new ServiceBusException (ex , ServiceBusErrorSource .USER_CALLBACK ));
203+ endProcessTracingSpan (processSpanContext , Signal .error (ex ));
173204 if (!processorOptions .isDisableAutoComplete ()) {
174205 logger .warning ("Error when processing message. Abandoning message." , ex );
175206 abandonMessage (serviceBusMessageContext , receiverClient );
@@ -201,6 +232,54 @@ public void onComplete() {
201232 });
202233 }
203234
235+ private void endProcessTracingSpan (Context processSpanContext , Signal <Void > signal ) {
236+ if (processSpanContext == null ) {
237+ return ;
238+ }
239+
240+ Optional <Object > spanScope = processSpanContext .getData (SCOPE_KEY );
241+ // Disposes of the scope when the trace span closes.
242+ if (!spanScope .isPresent () || !tracerProvider .isEnabled ()) {
243+ return ;
244+ }
245+ if (spanScope .get () instanceof Closeable ) {
246+ Closeable close = (Closeable ) processSpanContext .getData (SCOPE_KEY ).get ();
247+ try {
248+ close .close ();
249+ tracerProvider .endSpan (processSpanContext , signal );
250+ } catch (IOException ioException ) {
251+ logger .error ("endTracingSpan().close() failed with an error %s" , ioException );
252+ }
253+
254+ } else {
255+ logger .warning (String .format (Locale .US ,
256+ "Process span scope type is not of type Closeable, but type: %s. Not closing the scope and span" ,
257+ spanScope .get () != null ? spanScope .getClass () : "null" ));
258+ }
259+ }
260+
261+ private Context startProcessTracingSpan (ServiceBusReceivedMessage receivedMessage , String entityPath ,
262+ String fullyQualifiedNamespace ) {
263+
264+ Object diagnosticId = receivedMessage .getApplicationProperties ().get (DIAGNOSTIC_ID_KEY );
265+ if (diagnosticId == null || !tracerProvider .isEnabled ()) {
266+ return Context .NONE ;
267+ }
268+
269+ Context spanContext = tracerProvider .extractContext (diagnosticId .toString (), Context .NONE );
270+
271+ spanContext = spanContext
272+ .addData (ENTITY_PATH_KEY , entityPath )
273+ .addData (HOST_NAME_KEY , fullyQualifiedNamespace )
274+ .addData (AZ_TRACING_NAMESPACE_KEY , AZ_TRACING_NAMESPACE_VALUE );
275+ spanContext = receivedMessage .getEnqueuedTime () == null
276+ ? spanContext
277+ : spanContext .addData (MESSAGE_ENQUEUED_TIME ,
278+ receivedMessage .getEnqueuedTime ().toInstant ().getEpochSecond ());
279+
280+ return tracerProvider .startSpan (AZ_TRACING_SERVICE_NAME , spanContext , ProcessKind .PROCESS );
281+ }
282+
204283 private void abandonMessage (ServiceBusMessageContext serviceBusMessageContext ,
205284 ServiceBusReceiverAsyncClient receiverClient ) {
206285 try {
0 commit comments