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

Set a injected CDI CommandListener to the mongodb configuration. #12082

Merged
merged 2 commits into from
Oct 22, 2020

Conversation

juazugas
Copy link
Contributor

This implementation allows injecting a CommandListener to the current MongoDB client configuration via CDI bean definition.
The idea came from using the MP opentracing library and trying to include the mongodb tracing spans.

Following the instructions in the OpenTracing Mongo Driver Instrumentation there is a way to include the tracing information simply adding a CommandListener to the mongoclient settings.

I prepared an example to show the MongoDB tracing:
https://github.com/juazugas/quarkus-mongodb-opentracing/blob/master/src/main/java/com/example/mongodb/tracing/TracingCommandListenerProducer.java
on project sample https://github.com/juazugas/quarkus-mongodb-opentracing/

Copy link
Member

@gsmet gsmet left a comment

Choose a reason for hiding this comment

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

I added a few small comments.

List<ConnectionPoolListener> connectionPoolListeners = new ArrayList<>(connectionPoolListenerSuppliers.size());
for (Supplier<ConnectionPoolListener> item : connectionPoolListenerSuppliers) {
connectionPoolListeners.add(item.get());
}
final CommandListener commandListener = Arc.container().instance(CommandListener.class).get();
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't we support multiple CommandListeners given it looks like MongoDB supports it?

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree. The limitation is on my knowledge of how to retrieve the List of CDI beans of a given type. any help?

Copy link
Contributor

Choose a reason for hiding this comment

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

Set<Bean<?>> beans = Arc.container().beanManager().getBeans(CommandListener.class); will return all beans of type CommandListener.

Then you can get the bean from Arc by type if the name is null or by name.
You can find an example here but there may be an easier way to do it if you don't rely on CDI but instead use Jandex and retrieve all implementors of CommandListener.

return commandListeners;
}

public void setCommandListenerList(List<CommandListener> commandListenersList) {
Copy link
Member

Choose a reason for hiding this comment

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

I would drop this method, it's useless, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It depends on the above comment (#12082 (comment)). If we need to add a single CommandListener or a full list. But finally it should exist only the one that it's used.

}

