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 R2dbc statement javaagent instrumentation #7977

Merged

Conversation

PhilKes
Copy link
Contributor

@PhilKes PhilKes commented Mar 4, 2023

This PR resolves #2515 .
It adds javaagent instrumentation for r2dbc-spi >= v1.0

As suggested by @mp911de in #2515 (comment) I used the r2dbc-proxy ProxyConnectionFactory to intercept Database query executions and create according spans.

Example span from example project using spring-boot-starter-data-r2dbc Application:
r2dbc-example

@PhilKes PhilKes requested a review from a team as a code owner March 4, 2023 13:01
@PhilKes
Copy link
Contributor Author

PhilKes commented Mar 4, 2023

Open Questions:

I had a look at the jdbc instrumentation and for the same example as above the spans look like this:
jdbc-example

I am guessing the r2dbc example should have the same amount/type of spans as jdbc, but I am a bit confused of all the spans.
The first database span (db 167.08µs) contains set autocommit = ?, for the r2dbc example auto-commit is set to true by default and there doesnt seem to be an additional query for setting the autocommit, so this span should not exist in r2dbc?

Same question for the Transaction.commit span, it seems the ProxyMethodExecutionListener#before/afterCommitTransactionOnConnection() are not called if auto-commit is true, so do I have to add a Transaction.commit span manually after any query if auto-commit is true?

Also, is the SELECT CustomerJDBC simply a wrapper-span around the actual sql-query span?

@github-actions github-actions bot requested a review from theletterf March 4, 2023 13:01

@Override
public boolean isHelperClass(String className) {
return className.startsWith("io.r2dbc.proxy");
Copy link
Contributor

Choose a reason for hiding this comment

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

The drawback of doing it this way is that when the application also contains the r2dbc-proxy library there may be incompatibilities unless the library bundled in the agent has the same version that the application has.

Copy link
Contributor Author

@PhilKes PhilKes Mar 6, 2023

Choose a reason for hiding this comment

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

Thanks for your feedback.
So the alternative would be to basically reimplement the r2dbc-proxy library in order to get Query Listeners just to not use r2dbx-proxy directly? It just seemed a bit redundant to me, but I get your point.
After @marcingrzejszczak #7977 (comment), should I be simply using the existing ObservationProxyExecutionListener or stick to the manually implemented TraceProxyListener, or even move away entirely from r2dbc-proxy for the auto instrumentation?

Copy link
Contributor

Choose a reason for hiding this comment

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

We could shade the r2dbc-proxy library under a different package so it wouldn't conflict with whatever is in the application. I think this isn't super important, we can always fix it later. @marcingrzejszczak is one of the authors of micrometer tracing. As for his suggestion I'm not quite sure what he means. It could be either that he is suggesting to implement some sort of bridge with micrometer api so that their instrumentations could be automatically used or he might be hinting that instead of implementing this instrumentation for opentelemetry you could be using micrometer tracing where this instrumentation already exists.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I'm suggesting that since the instrumentation is there providing a bridge would mean you have nothing to do 🤷 Otherwise you can just replicate the same code but use the OTel API.

Copy link
Member

Choose a reason for hiding this comment

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

We could shade the r2dbc-proxy library under a different package so it wouldn't conflict with whatever is in the application.

We're doing a similar thing in the couchbase instrumentation. @PhilKes you could probably implement it in a somewhat similar manner.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could shade the r2dbc-proxy library under a different package so it wouldn't conflict with whatever is in the application.

We're doing a similar thing in the couchbase instrumentation. @PhilKes you could probably implement it in a somewhat similar manner.

@mateuszrzeszutek I tried to shade the r2dbc-proxy the same way as in the couchbase instrumentation, but I ran into a problem when running the tests:

R2dbcStatementTest > testQueries(Parameter) > io.opentelemetry.javaagent.instrumentation.r2dbc.v1_0.R2dbcStatementTest.testQueries(Parameter)[8] FAILED
    java.util.ServiceConfigurationError: io.r2dbc.spi.ConnectionFactoryProvider: Provider io.r2dbc.proxy.ProxyConnectionFactoryProvider not found
        at java.base/java.util.ServiceLoader.fail(ServiceLoader.java:593)
        at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.nextProviderClass(ServiceLoader.java:1219)
        at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNextService(ServiceLoader.java:1228)
        at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNext(ServiceLoader.java:1273)
        at java.base/java.util.ServiceLoader$2.hasNext(ServiceLoader.java:1309)
        at java.base/java.util.ServiceLoader$3.hasNext(ServiceLoader.java:1393)
        at io.r2dbc.spi.ConnectionFactories.find(ConnectionFactories.java:108)
        at io.opentelemetry.javaagent.instrumentation.r2dbc.v1_0.AbstractR2dbcStatementTest.createProxyConnectionFactory(AbstractR2dbcStatementTest.java:93)
        at io.opentelemetry.javaagent.instrumentation.r2dbc.v1_0.R2dbcStatementTest.createProxyConnectionFactory(R2dbcStatementTest.java:44)

I dont get why ConnectionFactoryProvider looks for the io.r2dbc.proxy package, the shaded imports work everywhere else and the ServiceLoader should find the ProxyConnectionFactoryProvider in the io.opentelemetry.javaagent.instrumentation.r2dbc.v1_0.shaded.io.r2dbc.proxy package, or is there something wrong with the shaded r2dbc-proxy?

Copy link
Contributor

@laurit laurit Mar 14, 2023

Choose a reason for hiding this comment

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

@PhilKes I think it is better to to rename packages only in the code that gets embedded in the agent and leave the library module unshaded. I created a pr that applies on top of your pr for that PhilKes#1

@marcingrzejszczak
Copy link
Contributor

BTW you can use Micrometer Observation API that is already there in R2DBC proxy (https://github.com/r2dbc/r2dbc-proxy/tree/main/src/main/java/io/r2dbc/proxy/observation) to have the instrumentation done for you.

@PhilKes PhilKes requested review from laurit and removed request for theletterf March 8, 2023 17:49
@mateuszrzeszutek
Copy link
Member

The first database span (db 167.08µs) contains set autocommit = ?, for the r2dbc example auto-commit is set to true by default and there doesnt seem to be an additional query for setting the autocommit, so this span should not exist in r2dbc?

No, you shouldn't worry about keeping it 1:1 with JDBC.

Same question for the Transaction.commit span, it seems the ProxyMethodExecutionListener#before/afterCommitTransactionOnConnection() are not called if auto-commit is true, so do I have to add a Transaction.commit span manually after any query if auto-commit is true?

Also, is the SELECT CustomerJDBC simply a wrapper-span around the actual sql-query span?

I think both of the Transaction.commit and SELECT CustomerJDBC might be Hibernate spans.

Copy link
Member

@mateuszrzeszutek mateuszrzeszutek left a comment

Choose a reason for hiding this comment

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

Thanks @PhilKes !

Comment on lines 44 to 47
ConnectionFactory proxiedFactory =
ProxyConnectionFactory.builder(originalConnectionFactory)
.listener(new TraceProxyListener(instrumenterBuilder.build(), connectionFactoryOptions))
.build();
Copy link
Member

Choose a reason for hiding this comment

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

I think this part should live in the R2dbcTelemetry class - instead of the getConnectionFactory method there should be a wrapConnectionFactory(ConnectionFactory, ConnectionFactoryOptions) (or similar name) that takes any connection factory and returns a decorated instance. The R2dbcTelemetry class should include any actual connection factory, it should be possible to use R2dbcTelemetry to decorate multiple connection factories - at least that's how we're doing it in all other library instrumentations.

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 moved it into the R2dbcTelemetry as suggested, so that any number of ConnectionFactory can be wrapped.

Comment on lines 52 to 54
.setStatementSanitizationEnabled(
ConfigPropertiesUtil.getBoolean(
"otel.instrumentation.common.db-statement-sanitizer.enabled", true))
Copy link
Member

Choose a reason for hiding this comment

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

In general, usually we don't use properties in library instrumentations - instead, we expose a builder method that allows users to configure the setting by themselves. The JDBC library instrumentation is not a good example of that because it utilizes the Driver decorator approach - where it's not possible to have a builder to configure a setting.
See the CassandraTelemetryBuilder for an example.

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 added the the flag as a builder method


@Override
public boolean isHelperClass(String className) {
return className.startsWith("io.r2dbc.proxy");
Copy link
Member

Choose a reason for hiding this comment

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

We could shade the r2dbc-proxy library under a different package so it wouldn't conflict with whatever is in the application.

We're doing a similar thing in the couchbase instrumentation. @PhilKes you could probably implement it in a somewhat similar manner.

@PhilKes PhilKes requested review from mateuszrzeszutek and removed request for laurit and theletterf March 12, 2023 10:00
@mateuszrzeszutek
Copy link
Member

Hey @PhilKes , can you rebase (or clean merge) your branch with main? Right now this PR includes lots of changes that are not yours

@PhilKes
Copy link
Contributor Author

PhilKes commented Mar 14, 2023

Hey @PhilKes , can you rebase (or clean merge) your branch with main? Right now this PR includes lots of changes that are not yours

Sorry for that, I fixed the rebase

@laurit
Copy link
Contributor

laurit commented Mar 15, 2023

@PhilKes to get rid of the muzzle failure https://github.com/open-telemetry/opentelemetry-java-instrumentation/actions/runs/4425709289/jobs/7761112434 Try adding extraDependency("io.projectreactor:reactor-core:3.4.12") as described in

extraDependency("javax.servlet:javax.servlet-api:3.0.1")

@PhilKes PhilKes requested review from laurit and removed request for mateuszrzeszutek and theletterf March 20, 2023 11:43
@PhilKes PhilKes requested review from mateuszrzeszutek and removed request for theletterf March 20, 2023 17:33
Copy link
Member

@mateuszrzeszutek mateuszrzeszutek left a comment

Choose a reason for hiding this comment

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

Thanks @PhilKes !

@mateuszrzeszutek mateuszrzeszutek merged commit a6bc3b1 into open-telemetry:main Mar 23, 2023
@juandaco
Copy link

juandaco commented Apr 3, 2023

Hello all. Thank you so much for your work on supporting thi!

I'm unable to get R2DBC tracing on a Spring Boot application running with the javaagent (using the proxy). Is there any particular configuration that is required for this to work?

@trask
Copy link
Member

trask commented Apr 3, 2023

hi @juandaco!

can you open a new issue?

you shouldn't need to make any code changes if you are using the javaagent (e.g. you shouldn't need to use a proxy)

and if you can post a minimal repro that is usually the easiest way for folks to understand and help out

@pkgonan
Copy link

pkgonan commented Apr 14, 2023

Hi. The actual query was performed 1 time, but there seems to be a bug exposed to APM by duplicating 2 times for the same query.

스크린샷 2023-04-14 오전 11 18 07

@PhilKes
Copy link
Contributor Author

PhilKes commented Apr 22, 2023

Hi. The actual query was performed 1 time, but there seems to be a bug exposed to APM by duplicating 2 times for the same query.

스크린샷 2023-04-14 오전 11 18 07

Hi, could you please open a new issue with this?
Are you using the r2dbc-proxy? Maybe there is an issue if you use the opentelemetry-autoinstrumentation (which already uses r2dbc-proxy) and then use another Proxy on top of it

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

Successfully merging this pull request may close these issues.

Supporting r2dbc
7 participants