-
Notifications
You must be signed in to change notification settings - Fork 848
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
Allow OTLP gRPC exporters to work without grpc-java using okhttp directly. #3684
Conversation
@@ -75,8 +75,8 @@ val DEPENDENCIES = listOf( | |||
// using old version of okhttp to avoid pulling in kotlin stdlib | |||
// not using (old) okhttp bom because that is pulling in old guava version | |||
// and overriding the guava bom | |||
"com.squareup.okhttp3:okhttp:3.12.13", | |||
"com.squareup.okhttp3:okhttp-tls:3.12.13", | |||
"com.squareup.okhttp3:okhttp:3.14.9", |
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.
We were accidentally not using the latest version (.12.13 appeared newer but was actually a backport of a fix to older okhttp3)
If it seems OK to proceed, then I'll start by extracting common logic in the HTTP exporters into an internal utility first |
f8cf9bf
to
abeefbc
Compare
abeefbc
to
08fda86
Compare
String grpcStatus = response.header(GRPC_STATUS); | ||
if (grpcStatus == null) { | ||
try { | ||
grpcStatus = response.trailers().get(GRPC_STATUS); |
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.
By the way, I had an idea of trying out a completely no-deps artifact for Java 11. But then I found this 😂
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.
Well that's unfortunate. 😬
I think this is a cool idea! |
@@ -127,9 +129,28 @@ public OtlpHttpSpanExporter build() { | |||
} | |||
} |
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 would consider adding a new method, maybe called setGrpcEndpoint(String endpoint)
, which expects just an endpoint with scheme, host, and port (like in the otlp grpc exporter builders). If that endpoint is called, grpc
is enabled and the correct path GRPC_ENDPOINT_PATH
is automatically appended.
An upside of your approach where grpc is detected based on the endpoint is that this would work out of the box with autoconfigure - no additional properties needed.
I'm working on a different approach that actually has the normal |
@jack-berg By the way you mentioned having ideas for cleaning up some duplication among the exporters. I've ended up doing some of that in my not-yet-pushed code you may want to check to see if that pattern aligns with it or should be changed to align with it. |
I have tacked on a different approach to this PR, everything except what's in It detects whether the classpath is missing grpc-java, and includes okhttp, and uses an okhttp implementation automatically for that case. While the code may look like it's highly invasive for this use case, it's actually at the same time solving an issue we currently have in that all the gRPC exporters duplicate a lot of code. The extraction of |
This does seem like a decent approach to removing the code duplication. I do find the various names of the classes a bit hard to keep straight in my head without a bunch of thinking:
(and the various Builders as well) I'm not saying any of these names is wrong...just that they all blur together in my brain when I try to think about which is which. I suspect it will get worse when Metrics are involved. I guess the question is... is it worth the increase in inherent complexity in these classes to remove the code duplication? How often will we need to touch these classes in the future? |
@anuraaga I went down the road of trying to use interfaces / abstract classes to DRY up the OTLP builders / exporters in the past and at the time concluded that the increased cognitive overhead wasn't worth the code reduction, especially considering how these are likely to stabilize soon. My idea for code reuse was to have a variety of utility functions that the builders / exporters could call. For example, we could have a grpc export function like:
Which would be called in the grpc exporters such as
Or for example take the endpoint validation that is duplicated across all the builders. We could add a utility method like:
And the builders such as
The idea being to DRY up the consequential code without fussing with OOP questions like inheritance. |
@jkwatson Yeah the names were definitely something that came to mind, especially @jack-berg Here the shared code is in a class that is delegated to, not part of the inheritance hierarchy so think it may not have that problem. Notably the only difference with helper methods seems to be whether it's a static method or not - static methods would have the downside of not allowing this approach of switching implementations for okhttp. |
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change | ||
* at any time. | ||
*/ | ||
public final class DefaultGrpcExporter<T extends Marshaler> implements GrpcExporter<T> { |
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.
Open to any naming suggestions. On the bright side, the actual exporters don't need to worry about the two types, they just use GrpcExporter.builder()
.
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.RegisterExtension; | ||
|
||
class OkHttpOnlyExportTest { |
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.
Future work is to remove duplication among the exporter test sets. A lot of the new lines in this PR are this copy-pasted export test, it'll be cool to stop doing this
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 think this is fine. A ton of changes here, but I'm assuming it's almost entirely pure refactoring. Given the size of the change, do we want to hold off until the next release to merge this, or are we confident enough in our tests & integration tests that we can release this right away in 1.7.0?
Personally I'm confident - the only new code for existing users is the calculation of |
this needs a rebase, then |
long timeoutNanos, | ||
boolean compressionEnabled) { | ||
this.type = type; | ||
Meter meter = GlobalMeterProvider.get().get("io.opentelemetry.exporters.otlp-grpc"); |
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.
Do we want to call out in the changelog that the name of these metrics are changing? The instrumentation library will change, and also the metric name will change from io.opentelemetry.exporters.otlp
to io.opentelemetry.exporters.otlp-grpc
.
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.
That's a good call, although this shouldn't change the name of any metrics (unless you have a custom exporter that appends the meter name to the metric name, I guess?). I would be slightly surprised if anyone was actually using these metrics yet, but in case they are, it is definitely reasonable to call it out.
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 renamed the meter names too, instead of having the type in the meter name it's an attribute now. The camelcase before also didn't seem conventional. I suspect calling out in release notes while making the change is good here - the instrumentation library change isn't that important so I could revert it, but given the metric name change anyways, I figured it's good to more easily differentiate metrics from -grpc, -grpc-okhttp, -http
|
||
static <T extends Marshaler> GrpcExporterBuilder<T> exporterBuilder( | ||
String type, | ||
long defaultTimeoutSecs, |
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.
#nit: Default timeout is always the same, no? Could omit this argument.
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.
Yeah I don't think we can lose the reference in the javadoc though for users in the public API like
Could move the constant to a shared place and reference it, but this seemed more straightforward.
String errorMessage = grpcMessage(response); | ||
logger.log( | ||
Level.WARNING, | ||
"Failed to export spans. Server responded with " |
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.
Oops will want to change this log message to include the configured type
.
@@ -100,9 +92,7 @@ public OtlpGrpcLogExporterBuilder setCompression(String compressionMethod) { | |||
checkArgument( | |||
compressionMethod.equals("gzip") || compressionMethod.equals("none"), | |||
"Unsupported compression method. Supported compression methods include: gzip, none."); |
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.
Isn't all the validation in here unnecessary now? Duplicates logic in the delegate.
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.
Same feedback applies to the other builders.
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.
Ah yeah did want to confirm what people think on this. There is a school of thought that validation mentions are more user friendly being as close to the user code as possible, even if it means some duplicated code (prioritize UX over duplication). I follow this school :) If it's ok I'd like to remove from the delegate and keep in the callers, what do you think?
|
||
/** Builder for {@link OtlpGrpcLogExporter}. */ | ||
public final class OtlpGrpcLogExporterBuilder { | ||
|
||
// Visible for testing | ||
static final String GRPC_ENDPOINT_PATH = | ||
"/opentelemetry.proto.collector.logs.v1.LogsService/Export"; |
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.
#nit: These values appear as constants in the Marshaler{Type}ServiceGrpc
classes. Would be good to move them to :exporters:otlp:common
at some point.
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.
Yeah I think I'll try that in a followup
Noticed a couple of minor things, but looks ok to me. Nice to add the okhttp grpc capability and simultaneously help to reduce code repetition 👍. |
Happy to followup on anything, want to link some of these files from a doc :) |
Serializing in gRPC wire format is actually quite trivial (parsing is way harder) - I've used this approach in Zipkin server in the past to allow using gRPC without any dependencies on grpc-java.
Throwing this out there, I used an approach that checks if grpc-java is on the classpath, and if it isn't while okhttp is, switches to the okhttp implementation. This exporter should work fine in general, and avoids ~4MB of dependencies brought in by grpc-okhttp, such as its guava dependencies. There is interest in this for a slim distribution of the javaagent which would clock in at 10MB without any difference in semantics from the full.