-
Notifications
You must be signed in to change notification settings - Fork 232
Remove dependency from jaeger-core to jaeger-thrift #449
Remove dependency from jaeger-core to jaeger-thrift #449
Conversation
Resolves jaegertracing#448 - This PR removes the dependency from jaeger-core to jaeger-thrift by loading the appropriate Sender via either environment variables or based on existing service providers on the classpath, loaded via ServiceLoader. Signed-off-by: Juraci Paixão Kröhling <[email protected]>
Codecov Report
@@ Coverage Diff @@
## master #449 +/- ##
===========================================
+ Coverage 87.55% 88.35% +0.8%
+ Complexity 517 489 -28
===========================================
Files 65 63 -2
Lines 2000 1846 -154
Branches 263 240 -23
===========================================
- Hits 1751 1631 -120
+ Misses 160 137 -23
+ Partials 89 78 -11
Continue to review full report at Codecov.
|
@@ -137,6 +136,11 @@ | |||
*/ | |||
public static final String JAEGER_PROPAGATION = JAEGER_PREFIX + "PROPAGATION"; | |||
|
|||
/** | |||
* The fully qualified class name for the sender factory |
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.
Just wondering if we could select the factory based on a simpler name, which may be maps onto a predefined class name - so the configuration by the user requires less implementation knowledge?
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 would be required only if there are multiple factories in the classpath. Do you think it would be a common use case to justify a map of "common" factories?
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.
Not necessary common, but just thinking of the detail the user would need to know (i.e. the fully qualified class name) to select a transport.
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.
In the usual case, they just need to have the transport on the classpath, there's no need to know the class name. If they end up somehow with two transports, the class name is used to disambiguate.
How about we go without this map for now, and add later if the case is more common than we thought?
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.
Two other suggestions - (1) we treat multiple factories as an error - so if the case comes up to justify having two in the classpath, we can explore it further then, (2) add a type property on the factory, and that is what is specified in the environment variable?
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.
add a type property on the factory, and that is what is specified in the environment variable?
I like this idea, as it's easily extensible.
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 FQN is better than just a class name.
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.
add a type property on the factory,
is the type known when calling the factory?
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 FQN is better than just a class name.
This is also available: if the env var contains a class name, we try to load it. Otherwise, we try to get a single factory from the service loader. If there are multiple, we try to match the type with the env var. If none matches, NoopSender is returned.
return sender; | ||
} | ||
|
||
return new NoopSender(); |
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.
Should also log a warning that no sender has been found.
jaeger-thrift/build.gradle
Outdated
@@ -43,6 +48,11 @@ shadowJar { | |||
classifier 'thrift92' | |||
} | |||
|
|||
shadowJar { |
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.
Don't we just want the single shaded jar, with the dependencies relocated?
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.
Should I just remove the one from jaeger-core and have then jaeger-thrift-okhttp381
?
http://central.maven.org/maven2/io/jaegertracing/jaeger-core/0.28.0/
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.
Sorry, I think I get a better understanding now. Did you mean to have the shadowed okhttp on the main artifact as well? Or did you mean to have the optional okhttp shadow artifact for thrift, instead of for jaeger-core?
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 just changed this PR to include both shadowed thrift and okhttp on the same "shadow" artifact
|
||
assertTrue(Configuration.SenderConfiguration.fromEnv().getSender() instanceof HttpSender); | ||
} | ||
|
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 there be some tests for getting the UdpSender?
jaeger-zipkin/build.gradle
Outdated
@@ -8,6 +8,7 @@ dependencies { | |||
compile group: 'io.zipkin.reporter2', name: 'zipkin-reporter', version: '2.6.0' | |||
|
|||
compile project(path: ':jaeger-core', configuration: 'shadow') |
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.
Is a shadow configuration still required here? It might need to be on the jaeger-thrift dependency?
Signed-off-by: Juraci Paixão Kröhling <[email protected]>
b21feed
to
043a4b0
Compare
@@ -34,33 +32,32 @@ | |||
private Semaphore semaphore = new Semaphore(Integer.MAX_VALUE); | |||
|
|||
public InMemorySender() { |
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 used to have inMemoryReporter. What's the point of InMemorySender?
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.
The InMemorySender
was in the reporters
package. I moved it to this package, as it seems more appropriate.
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.
ok, just for tests - nvm
} | ||
|
||
public List<Span> getAppended() { | ||
return new ArrayList<Span>(appended); | ||
return new ArrayList<>(appended); |
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.
what are the threading expectations?
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.
Given that it's used only for tests, I would say that we don't guarantee anything. Concurrent modification will cause a ConcurrentModificationException
, for instance.
jaeger-thrift/build.gradle
Outdated
@@ -6,9 +6,14 @@ apply plugin: 'com.github.johnrengelman.shadow' | |||
description = 'Generated thrift code' |
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 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 description wasn't changed in this PR. Would you like this description changed?
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.
it's no longer accurate since you're extending the purpose and footprint of this module.
* @param senderConfiguration the sender configuration | ||
* @return an appropriate sender based on the configuration, or {@link NoopSender}. | ||
*/ | ||
Sender getSender(Configuration.SenderConfiguration senderConfiguration); |
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.
passing the configuration is kind of an abstraction leak in this case - the configuration must know about the needs of every possible sender factory.
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.
Agree. This would require a bigger refactor than I'd like to focus on at this point. For consistency's sake, other configuration objects would also need such a change :/
Signed-off-by: Juraci Paixão Kröhling <[email protected]>
Signed-off-by: Juraci Paixão Kröhling <[email protected]>
/** | ||
* Builds and/or selects the appropriate sender based on the given {@link Configuration.SenderConfiguration} | ||
* @param senderConfiguration the sender configuration | ||
* @return an appropriate sender based on the configuration, or {@link NoopSender}. |
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.
The NoopSender is an implementation detail of SenderResolver
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.
Not really -- I expect concrete SenderFactory
implementations to never return null. The contract is: either a real Sender
, or NoopSender
.
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.
What can't we return null? It's invoked only in SenderFactory
which takes care about null/noop.
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 is a contract between the implementation and the consumer, meaning that this documentation is a reference to both sides. By stating that it might return NoopSender, we are ruling out a null object, which makes it far easier for consuming code to not break (no null checks, no null pointer exceptions).
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 still think the Noop here is just redundant. This API does not need it and it's just creating a dependency on specific sender impl
|
||
/** | ||
* Represents a class that knows how to select and build the appropriate {@link Sender} based on the given | ||
* {@link Configuration.SenderConfiguration} |
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 expect some comments that this class is resolved via service loader. Also some explanation about meta-inf/services
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.
The resolver loads this via the service loader, but there's nothing preventing a consumer from calling a concrete implementation directly. Like: SenderFactory f = new InMemorySenderFactory()
import lombok.extern.slf4j.Slf4j; | ||
|
||
/** | ||
* Provides a way to resolve an appropriate {@link Sender} |
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 am missing some comments about how the sender is resolved. Is it using service loader or what approach? What should I do to provide my own sender?
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 is implementation-specific. The ThriftSender
, for instance, builds an UdpSender
or HttpSender
based on the contents of the SenderConfiguration
. More information is provided at the method-level javadoc.
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.
Then how consumers of this API know how to provide their own sender?
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.
Consumer of the API != Service Provider.
The Service Provider will know how to instantiate a new Sender and will return it as a result of this method call (see jaeger-thrift).
The consumer of the API (target application) will, at most, set the appropriate configuration options on SenderConfiguration
and will get an appropriate sender in return.
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 have my own sender impl. How do I use jaeger-core to load it from classpath? Where can I found more info about it? I just want to use Configuration.fromEnv()
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.
You implement the SenderFactory
, which returns your own Sender
on the resolve()
method. See the unit tests and the jaeger-thrift for an example on how to do that.
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 implement SenderFactory
in my app, add jaeger-core
. then I use Configuration.fromEnv()
without specifying any env var. Would be my sender factory used?
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.
Yes. The only thing missing in your example is the META-INF/services/io.jaegertracing.senders.SenderFactory .
@@ -137,6 +136,11 @@ | |||
*/ | |||
public static final String JAEGER_PROPAGATION = JAEGER_PREFIX + "PROPAGATION"; | |||
|
|||
/** | |||
* The fully qualified class name for the sender factory |
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 FQN is better than just a class name.
@@ -137,6 +136,11 @@ | |||
*/ | |||
public static final String JAEGER_PROPAGATION = JAEGER_PREFIX + "PROPAGATION"; | |||
|
|||
/** | |||
* The fully qualified class name for the sender factory |
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.
add a type property on the factory,
is the type known when calling the factory?
|
||
@Override | ||
public int flush() { | ||
return 1; |
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 0 is more appropriate - also for close.
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.
Thought so too. But not for the append(Span)
, right?
Signed-off-by: Juraci Paixão Kröhling <[email protected]>
Sender getSender(Configuration.SenderConfiguration senderConfiguration); | ||
|
||
/** | ||
* The Factory's name. Can specified via {@link Configuration#JAEGER_SENDER_FACTORY} to disambiguate |
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: "Can be specified"
* @return the resolved Sender, or NoopSender | ||
*/ | ||
public static Sender resolve(Configuration.SenderConfiguration senderConfiguration) { | ||
String senderFactoryClass = System.getProperty(Configuration.JAEGER_SENDER_FACTORY); |
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: senderFactoryClass
-> senderFactoryType
?
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 this still using the old mechanism - where the env var specifies a fq class name?
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.
It can be both. At this point in the code, we assume it's a class (we try to use it on a Class.forName
later on).
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 this still using the old mechanism - where the env var specifies a fq class name?
Yes. I thought the type is only used to disambiguate in case there are multiple factories available? The previous resolution was:
- FQCN
- Service Loader (first service available)
- NoopSender
The current is:
- FQCN
- Service Loader (when only one service is available)
- Service Loader + Type (when multiple services are available)
- NoopSender
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.
Type in SenderFactory
seems redundant as you can still specify the wanted impl as FQCN or wouldn't it work?
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.
It would, and it's how it was being done before. Looks at this thread: #449 (comment)
@@ -40,7 +43,8 @@ checkstyleTest.enabled = false | |||
shadowJar { | |||
baseName = 'jaeger-thrift' | |||
relocate 'org.apache.thrift', 'org.shadow.apache.thrift92' | |||
classifier 'thrift92' | |||
relocate 'okhttp', 'jaeger.okhttp' |
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: we should maybe have a consistent relocate pattern - so possibly prefix jaeger as here, so update the thrift one to jaeger.org.apache.thrift
(without the 92)?
btw - might also be worth looking at the other dependencies included in this shaded jar, as they may be should be relocated aswell to avoid any potential clashes - or possibly this should be a separate PR.
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.
or possibly this should be a separate PR
That would be my preference.
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 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.
LGTM - subject to resolving the factory env var issue. Couple of nits to look at.
Signed-off-by: Juraci Paixão Kröhling <[email protected]>
/** | ||
* Resolves a sender by passing the given {@link Configuration.SenderConfiguration} down to the | ||
* {@link SenderFactory}. The factory is loaded either based on the value from the environment variable | ||
* JAEGER_SENDER_FACTORY or, in its absence or failure to deliver a {@link Sender}, via the {@link ServiceLoader}. |
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.
JAEGER_SENDER_FACTORY make it a javadoc link.
* @return the resolved Sender, or NoopSender | ||
*/ | ||
public static Sender resolve(Configuration.SenderConfiguration senderConfiguration) { | ||
String senderFactoryClass = System.getProperty(Configuration.JAEGER_SENDER_FACTORY); |
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.
Type in SenderFactory
seems redundant as you can still specify the wanted impl as FQCN or wouldn't it work?
import lombok.extern.slf4j.Slf4j; | ||
|
||
/** | ||
* Provides a way to resolve an appropriate {@link Sender} |
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 implement SenderFactory
in my app, add jaeger-core
. then I use Configuration.fromEnv()
without specifying any env var. Would be my sender factory used?
Signed-off-by: Juraci Paixão Kröhling <[email protected]>
Signed-off-by: Juraci Paixão Kröhling <[email protected]>
9c8cec0
to
57f53ec
Compare
Signed-off-by: Juraci Paixão Kröhling <[email protected]>
Resolves #448 - This PR removes the dependency from jaeger-core to jaeger-thrift by loading
the appropriate Sender via either environment variables or based on existing service providers
on the classpath, loaded via ServiceLoader.
Signed-off-by: Juraci Paixão Kröhling [email protected]