Skip to content
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

Merged
merged 4 commits into from Dec 20, 2018
Merged

mdc auto injection integration #630

merged 4 commits into from Dec 20, 2018

Conversation

realark
Copy link
Contributor

@realark realark commented Dec 18, 2018

  • Add post-match hook to default instrumenter api
  • Add internal scope listener api
  • slf4j MDC autoinjection integration

@realark realark added this to the 0.21.0 milestone Dec 18, 2018
@@ -92,7 +92,6 @@ public static ResettableClassFileTransformer installBytebuddyAgent(
.or(nameStartsWith("org.aspectj."))
.or(nameStartsWith("org.groovy."))
.or(nameStartsWith("com.p6spy."))
.or(nameStartsWith("org.slf4j."))
Copy link
Contributor Author

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.

@realark realark force-pushed the ark/mdc-auto-injection branch 3 times, most recently from 1ebc49b to b3b0d5e Compare December 19, 2018 01:16
protectionDomain);
return true;
}
})
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

gradle/java.gradle Outdated Show resolved Hide resolved

@Override
protected boolean defaultEnabled() {
final String enableInjection = getPropOrEnv("dd." + Config.LOGS_INJECTION);
Copy link
Contributor

@mar-kolya mar-kolya Dec 19, 2018

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?

Copy link
Contributor Author

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.
Copy link
Contributor

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?

Copy link
Contributor Author

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;
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor

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 :)

Copy link
Contributor Author

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);
Copy link
Contributor

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?

Copy link
Contributor Author

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);
}
Copy link
Contributor

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.

Copy link
Contributor

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?

Copy link
Contributor Author

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
Copy link
Contributor

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?

Copy link
Contributor Author

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";
Copy link
Contributor

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.

Copy link
Contributor Author

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();
}
Copy link
Contributor

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).

Copy link
Contributor Author

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).

Copy link
Contributor Author

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.

Copy link
Contributor

@mar-kolya mar-kolya Dec 19, 2018

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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"() {
Copy link
Contributor

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.

Copy link
Contributor

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'.

Copy link
Contributor Author

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.

Copy link
Contributor

@tylerbenson tylerbenson left a 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() {
Copy link
Contributor

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)?)

Copy link
Contributor Author

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";
Copy link
Contributor

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?

Copy link
Contributor Author

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");
Copy link
Contributor

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?

Copy link
Contributor Author

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);
}
Copy link
Contributor

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";
Copy link
Contributor

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

Copy link
Contributor Author

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;
Copy link
Contributor

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 {
Copy link
Contributor

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?

Copy link
Contributor Author

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();
Copy link
Contributor

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.

Copy link
Contributor Author

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.

@realark
Copy link
Contributor Author

realark commented Dec 19, 2018

@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 fixup: commits.

@realark realark merged commit f6818fa into master Dec 20, 2018
@realark realark deleted the ark/mdc-auto-injection branch December 20, 2018 01:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants