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
mdc auto injection integration #630
Conversation
realark
commented
Dec 18, 2018
•
edited
edited
- Add post-match hook to default instrumenter api
- Add internal scope listener api
- slf4j MDC autoinjection integration
@@ -92,7 +92,6 @@ public static ResettableClassFileTransformer installBytebuddyAgent( | |||
.or(nameStartsWith("org.aspectj.")) | |||
.or(nameStartsWith("org.groovy.")) | |||
.or(nameStartsWith("com.p6spy.")) | |||
.or(nameStartsWith("org.slf4j.")) |
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 was intentional. This is actually not doing anything currently because shadow will rename the prefix to datadog.slf4j
, which is already skipped.
1ebc49b
to
b3b0d5e
Compare
protectionDomain); | ||
return true; | ||
} | ||
}) |
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.
Would it make sense to pull this out into an actual named class like we do for MuzzleMatcher above to make this method more readable?
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.
👍
|
||
@Override | ||
protected boolean defaultEnabled() { | ||
final String enableInjection = getPropOrEnv("dd." + Config.LOGS_INJECTION); |
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 Config
getter not available here?
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 is available, but our instrumentation seems to use a parallel path to get the configuration. I tried originally using Config.get()
, but that isn't compatible with our testing.
That can be addressed but it's more of a general instrumenter thing, so I'd rather not fix it in this PR.
@@ -179,6 +197,23 @@ protected ReferenceMatcher getInstrumentationMuzzle() { | |||
/** @return A type matcher used to match the class under transform. */ | |||
public abstract ElementMatcher<? super TypeDescription> typeMatcher(); | |||
|
|||
/** | |||
* A hook invoked after matching has succeeded and before transformers have run. |
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.
Could you please add some sort of a note here that this is being run from matcher context, not from transformation context?
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.
👍
@@ -91,6 +94,8 @@ | |||
@Getter private final Integer jmxFetchRefreshBeansPeriod; | |||
@Getter private final String jmxFetchStatsdHost; | |||
@Getter private final Integer jmxFetchStatsdPort; | |||
// Use (capital) Boolean to prefix generated getter with "get" | |||
@Getter private final Boolean logsInjection; |
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 may want to make this boolean logsInjectionEnabled
to avoid boxed fields.
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.
isLogsInjection
sounds awkward, but I guess I'm okay with it.
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 annotation might allow customizing the method name if you want something different.
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.
logsInjectionEnabled
would lead to isLogsInjectionEnabled
which sounds reasonable to me :)
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 annotation doesn't allow customizing the name in this case unfortunately.
logsInjectionEnabled
sounds good!
JavaModule module, | ||
Class<?> classBeingRedefined, | ||
ProtectionDomain protectionDomain) { | ||
MDCAdvice.mdcClassInitialized(classBeingRedefined); |
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 method is annotated with BB stuff, are we sure it is safe to call is from 'outside' of BB logic?
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 safe to do in this case specifically because the advice only uses bootstrap classes. Also, this bit of code runs underneath the jvm transformer, so any errors can't escape into the application.
putMethod = mdcClass.getMethod("put", String.class, String.class); | ||
} catch (NoSuchMethodException e) { | ||
putMethod = mdcClass.getMethod("put", String.class, Object.class); | ||
} |
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.
Could you please add a comment about why there is this try/catch and why you do not need one for remove.
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.
Maybe include the versions on which the signature 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.
That was actually something I was doing for logback specifically. I ended up only instrumenting the slf4j api, so I'll remove the Object
fallback.
} | ||
|
||
public static class MDCAdvice { | ||
@Advice.OnMethodExit |
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 there be 'suppress' stuff?
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, thanks!
public static class MDCScopeListener implements ScopeListener { | ||
private static final Logger log = org.slf4j.LoggerFactory.getLogger("mdc-scope-listener"); | ||
private static final String traceIdKey = "dd.trace_id"; | ||
private static final String spanIdKey = "dd.span_id"; |
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.
These string seem to be constants, could you please capitalize them. Also you may want to make them visible for tests to avoid repeating them there.
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.
👍
|
||
/** Called just after a scope is closed. */ | ||
void afterScopeClose(); | ||
} |
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 seems to be highly asymmetrical API. I would suggest changing this to:
void afterScopeActivated();
void beforeScopeDeactivated();
And make sure that these are called whenever active scope changes (i.e. beforeScopeDeactivated
is called on a scope being replaced as well).
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 intentionally did that because we only need hooks for when the scope activates (multiple times) and when it closes (just once).
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.
A fuller api would probably include four hooks:
- open (once)
- activated (>= 1)
- deactivated (>= 1)
- closed (once)
I was thinking for now just create the two we need and add the rest later as-needed.
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 fine by me, but I guess in your terms activated
/deactivated
is essentially same things I've proposed with afterScopeActivated
/beforeScopeDeactivated
. I'm slightly concerned that this is declared public and is not in a state we want that to be longer term - at the same time.
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, they are the the same thing. I think your names would be better for a real api; I just meant to point out that the current ScopeListener is intentionally incomplete.
I have concerns about this as well, but it's the least evil option I see until we can replace dd-trace-ot
with a the new api.
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.
Perhaps at least change it to afterScopeClosed
to maintain some consistency.
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.
Works for me. Done
@@ -407,6 +409,52 @@ class ScopeManagerTest extends Specification { | |||
[new AtomicReferenceScope(true), new AtomicReferenceScope(true)] | _ | |||
} | |||
|
|||
def "add scope listener"() { |
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.
Looks like changes to ContinuableScope
remain untested.
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 see ContinuableScope
changes are exercised by Slf4jMDCTest
but it still would be nice to have tests for them 'locally'.
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.
Sure, I think that's easy to add.
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.
Nice work. I like the postMatch
changes. I have some questions about making the ScopeListener public right now.
@@ -86,6 +86,24 @@ public final AgentBuilder instrument(final AgentBuilder parentAgentBuilder) { | |||
"Instrumentation class loader matcher unexpected exception: " | |||
+ getClass().getName())) | |||
.and(new MuzzleMatcher()) | |||
.and( | |||
new AgentBuilder.RawMatcher() { |
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.
Would it be possible/better to only add this if a postMatch is defined? (maybe !(this instanceof Default)
?)
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.
Only Defaults can hook the post-match, so we'd need another way to check if one is defined. I don't think it's worth the extra effort since this only runs when there's an actual match (not very often as far as classloads go)
public static class MDCScopeListener implements ScopeListener { | ||
private static final Logger log = org.slf4j.LoggerFactory.getLogger("mdc-scope-listener"); | ||
private static final String traceIdKey = "dd.trace_id"; | ||
private static final String spanIdKey = "dd.span_id"; |
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.
Are these keys defined in a spec? Do we want to allow users to configure different keys?
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, in the trace-logs
rfc. For now the spec doesn't specify configuring different keys.
} | ||
|
||
public static class MDCScopeListener implements ScopeListener { | ||
private static final Logger log = org.slf4j.LoggerFactory.getLogger("mdc-scope-listener"); |
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 don't want to use @Slf4j
?
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 in the habit of not doing that in instrumentation, but it would work in this case since it's a helper class.
putMethod = mdcClass.getMethod("put", String.class, String.class); | ||
} catch (NoSuchMethodException e) { | ||
putMethod = mdcClass.getMethod("put", String.class, Object.class); | ||
} |
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.
Maybe include the versions on which the signature changed?
@@ -59,6 +59,9 @@ | |||
public static final String DEFAULT_AGENT_HOST = "localhost"; | |||
public static final int DEFAULT_TRACE_AGENT_PORT = 8126; | |||
|
|||
public static final String LOGS_INJECTION = "logs.injection"; |
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 fine, but throwing out some alternatives:
logs.correlation
logs.associate
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 those are fine, but we'll need an rfc change first. We're just following the rfc for configuring a common env variable.
@@ -91,6 +94,8 @@ | |||
@Getter private final Integer jmxFetchRefreshBeansPeriod; | |||
@Getter private final String jmxFetchStatsdHost; | |||
@Getter private final Integer jmxFetchStatsdPort; | |||
// Use (capital) Boolean to prefix generated getter with "get" | |||
@Getter private final Boolean logsInjection; |
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 annotation might allow customizing the method name if you want something different.
package datadog.trace.context; | ||
|
||
/** Hooks for scope activation */ | ||
public interface ScopeListener { |
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.
Are we sure we want this on the public API right now?
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'd rather not, but unfortunately this is the only project available to both dd-trace-ot and instrumentation.
We already mix semi-private APIs in this project (see other classes in context package). It's not great, but it's the lesser of two evils. The alternative would be to expose agent-bootstrap
to dd-trace-ot
, which then brings a lot of stuff into the published dd-trace-ot
artifact.
* <p>May be called multiple times. When a scope is initially created, or after a child scope is | ||
* deactivated. | ||
*/ | ||
void afterScopeActivated(); |
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.
Would it be better to allow the scope/span to be passed into both methods? It might not be necessary here, but I can imagine it might be useful in other cases.
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 like to do that, but dd-trace-api
doesn't depend on OpenTracing. So we'd have create another scope-like interface or overload the existing TraceScope.
b3b0d5e
to
e7d22b0
Compare
e7d22b0
to
60d2ec1
Compare
@mar-kolya new changes pushed. I believe this address all the feedback Can you please re-review? Once the review gets to a good state I'll doe one final rebase for the |
60d2ec1
to
e9f1436
Compare