-
Notifications
You must be signed in to change notification settings - Fork 881
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
Port Spring Boot WithSpanAspect to Instrumenter API #3607
Port Spring Boot WithSpanAspect to Instrumenter API #3607
Conversation
d666c04
to
c08de66
Compare
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 the general approach of an "annotations instrumentation API" makes sense since we have several of them. I guess there isn't any real win from the actual Instrumenter
API (or BaseTracer for that matter) here since there aren't any semantic conventions - annotations seem to be a annotation-driven wrapper around the OpenTelemetry API itself, not "real instrumentation" for lack of a better word. @HaloFour Do you think it's helping or hurting to use Instrumenter here as opposed to just sticking with direct usage of the OpenTelemetry API?
...entelemetry/instrumentation/api/annotation/support/MethodSpanAttributesExtractorBuilder.java
Outdated
Show resolved
Hide resolved
I don't think it hurts using I've been working on the Is the intention to eventually deprecate/remove |
99e934f
to
324e177
Compare
protected void onStart(AttributesBuilder attributes, REQUEST request) { | ||
Method method = methodExtractor.extract(request); | ||
AttributeBindings bindings; | ||
if (cache != null) { |
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.
cache
is now always non null
import org.checkerframework.checker.nullness.qual.Nullable; | ||
|
||
/** Extractor of {@link io.opentelemetry.api.common.Attributes} for a traced method. */ | ||
public class MethodSpanAttributesExtractor<REQUEST, RESPONSE> |
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:
public class MethodSpanAttributesExtractor<REQUEST, RESPONSE> | |
public final class MethodSpanAttributesExtractor<REQUEST, RESPONSE> |
@Override | ||
protected @Nullable String[] attributeNamesForParameters( | ||
Method method, Parameter[] parameters) { | ||
return parameterAttributeNamesExtractor.extract(method, parameters); |
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.
parameterAttributeNamesExtractor
seems to be optional in the builder. What if it's not set? Should it be required?
public JoinPointRequest(JoinPoint joinPoint) { | ||
this.joinPoint = joinPoint; | ||
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); | ||
this.method = methodSignature.getMethod(); | ||
this.annotation = this.method.getDeclaredAnnotation(WithSpan.class); | ||
} | ||
|
||
public Method method() { | ||
return method; | ||
} | ||
|
||
public WithSpan annotation() { | ||
return annotation; | ||
} | ||
|
||
public Object[] args() { | ||
return joinPoint.getArgs(); | ||
} |
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: the class itself is package-private?
public JoinPointRequest(JoinPoint joinPoint) { | |
this.joinPoint = joinPoint; | |
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); | |
this.method = methodSignature.getMethod(); | |
this.annotation = this.method.getDeclaredAnnotation(WithSpan.class); | |
} | |
public Method method() { | |
return method; | |
} | |
public WithSpan annotation() { | |
return annotation; | |
} | |
public Object[] args() { | |
return joinPoint.getArgs(); | |
} | |
JoinPointRequest(JoinPoint joinPoint) { | |
this.joinPoint = joinPoint; | |
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); | |
this.method = methodSignature.getMethod(); | |
this.annotation = this.method.getDeclaredAnnotation(WithSpan.class); | |
} | |
Method method() { | |
return method; | |
} | |
WithSpan annotation() { | |
return annotation; | |
} | |
Object[] args() { | |
return joinPoint.getArgs(); | |
} |
Yep, we intend to do that once it's no longer used anywhere. |
Method method = methodExtractor.extract(request); | ||
AttributeBindings bindings; | ||
if (cache != null) { | ||
bindings = cache.computeIfAbsent(method, binder::bind); |
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.
Our weak caches use identity comparison for keys. For this cache to work it would need to get the exact same method instance. For example Class.getDeclaredMethod
returns a different instance each time. Are you sure that MethodExtractor
implementations will actually always return the same instance? Should this be documented? Or use a different caching strategy, for example could use a ClassValue
on declaring class of the method that contains a plain concurrent map. If you believe that the cache should be weak then you could use ClassValue
to hold keys that you derive from method so that different instances of the same method would have the same key.
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.
Oof, I did not know that our cache implementation was limited to identity comparison of weak keys. I see that this behavior is effectively inherited from Caffeine so we don't have an easy way to support value-based equality of weak keys.
I ran some tests locally and it seems like Spring AOP does cache the Method instance on subsequent invocations, but I am a little hesitant to depend on this behavior.
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.
Unfortunately ByteBuddy does not cache the Method instance. When referencing @Advice.Origin Method method
it effectively emits Foo.class.getMethod("name", ...)
on each invocation. I'll look into your suggestions on different cache strategies.
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 added an implementation of Cache<K, V>
that uses ClassValue<T>
under the hood. It's probably overkill to have it implement Cache<K, V>
since I'll only ever use computeIfAbsent
here. The use of ConcurrentHashMap
is probably also overkill but very rudimentary testing seems to indicate that it performs better for this read-mostly scenario even with relatively few entries.
48b6c49
to
6a3397f
Compare
MethodExtractor<REQUEST> methodExtractor; | ||
MethodArgumentsExtractor<REQUEST> methodArgumentsExtractor; | ||
ParameterAttributeNamesExtractor parameterAttributeNamesExtractor; |
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.
Hmm, since all 3 params are required, WDYT about using a static factory method instead of a builder?
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.
Good idea. I was trying to make it fit into the same builder pattern as Instrumenter
but I agree that it's not a good fit.
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.
thx 👍
Port
spring-boot-autoconfigure
projectWithSpanAspect
and supporting annotation types to the newerInstrumenter<REQUEST, RESPONSE>
API.Adds some new classes to
instrumentation-api-annotation-support
to fit with theInstrumenter<REQUEST, RESPONSE>
API. These new classes are shims to the existing types. I plan on following this PR up with similar changes to theopentelemetry-annotations-1.0
project at which time I will refactor the logic from the shimmed classes into the new classes.