Business Traces

Just like metrics, it is also possible to add your own spans on arbitrary methods to provide more business value to the observability of your application.

Let’s return to our code!

Objectives

We want to add new spans to the traces generated in the easypay-service application to track payment processing and store events.

To achieve this goal, we will create new spans when the process and store methods of the com.worldline.easypay.payment.control.PaymentService class in the easypay-service module are invoked.

As a reminder, this class is the central component responsible for processing payments. It provides the public method accept, which delegates its responsibilities to two private methods:

  • process: which handles all the processing of the payment, including validation and calling third parties.
  • store: which saves the processing result in the database.

1. Add Required Dependencies

We need to add the io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations dependency to our module to access some useful annotations.

👀 This has already been done in advance for this workshop. The following dependencies were added to the Gradle build file (build.gradle.kts) of the easypay-service module:

dependencies {
    //...
    // Add opentelemetry Annotations support
    implementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations")

    // ...
}

2. Add Custom Spans

📝 To add new spans based on methods, we can simply use the @WithSpan Java annotation. When a traced transaction invokes the annotated method, a new span will be created. Here’s how to do it:

// ...

import io.opentelemetry.instrumentation.annotations.WithSpan;

@Service
public class PaymentService {
    // ...

    @WithSpan("easypay: Payment processing method")
    private void process(PaymentProcessingContext context) {
        //...
    }

    @WithSpan("easypay: Payment store method")
    private void store(PaymentProcessingContext context) {
        //...
    }
    
    // ...
}

📝 We can also provide additional information to the span, such as method parameters using the @SpanAttribute annotation:

// ...

import io.opentelemetry.instrumentation.annotations.SpanAttribute;

@Service
public class PaymentService {
    // ...

    @WithSpan("easypay: Payment processing method")
    private void process(@SpanAttribute("context") PaymentProcessingContext context) { // <-- HERE
        // ...
    }

    @WithSpan("easypay: Payment store method")
    private void store(@SpanAttribute("context") PaymentProcessingContext context) { // <-- HERE
        // ...
    }
    // ...
}

This will provide the whole PaymentProcessingContext into the trace.

3. Build and redeploy

🛠️ As we did before:

$ docker compose up -d --build easypay-service

4. Test it!

🛠️ Generate some payments:

$ http POST :8080/api/easypay/payments posId=POS-01 cardNumber=5555567898780008 expiryDate=789456123 amount:=40000

👀 Go back to Grafana and try to find your new traces using what you’ve learned previously. Observe the spans you added.

Caution

It may take some time for easypay-service to be registered in the service discovery and be available from the API gateway.
Similarly, your traces being ingested by Tempo might also take some time. Patience is key 😅