public void addCommandListener(CommandListener commandListener) {
if (null != commandListener) {
Copy link
Member

Choose a reason for hiding this comment

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

You already test the caller, I think we can drop this test here and consider it will be called with a non-null commandListener.

@gsmet
Copy link
Member

gsmet commented Sep 14, 2020

@loicmathieu @pavolloffay WDYT about this? I wonder if we should somehow integrate things better with OpenTracing to just have a boolean configuration to enable tracing?

I have the same question for the JDBC driver implementation btw.

@loicmathieu
Copy link
Contributor

This PR raises two questions:

  • If we automatically add CommandListener, people may ask for the other types of listeners
  • Why not discovering the listeners from Jandex as we did for Discriminators and Codecs ? Note that in this case they will not be CDI beans so you will not be able to inject dependencies on them

For opentracing support, I would rather prefere activating it via a boolean property (with a check to verify that smallrye-opentracing is in the classpath).

For the JDBC support it's a different story as it's already configured via the inclusion of the library + some config property (JDBC driver name).

@juazugas
Copy link
Contributor Author

juazugas commented Sep 14, 2020

This PR raises two questions:

  • If we automatically add CommandListener, people may ask for the other types of listeners

The current approach is based on the user definition via CDI of the CommandListener that the application would like to be injected.

  • Why not discovering the listeners from Jandex as we did for Discriminators and Codecs ? Note that in this case they will not be CDI beans so you will not be able to inject dependencies on them

+1 That's another good approach, define the specific TracingCommandListener "the same way" as the metrics to the ConnectionListeners are defined. We could define more settings for the Listener or even there could be more configuration values specifying the commands to exclude from the tracing. (https://github.com/opentracing-contrib/java-mongo-driver#exclude-commands-from-tracing)

For opentracing support, I would rather prefere activating it via a boolean property (with a check to verify that smallrye-opentracing is in the classpath).

+1 to that approach ^^

@juazugas juazugas force-pushed the ext-mongodb-opentracing branch from 80d7036 to b7092f2 Compare September 16, 2020 13:05
@juazugas
Copy link
Contributor Author

@gsmet @loicmathieu
In reference to the comments above (#12082 (comment))

For opentracing support, I would rather prefere activating it via a boolean property (with a check to verify that smallrye-opentracing is in the classpath).

I've prepared another branch with the suggestions (the branch extends from the one of this PR).

The tracing of the Mongo driver commands now checks the build property quarkus.mongodb.tracing.enabled and when is set to true and the opentracing capability is active then the TracingCommandListener is prepared and injected into the MongoDB settings.

The comparison of the changes can be seen on:
master...juazugas:ext-mongodb-opentracing-byconfig

As a working example of this branch, the project mongodb-opentracing/ext-mongodb-opentracing-byconfig has the property quarkus.mongo.tracing.enabled set to true.

@pavolloffay
Copy link
Contributor

@loicmathieu @pavolloffay WDYT about this? I wonder if we should somehow integrate things better with OpenTracing to just have a boolean configuration to enable tracing?

You should consider that OpenTracing is basically in the maintenance mode, it will be sunsetted once OpenTelemetry is released. Maybe it's better to wait until OTEL GA is released and then do the integration?

@loicmathieu
Copy link
Contributor

@pavolloffay yesterday Ken Finnigan told me that OpenTelemetry will be integrated in Quarkus by the end of the year.
And that Quarkus will support the two during some time.

So it makes sense to me to integrate MongoDB with OpenTracing while waiting for OpenTelemetry support.

Copy link
Contributor

@loicmathieu loicmathieu left a comment

Choose a reason for hiding this comment

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

My main point is whether or not the CommandListener needs to comes from CDI (so we can inject dependencies in it) or from Jandex (so we can retrieve it easily but injection will not work for it).
Everything else is build from CDI for MongoDB

this.commandListeners = new ArrayList<>();
this.disableSslSupport = disableSslSupport;
}

public MongoClientSupport(List<String> codecProviders, List<String> bsonDiscriminators,
List<ConnectionPoolListener> connectionPoolListeners, List<CommandListener> commandListeners,
boolean disableSslSupport) {
this.codecProviders = codecProviders;
this.bsonDiscriminators = bsonDiscriminators;
this.connectionPoolListeners = connectionPoolListeners;
this.commandListeners = commandListeners;
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is needed to provides methods with and without the List of CommandListener. This method is only used inside the deployment module so it's signature can be modified.

Comment on lines 39 to 41
if (null != commandListener) {
mongoClientSupport.addCommandListener(commandListener);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This should not be needed, just pass the Command listener list as we do for all other MongoDB related classes

@loicmathieu
Copy link
Contributor

@juazugas regarding what you done here master...juazugas:ext-mongodb-opentracing-byconfig there is no need for a CDI Command Listener so I would implement the retrieval of the Command Listener list from Jandex (as we did for BsonDiscriminators and other MongoDB related classes).

@juazugas
Copy link
Contributor Author

@juazugas regarding what you done here master...juazugas:ext-mongodb-opentracing-byconfig there is no need for a CDI Command Listener so I would implement the retrieval of the Command Listener list from Jandex (as we did for BsonDiscriminators and other MongoDB related classes).

@loicmathieu
Exactly!, that branch is meant to override the one in this PR. No CDI injection of command listener/s and if enabled by the config a specific tracing command listener is created for the mongo client configuration.
On that branch, there's no command listener injected by CDI (maybe I chose a bad name for the MongoTracingCommandListenerProducer ). However, the TracingCommandListener cannot be instantiated from the constructor like the CodecProviders or registered like the BsonDiscriminator, the tracer is needed to be instantiated.
What could be done (I think it is already registered at that stage) is to extract it from the INSTANCE of the GlobalTracer, so no access using CDI would be done (like in MongoMetricsConnectionPoolListener).

@loicmathieu
Copy link
Contributor

OK, I propose to make this in two phases.
First, go with this PR without CDI.
Then open a PR to deal with OpenTracing.

@juazugas
Copy link
Contributor Author

juazugas commented Sep 23, 2020

First, go with this PR without CDI.

Ok. Search the implementors and add them after default constructor instantiation.

Then open a PR to deal with OpenTracing.

Super! baking new PR. 😁

@juazugas juazugas marked this pull request as ready for review September 29, 2020 14:49
@@ -249,6 +250,12 @@ private MongoClientSettings createMongoConfiguration(MongoClientConfig config) {
CodecRegistries.fromProviders(providers));
settings.codecRegistry(registry);

List<CommandListener> listeners = new ArrayList<>();
Copy link
Contributor

Choose a reason for hiding this comment

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

You create a List fo CommandListener in the getCommandListener method so this one is not needed.

Comment on lines 18 to 20
assertThat(startedEvent, notNullValue());
assertThat(startedEvent.getCommandName(), anyOf(equalTo("listDatabases"), equalTo("endSessions")));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Having the insertions here is not realy readable for a test.
Can you instead register the CommandEvent in a static List and assert in the test that the event is present inside the list ?

Copy link
Contributor

@loicmathieu loicmathieu left a comment

Choose a reason for hiding this comment

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

Please stash all your commits then I'll approve this PR


import com.mongodb.event.CommandListener;
import com.mongodb.event.CommandStartedEvent;

public class MockCommandListener implements CommandListener {

private CommandStartedEvent commandStartedEvent;
public static final List<String> events = new ArrayList<>();
Copy link
Contributor

Choose a reason for hiding this comment

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

constants should be uppercase

@juazugas juazugas force-pushed the ext-mongodb-opentracing branch from 7715e92 to 8a5a020 Compare September 30, 2020 15:17
Copy link
Contributor

@loicmathieu loicmathieu left a comment

Choose a reason for hiding this comment

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

LGTM

@loicmathieu
Copy link
Contributor

@gsmet this PR changed to only allow to setup a CommandListener, not via CDI as for other MongoDB component.

It will then enable to implement an OpenTracing CommandListener for MongoDB.

Copy link
Member

@gsmet gsmet left a comment

Choose a reason for hiding this comment

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

Added some comments inline.

}

@BuildStep
List<ReflectiveClassBuildItem> addCodecsAndDiscriminatorsAndListenersToNative(CodecProviderBuildItem codecProviders,
Copy link
Member

Choose a reason for hiding this comment

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

Maybe rename the method to addExtensionPointsToNative ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure

List<CommandListener> listeners = new ArrayList<>();
for (String name : classNames) {
try {
Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(name);
Copy link
Member

Choose a reason for hiding this comment

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

Could we use Class.forName here instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok. Tested on a project with the jar and the native image does not make the difference, I suppose because we do
the initialization immediately after loading the class.

@@ -249,6 +250,8 @@ private MongoClientSettings createMongoConfiguration(MongoClientConfig config) {
CodecRegistries.fromProviders(providers));
settings.codecRegistry(registry);

settings.commandListenerList(getCommandListeners(mongoClientSupport.getCommandListeners()));
Copy link
Member

Choose a reason for hiding this comment

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

Maybe call this one commandListeners?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure about this suggestion.

  • The settings class is from mongodb driver (com.mongodb.MongoClientSettings.Builder)
  • The MongoClientSupport class has all its fields exposed to getter methods
  • The private method getCommandListeners is called after the same naming pattern as the getCodeProviders method.

It could be ... extracting the call to the getter function to a variable called commandListeners (very likely to happen in a future PR). For example:

List<CommandListeners> commandListeners = getCommandListeners(mongoClientSupport.getCommandListeners());
settings.commandListenerList(commandListeners);

@gsmet
Copy link
Member

gsmet commented Oct 12, 2020

Hmmm, I don't know what was merged that is causing so many conflicts but this patch will need a big rebase.

Please ping me when done so that I can have a final look. Thanks!

@gsmet gsmet added the triage/needs-rebase This PR needs to be rebased first because it has merge conflicts label Oct 12, 2020
@juazugas juazugas force-pushed the ext-mongodb-opentracing branch from aee8c58 to 557b58a Compare October 12, 2020 15:30
@juazugas
Copy link
Contributor Author

Hmmm, I don't know what was merged that is causing so many conflicts but this patch will need a big rebase.

Please ping me when done so that I can have a final look. Thanks!

Ping @gsmet, the rebase is done.

@juazugas juazugas requested a review from gsmet October 12, 2020 15:49
@gsmet gsmet force-pushed the ext-mongodb-opentracing branch from 557b58a to 1230272 Compare October 13, 2020 12:45
Copy link
Member

@gsmet gsmet left a comment

Choose a reason for hiding this comment

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

I adjusted the Class.forName() call a bit and squashed everything.

Looks good now, thanks!

@gsmet gsmet added triage/waiting-for-ci Ready to merge when CI successfully finishes and removed triage/needs-rebase This PR needs to be rebased first because it has merge conflicts labels Oct 13, 2020
@gsmet gsmet added this to the 1.10 - master milestone Oct 13, 2020

import io.quarkus.builder.item.SimpleBuildItem;

public final class CommandListenerBuildItem extends SimpleBuildItem {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you please add some Javadoc to this class? This is necessary for the All Build Items page

@gsmet gsmet force-pushed the ext-mongodb-opentracing branch from 1230272 to e09bd88 Compare October 22, 2020 13:36
@gsmet
Copy link
Member

gsmet commented Oct 22, 2020

I added some javadoc and rebased.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
triage/waiting-for-ci Ready to merge when CI successfully finishes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants