-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Added Jakarta and Javax instrumentations #3989
base: main
Are you sure you want to change the base?
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
a456e47
to
9628ee9
Compare
Thanks Marcin for working on this! I'm wondering about the instrumentation for the JAX RS client bits. This is using the I'm also wondering about the same problems on the server side with the Now about the Tomcat Valve. I'm not super familiar with this contract, but I remember that the In general, in the Spring Framework instrumentation I tried to provide a lot of flexibility with custom names for observations and custom conventions (by providing constructor variants), but I'm wondering if this doesn't clutter the API in the end. Maybe this would be better handled with other contracts like the observation filter? I think this really depends on the use case. Maybe here JAX RS implementations would like to have a choice to come up with their own custom name? |
Yup I fully agree. I don't know if these are the right contracts to be used for proper wrapping. I think I'll need some help from people working with Jakarta API directly. I asked about it in the RestEASY tracker https://issues.redhat.com/browse/RESTEASY-3356?focusedId=22781362&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-22781362 and still am waiting for some feedback.
We had it turned on for Sleuth for ages and it worked well 🤷
I see. I guess it would be good for someone using JAX RS to chime in and give us feedback. Do you know anyone we can mention here? :) |
My bad, I missed that it was calling
Not really unfortunately. I think Helidon is integrating with tracing libraries directly. Maybe something there that we can take a look at? |
So I think we're using the proper APIs here. We just need to add support for error cases https://github.com/helidon-io/helidon/blob/helidon-3.x/tracing/jersey/src/main/java/io/helidon/tracing/jersey/AbstractTracingFilter.java#L125C9-L139 |
However for the client side there's also the interceptor https://github.com/helidon-io/helidon/blob/helidon-3.x/tracing/jersey-client/src/main/java/io/helidon/tracing/jersey/client/ClientTracingInterceptor.java . I'll check it out |
9628ee9
to
77bd8b1
Compare
d7c720f
to
7df8b37
Compare
...meter/core/instrument/binder/http/AbstractDefaultHttpClientRequestObservationConvention.java
Outdated
Show resolved
Hide resolved
...meter/core/instrument/binder/http/AbstractDefaultHttpClientRequestObservationConvention.java
Outdated
Show resolved
Hide resolved
...meter/core/instrument/binder/http/AbstractDefaultHttpClientRequestObservationConvention.java
Show resolved
Hide resolved
} | ||
|
||
protected KeyValues getHighCardinalityKeyValues(String requestUri) { | ||
return KeyValues.of(httpUrl(requestUri)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we add the user-agent too just like we did with the client?
...re/src/main/java/io/micrometer/core/instrument/binder/http/HttpObservationDocumentation.java
Show resolved
Hide resolved
...re/src/main/java/io/micrometer/core/instrument/binder/http/HttpObservationDocumentation.java
Show resolved
Hide resolved
7df8b37
to
c98b299
Compare
public String getContextualName(HttpJakartaClientRequestObservationContext context) { | ||
String method = context.getCarrier() != null | ||
? (context.getCarrier().getMethod() != null ? context.getCarrier().getMethod() : null) : null; | ||
return getContextualName(method); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
method
might be null here, which would result in http null
as the contextual name. Is that what we want?
...io/micrometer/jakarta/instrument/binder/http/HttpJakartaClientRequestObservationContext.java
Outdated
Show resolved
Hide resolved
...meter/core/instrument/binder/http/AbstractDefaultHttpServerRequestObservationConvention.java
Show resolved
Hide resolved
* @author Marcin Grzejszczak | ||
* @since 1.12.0 | ||
*/ | ||
public class HttpJakartaServerRequestObservationContext |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the client instrumentation, I think this should be named more specific to JAX-RS, since it requires JAX-RS classes. Perhaps JaxRsServerObservationContext
, and other classes that use this should be similarly renamed. The JavaDoc should also be updated to not mention Servlet but JAX-RS instead. We should also guide users on instrumenting at the right level. If they are using a framework, they should use the instrumentation there. If they are not using a framework or it doesn't have Micrometer instrumentation, but they are using JAX-RS, they should instrument at the JAX-RS level. If they are not using JAX-RS but are using Servlet, they should instrument at the Servlet level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated the naming of all related classes. The JavaDocs still need an update.
* Binds observation into Jersey. | ||
*/ | ||
@ConstrainedTo(RuntimeType.CLIENT) | ||
public class ObservationAutoDiscoverable implements AutoDiscoverable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not familiar with how libraries provide these AutoDiscoverable
implementations, but the JavaDoc mentions declaring the implementation in a file in META-INF/services
named org.glassfish.jersey.internal.spi.AutoDiscoverable
. Otherwise I guess this won't be auto-discovered, but as I said, I'm not familiar with this.
Looking at the documentation, it says this is internal, so I wonder if we should be providing this implementation at all.
Auto discovery functionality is in Jersey supported by implementing an internal
AutoDiscoverable
Jersey SPI. This interface is not public at the moment, and is subject to change in the future, so be careful when trying to use it.
* @author Marcin Grzejszczak | ||
* @since 1.12.0 | ||
*/ | ||
public class ObservationHttpJakartaInterceptor implements PostInvocationInterceptor { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be named more specifically to Jersey, since it implements a Jersey SPI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose this would be a problem in an environment that is using a JAX-RS implementation not including Jersey. I don't know how much of a problem that is, but as far as I understand, there will be issues with the filter linked in the JavaDoc if there are exceptions and an interceptor like this isn't configured.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ultimately, this is a blocker for providing JAX-RS client instrumentation. This was ran into by the OTel Java instrumentation as well in open-telemetry/opentelemetry-java-instrumentation#5430. There's more information about the limitation in JAX-RS itself in jakartaee/rest#684. So I don't know that we can provide JAX-RS client instrumentation without it being buggy. For now, we should not merge this code until/unless we can find an alternative solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@marcingrzejszczak The existing tests do not demonstrate the issue mentioned in the linked issues. Running the tests against a WireMock endpoint stubbed as follows demonstrates the issue:
wmRuntimeInfo.getWireMock().register(WireMock.get("/connectionReset").willReturn(WireMock.aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER)));
The observation will be started but never stopped in this case. The Jersey specific ObservationJerseyClientInterceptor
doesn't have any effect in the ObservationJaxRsHttpClientFilterTests
because RESTEasy is used. This demonstrates why I don't think we can offer JAX-RS client instrumentation: it leaks observations when there is no response from the server. Rather we can add Jersey client instrumentation to jersey-micrometer, where with the Jersey interceptor we can ensure no leaks. It's unfortunate we can't instrument at the JAX-RS level for the client side, but we don't want to put out instrumentation with a known leak.
Full test code and failure
Test method added to ObservationJaxRsHttpClientFilterTests
@Test
void clientFilterShouldWorkWithJakartaHttpClientForExceptionsWithoutResponse(WireMockRuntimeInfo wmRuntimeInfo) {
wmRuntimeInfo.getWireMock().register(WireMock.get("/connectionReset").willReturn(WireMock.aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER)));
try (Client client = ClientBuilder.newClient()) {
client.register(new ObservationJaxRsHttpClientFilter(observationRegistry, null));
final WebTarget target = client
.target("http://localhost:" + wmRuntimeInfo.getHttpPort() + "/connectionReset");
Exception exception = null;
try (Response response = target.request().get()) {
BDDAssertions.fail("Response should not be returned");
} catch (Exception e) {
exception = e;
}
BDDAssertions.then(exception).isNotNull().isInstanceOf(ProcessingException.class);
}
wmRuntimeInfo.getWireMock()
.verifyThat(WireMock.getRequestedFor(WireMock.urlEqualTo("/connectionReset"))
.withHeader("foo", WireMock.equalTo("bar")));
TestObservationRegistryAssert.then(observationRegistry)
.hasSingleObservationThat()
.hasBeenStarted()
.hasBeenStopped()
.hasError();
}
Test outcome
java.lang.AssertionError: Observation is not stopped
at io.micrometer.jakarta9.instrument.binder.http.jaxrs.client.ObservationJaxRsHttpClientFilterTests.clientFilterShouldWorkWithJakartaHttpClientForExceptionsWithoutResponses(ObservationJaxRsHttpClientFilterTests.java:112)
at java.base/java.lang.reflect.Method.invoke(Method.java:578)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
micrometer-core/build.gradle
Outdated
@@ -67,6 +67,8 @@ jar { | |||
dependencies { | |||
api project(":micrometer-commons") | |||
api project(":micrometer-observation") | |||
optionalApi 'javax.ws.rs:javax.ws.rs-api' // javax |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused why this is needed now but it wasn't prior to this pull request. I get a compilation error in JerseyTags
when I remove this, but that class in micrometer-core is unchanged.
...re/src/main/java/io/micrometer/core/instrument/binder/http/HttpObservationDocumentation.java
Outdated
Show resolved
Hide resolved
...java/io/micrometer/jakarta/instrument/binder/jms/DefaultJmsPublishObservationConvention.java
Outdated
Show resolved
Hide resolved
b2ca433
to
0537383
Compare
BDDAssertions.then(response.getStatus()).isEqualTo(200); | ||
BDDAssertions.then(response.getHeaders()).isNotEmpty(); | ||
List<Object> header = response.getHeaders().get("baz"); | ||
then(header).hasSize(1).containsOnly("bar"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are no assertions on the observation produced by the instrumentation. I was looking to add an implementation of our HttpServerTimingInstrumentationVerificationTests
for this instrumentation, but it looks like the uri
tag always has the value UNKNOWN
because setPathPattern
is never called on JaxRsContainerObservationContext
to have that working we've added additional convention and contexts around javax and jakarta classes
modified the jakarta imports to have both jakarta and javax imports on the classpath. Added filters for client and server side that use jakarta imports and added RestEasy tests for client and server
created a separate micrometer-jakarta module and put all the latest deps there. That way we could instrument latest Tomcat, Jersey etc. without any classpath issues
Remove unused dependencies, plugins, tasks, bundle imports.
Renames classes for the specific API/implementation they target for clarity. Re-organizes the classes into more specific packages to more easily identify related classes.
without this change JaxRs doesn't give an option to close the scope and stop the observation in a case of an exception with this change we're adding a proxy around invocations. Users need to use that proxying mechanism manually until a better solution is provided by JaxRs
04975ce
to
2b4ede7
Compare
Hi, What is the status of this? It looks pretty interesting to us. We have a product using Spring Boot 3.2 consisting of both legacy code using RESTEasy (via RESTEasy Spring Boot Starter) and newer code using Spring Web MVC. It would be nice if we could easily get the same metrics for both, specifically the Thanks. |
to have that working we've added additional convention and contexts around javax and jakarta classes. Added tests for RestEasy that use the instrumentations. Also added ObservedValve that is an instrumentation for Tomcat (we currently support Tomcat up till version 8 - no jakarta imports).
cc @bclozel