Context Propagation for Distributed Tracing in the PubSub+ JCSMP API

Distributed tracing allows your enterprise applications to trace the flow of messages as they travel from your publisher, through the event mesh and to the receiving application. For a detailed overview see Distributed Tracing . For information about version requirements, see Distributed Tracing Version Compatibility.

For information about instrumenting your code for distributed tracing, see Instrumenting JCSMP for Distributed Tracing

  • The PubSub+ OpenTelemetry API Libraries support W3C propagators only.
  • For information about configuring OpenTelemetry SDK environment variables, see OpenTelemetry SDK Configuration.
  • By default, traces include command line parameters visible to backend applications like Jaeger. It is important to disable this feature for security purposes because these parameters may contain sensitive information such as your user name and password. For instructions, see Disabling Automatic Resource Providers in the OpenTelemetry documentation in GitHub.

Instrumenting JCSMP for Distributed Tracing

Manual Instrumentation involves making changes to your enterprise application's source code, and allows you to inject and extract additional context, such as baggage and trace states, into messages. Context propagation makes it easy to debug and optimize your application. For more information about context propagation in Solace event messages, see Distributed Tracing Context Propagation. The following examples show you how to create spans using the OpenTelemetry API.

How Context Propagation Enables Distributed Tracing in the JCSMP PubSub+ API

In your client application, you can use the OpenTelemetry API to create a span, which contains metadata about an operation in a distributed system. This span is associated with a context, which includes a unique TraceID. Next, when you use a PubSub+ message producer to publish a message, the Solace OTel integration package injects the context, which contains the TraceID, into the message. As the message travels through the event broker and is received by a consuming application, spans are generated at each step and have the same TraceID present in the original message context. When each span is closed in the publishing or consuming application, the Java OpenTelemetry API sends it to an OpenTelemetry collector, which collects, processes and exports the spans to a backend application that correlates the spans using their unique TraceID. A backend application uses the correlated spans to create a trace, which is an end-to-end snapshot detailing how the message traveled through the distributed system. If you do not use context propagation, then backend applications cannot use a unique TraceID to link the spans, making it difficult to trace the flow of messages through the distributed system.

Dependencies

To enable context propagation for distributed tracing, you must first add the Solace PubSub+ OpenTelemetry Integration For Solace JCSMP API library as a dependency in your application. This library automatically adds the OpenTelemetry API and SDK libraries, which are required for context propagation. For OpenTelemetry version compatibility see Distributed Tracing Version Compatibility. Adding the libraries gives you access to the following two interfaces:

  • SolaceJCSMPTextMapSetter— This interface allows a TextMapPropagator to inject context into a message.
  • SolaceJCSMPTextMapGetter— This interface allows a TextMapPropagator to extract context from a message.

This guide assumes you are familiar with configuring an instance of the OpenTelemetry class. For instructions for configuring OpenTelemetry objects, see OpenTelemetry Manual Instrumentation in Java in the OpenTelemetry documentation.

Generating a Send Span on Message Publish

Your publishing application can generate a send span and export it to the OpenTelemetry Collector. The following steps show you how to inject context into a message and generate a send span for a published message:

  1. Create a new span and set span attributes with the setAttribute() method. Next, set the current context as the parent of this span. Start the span with the startSpan() method:
  2. final Span sendSpan = tracer
        .spanBuilder("mySolacePublisherApp" + " " + MessagingOperationValues.PROCESS)
        .setSpanKind(SpanKind.CLIENT)
        // Publish to a non-temporary topic endpoint
        .setAttribute(SemanticAttributes.MESSAGING_DESTINATION_KIND, MessagingDestinationKindValues.TOPIC)
        .setAttribute(SemanticAttributes.MESSAGING_TEMP_DESTINATION, false)
        // Set more attributes as needed
        //.setAttribute(...)
        //.setAttribute(...)
        .setParent(Context.current()) // Set the current context as the parent span
        .startSpan();
    
  3. Set the created span from Step 1 (sendSpan in this example) as the new current context. Next, inject the current context into your message and then publish the message. Call the end() method on the span to export the span data:

  4. try (Scope scope = sendSpan.makeCurrent()) {
        final SolaceJCSMPCTextMapSetter setter = new SolaceJCSMPTextMapSetter();
        final TextMapPropagator propagator = openTelemetry.getPropagators().getTextMapPropagator();
        // Inject the current context with send span into the message
        propagator.inject(Context.current(), message, setter);
        // Publish the message to the given topic
        messageProducer.send(message, messageDestination);
    } catch (Exception e) {
        sendSpan.recordException(e);                          // Span can record an exception 
        sendSpan.setStatus(StatusCode.ERROR, e.getMessage()); // Set span status as ERROR/FAILED
    } finally {
        sendSpan.end(); // The span data is exported after you call end()
    }
    

Generating a Receive Span on Message Receive

Your consuming application can generate a receive span and then export it to the OpenTelemetry Collector. The following steps show you how to extract tracing context from a received message and generate a receive span:

  1. Use a SolaceJCSMPCTextMapGetter to extract any existing context from the received message:
  2. final SolaceJCSMPTextMapGetter getter = new SolaceJCSMPTextMapGetter();
    final Context extractedContext = openTelemetry.getPropagators().getTextMapPropagator()
           .extract(Context.current(), message, getter);
  3. Set the extracted context as the current context with the makeCurrent() method. Next create a child span (receiveSpan in this example) and set the extracted context as the parent of that child span. Start the span using the startSpan() method:
  4. try (Scope scope = extractedContext.makeCurrent()) {
        // Create a child span and set extracted/current context as the parent of this span
        final Span receiveSpan = tracer
            .spanBuilder("mySolaceReceiverApp" + " " + MessagingOperationValues.RECEIVE)
            .setSpanKind(SpanKind.CLIENT)
            // For the case the message was received on a non-temporary queue endpoint
            .setAttribute(SemanticAttributes.MESSAGING_DESTINATION_KIND,
                MessagingDestinationKindValues.QUEUE)
            .setAttribute(SemanticAttributes.MESSAGING_TEMP_DESTINATION, false)
            // Set more attributes as needed
            //.setAttribute(...)
            //.setAttribute(...)
            // Creates a parent-child relationship to a message publisher's application span
            .setParent(extractedContext)
            // Starts span
            .startSpan();
    
        // Try-catch continues in the next step...
    }
    
  5. Accept and process the received message, then call the end() method on the receive span to export the span data:
  6. try {
        // Do something with the message in a callback function
        messageProcessor.accept(receivedMessage);
    } catch (Exception e) {
        receiveSpan.recordException(e);                          // Span can record an exception
        receiveSpan.setStatus(StatusCode.ERROR, e.getMessage()); // and set span status as ERROR
    } finally {
        receiveSpan.end(); // Span data is exported when you call span.end()
    }