Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add helper methods for manual spans in mutiny pipelines #45478

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

arn-ivu
Copy link

@arn-ivu arn-ivu commented Jan 9, 2025

Fix #44411

In a traditional blocking and synchronous framework the opentelemetry context is attached to ThreadLocal. In reactive programming, where multiple processings share the same event-loop thread, one has to use Vert.x duplicated contexts instead (see https://quarkus.io/guides/duplicated-context). wrapWithSpan ensures that the pipeline is executed on a duplicated context (If the current context already is duplicated, it will stay the same. Therefore, nested calls to wrapWithSpan will all run on the same vert.x context).

Another difficulty of mutiny pipelines is, that usually only one parameter flows through the pipeline. In oder to end spans and to close the current scope at the end of the pipeline, span and scope must be stored in the mutiny-context. They are stored in a stack datastructure so that multiple nested spans/scopes are closed in the correct order.

inspired by Jan Peremsky (https://github.com/jan-peremsky/quarkus-reactive-otel/blob/c74043d388ec4df155f466f1d6938931c3389b70/src/main/java/com/fng/ewallet/pex/Tracer.java) and edeandrea (https://github.com/quarkusio/quarkus-super-heroes/blob/main/event-statistics/src/main/java/io/quarkus/sample/superheroes/statistics/listener/SuperStats.java)

In a traditional blocking and synchronous framework the opentelemetry context
is attached to ThreadLocal. In reactive programming, where multiple processings share the same event-loop thread, one has to use Vert.x duplicated contexts instead (see https://quarkus.io/guides/duplicated-context).
wrapWithSpan ensures that the pipeline is executed on a duplicated context (If the current context already is duplicated, it will stay the same. Therefore, nested calls to wrapWithSpan will all run on the same vert.x context).

Another difficulty of mutiny pipelines is, that usually only one parameter flows
through the pipeline. In oder to end spans and to close the current scope at the
end of the pipeline, span and scope must be stored in the mutiny-context. They are
stored in a stack datastructure so that multiple nested spans/scopes are closed in the correct order.

inspired by Jan Peremsky (https://github.com/jan-peremsky/quarkus-reactive-otel/blob/c74043d388ec4df155f466f1d6938931c3389b70/src/main/java/com/fng/ewallet/pex/Tracer.java)
and edeandrea (https://github.com/quarkusio/quarkus-super-heroes/blob/main/event-statistics/src/main/java/io/quarkus/sample/superheroes/statistics/listener/SuperStats.java)
Copy link

quarkus-bot bot commented Jan 9, 2025

Thanks for your pull request!

Your pull request does not follow our editorial rules. Could you have a look?

  • title should not end up with dot

This message is automatically generated by a bot.

Copy link

quarkus-bot bot commented Jan 9, 2025

/cc @brunobat (opentelemetry), @radcortez (opentelemetry)

@arn-ivu arn-ivu changed the title Add helper methods for manual spans in mutiny pipelines. Add helper methods for manual spans in mutiny pipelines Jan 9, 2025
extensions/opentelemetry/runtime/pom.xml Outdated Show resolved Hide resolved

@BeforeEach
public void setup() {
GlobalOpenTelemetry.resetForTest();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, Please use the injected OpenTelemetry instance. No need for any of this setup.
You can also inject the tracer and span.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think to do that, I would have to use QuarkusUnitTest-Extension, right?
Unfortunately QuarkusUnitTest together with ParameterizedTests does not seem to work. I think I would then do only one of the tests on all three contexts, and the rest of the tests on root-context only.
What do you think @brunobat? Or do I overlook a simpler solution?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have pushed an update with the altered tests.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few examples that work with parameterised tests:


and

@brunobat brunobat requested a review from ozangunalp January 9, 2025 14:49

This comment has been minimized.

Copy link

github-actions bot commented Jan 9, 2025

🎊 PR Preview f010b20 has been successfully built and deployed to https://quarkus-pr-main-45478-preview.surge.sh/version/main/guides/

  • Images of blog posts older than 3 months are not available.
  • Newsletters older than 3 months are not available.

This comment has been minimized.

I will squash with the previous commit once everything is approved
Copy link

quarkus-bot bot commented Jan 9, 2025

Status for workflow Quarkus CI

This is the status report for running Quarkus CI on commit dc05142.

Failing Jobs

Status Name Step Failures Logs Raw logs Build scan
Initial JDK 17 Build Build Failures Logs Raw logs 🔍

You can consult the Develocity build scans.

Failures

⚙️ Initial JDK 17 Build #

- Failing: extensions/opentelemetry/deployment 
! Skipped: devtools/bom-descriptor-json docs extensions/liquibase-mongodb/deployment and 62 more

📦 extensions/opentelemetry/deployment

Failed to execute goal net.revelc.code:impsort-maven-plugin:1.12.0:check (check-imports) on project quarkus-opentelemetry-deployment: Imports are not sorted in /home/runner/work/quarkus/quarkus/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/MutinyTracingHelperTest.java

Copy link

quarkus-bot bot commented Jan 9, 2025

Status for workflow Quarkus Documentation CI

This is the status report for running Quarkus Documentation CI on commit dc05142.

✅ The latest workflow run for the pull request has completed successfully.

It should be safe to merge provided you have a look at the other checks in the summary.

@ozangunalp
Copy link
Contributor

I need to read the motivation behind the Tracer method linked to the issue itself. For me, this needs to be equivalent to the WithSpanInterceptor, ie. an extracted method returning Uni and annotated with @WithSpan should have the exact same effect.

A helper method like this doesn't need to be redundant, but I don't see why it wouldn't be implemented the same way.

As for what has been done in SuperStats, this has to do with handling message traces. I need to set up an environment myself to compare produced traces, but I am pretty sure such code is no longer needed for message traces.

@arn-ivu
Copy link
Author

arn-ivu commented Jan 10, 2025

@ozangunalp My usecase looks roughly like this

private void handleMessages() {
        consumer.getMessages() //from our own rabbitMQ-Lib. Will issue item whenever a message arrives
                .onItem()
                .transformToUniAndConcatenate(message -> wrapWithSpan(tracer, 
                                                //this is how our lib provides the remote context
						message.getTracingData().map(MessageTracingData::getContext), 
                                               "handleIncomingMessage", 
						Uni.createFrom()
                                .item(message)
                                .map(Message::getPayload)
                                .invoke(this::handlePayload) //actual message processing
                )
                .subscribe().with(...);
    }

it might be possible to solve it like this

private void handleMessages() {
        consumer.getMessages() //from our own rabbitMQ-Lib. Will issue item whenever a message arrives
                .onItem().invoke(message -> makeContextCurrent(message.getTracingData().map(MessageTracingData::getContext)))
                .onItem().call(this::handleIncomingMessage)
                .subscribe().with(...)
    }
    
@WithSpan
public Uni<Void> handleMessage(Message message){
	Uni.createFrom()
    	.item(message)
        .map(Message::getPayload)
        .invoke(this::handlePayload) //actual message processing
        .replaceWithVoid()
}

however, I have 2 problems with that

  1. I would have to expose the extracted Method with public (otherwise I get a warning that @WithSpan won't do anything)
  2. to make the provided context current (in order for WithSpan to set the parent correctly) I would have to make sure to run on a duplicate context and I think I would have to close the context later as well. So essentially the same problems I encounter when creating the span manually.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement withSpan method utilities for Mutiny
3 participants