AgentInstaller.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. package datadog.trace.agent.tooling;
  2. import static datadog.trace.agent.tooling.ClassLoaderMatcher.skipClassLoader;
  3. import static net.bytebuddy.matcher.ElementMatchers.any;
  4. import static net.bytebuddy.matcher.ElementMatchers.nameContains;
  5. import static net.bytebuddy.matcher.ElementMatchers.nameMatches;
  6. import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
  7. import static net.bytebuddy.matcher.ElementMatchers.named;
  8. import static net.bytebuddy.matcher.ElementMatchers.none;
  9. import static net.bytebuddy.matcher.ElementMatchers.not;
  10. import datadog.trace.api.Config;
  11. import datadog.trace.bootstrap.WeakMap;
  12. import java.lang.instrument.Instrumentation;
  13. import java.util.Collections;
  14. import java.util.List;
  15. import java.util.Map;
  16. import java.util.ServiceLoader;
  17. import java.util.concurrent.ConcurrentHashMap;
  18. import lombok.extern.slf4j.Slf4j;
  19. import net.bytebuddy.agent.builder.AgentBuilder;
  20. import net.bytebuddy.agent.builder.ResettableClassFileTransformer;
  21. import net.bytebuddy.description.type.TypeDescription;
  22. import net.bytebuddy.dynamic.DynamicType;
  23. import net.bytebuddy.matcher.ElementMatcher;
  24. import net.bytebuddy.matcher.ElementMatchers;
  25. import net.bytebuddy.utility.JavaModule;
  26. @Slf4j
  27. public class AgentInstaller {
  28. private static final Map<String, Runnable> classLoadCallbacks = new ConcurrentHashMap<>();
  29. static {
  30. // WeakMap is used by other classes below, so we need to register the provider first.
  31. registerWeakMapProvider();
  32. }
  33. public static final DDLocationStrategy LOCATION_STRATEGY = new DDLocationStrategy();
  34. public static final AgentBuilder.PoolStrategy POOL_STRATEGY = new DDCachingPoolStrategy();
  35. private static volatile Instrumentation INSTRUMENTATION;
  36. public static Instrumentation getInstrumentation() {
  37. return INSTRUMENTATION;
  38. }
  39. public static void installBytebuddyAgent(final Instrumentation inst) {
  40. if (Config.get().isTraceEnabled()) {
  41. installBytebuddyAgent(inst, new AgentBuilder.Listener[0]);
  42. } else {
  43. log.debug("Tracing is disabled, not installing instrumentations.");
  44. }
  45. }
  46. /**
  47. * Install the core bytebuddy agent along with all implementations of {@link Instrumenter}.
  48. *
  49. * @param inst Java Instrumentation used to install bytebuddy
  50. * @return the agent's class transformer
  51. */
  52. public static ResettableClassFileTransformer installBytebuddyAgent(
  53. final Instrumentation inst, final AgentBuilder.Listener... listeners) {
  54. INSTRUMENTATION = inst;
  55. AgentBuilder agentBuilder =
  56. new AgentBuilder.Default()
  57. .disableClassFormatChanges()
  58. .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
  59. .with(new RedefinitionLoggingListener())
  60. .with(AgentBuilder.DescriptionStrategy.Default.POOL_ONLY)
  61. .with(POOL_STRATEGY)
  62. .with(new TransformLoggingListener())
  63. .with(new ClassLoadListener())
  64. .with(LOCATION_STRATEGY)
  65. // FIXME: we cannot enable it yet due to BB/JVM bug, see
  66. // https://github.com/raphw/byte-buddy/issues/558
  67. // .with(AgentBuilder.LambdaInstrumentationStrategy.ENABLED)
  68. .ignore(any(), skipClassLoader())
  69. // Unlikely to ever need to instrument an annotation:
  70. .or(ElementMatchers.<TypeDescription>isAnnotation())
  71. // Unlikely to ever need to instrument an enum:
  72. .or(ElementMatchers.<TypeDescription>isEnum())
  73. .or(
  74. nameStartsWith("datadog.trace.")
  75. // FIXME: We should remove this once
  76. // https://github.com/raphw/byte-buddy/issues/558 is fixed
  77. .and(
  78. not(
  79. named(
  80. "datadog.trace.bootstrap.instrumentation.java.concurrent.RunnableWrapper")
  81. .or(
  82. named(
  83. "datadog.trace.bootstrap.instrumentation.java.concurrent.CallableWrapper")))))
  84. .or(nameStartsWith("datadog.opentracing."))
  85. .or(nameStartsWith("datadog.slf4j."))
  86. .or(nameStartsWith("net.bytebuddy."))
  87. .or(
  88. nameStartsWith("java.")
  89. .and(
  90. not(
  91. named("java.net.URL")
  92. .or(named("java.net.HttpURLConnection"))
  93. .or(nameStartsWith("java.util.concurrent."))
  94. .or(
  95. nameStartsWith("java.util.logging.")
  96. // Concurrent instrumentation modifies the strucutre of
  97. // Cleaner class incompaibly with java9+ modules.
  98. // Working around until a long-term fix for modules can be
  99. // put in place.
  100. .and(not(named("java.util.logging.LogManager$Cleaner")))))))
  101. .or(nameStartsWith("com.sun.").and(not(nameStartsWith("com.sun.messaging."))))
  102. .or(
  103. nameStartsWith("sun.")
  104. .and(
  105. not(
  106. nameStartsWith("sun.net.www.protocol.")
  107. .or(named("sun.net.www.http.HttpClient")))))
  108. .or(nameStartsWith("jdk."))
  109. .or(nameStartsWith("org.aspectj."))
  110. .or(nameStartsWith("org.groovy."))
  111. .or(nameStartsWith("org.codehaus.groovy.macro."))
  112. .or(nameStartsWith("com.intellij.rt.debugger."))
  113. .or(nameStartsWith("com.p6spy."))
  114. .or(nameStartsWith("com.newrelic."))
  115. .or(nameStartsWith("com.dynatrace."))
  116. .or(nameStartsWith("com.jloadtrace."))
  117. .or(nameStartsWith("com.appdynamics."))
  118. .or(nameStartsWith("com.singularity."))
  119. .or(nameStartsWith("com.jinspired."))
  120. .or(nameStartsWith("org.jinspired."))
  121. .or(nameStartsWith("org.apache.log4j."))
  122. .or(nameStartsWith("org.slf4j.").and(not(named("org.slf4j.MDC"))))
  123. .or(nameContains("$JaxbAccessor"))
  124. .or(nameContains("CGLIB$$"))
  125. .or(nameContains("javassist"))
  126. .or(nameContains(".asm."))
  127. .or(nameMatches("com\\.mchange\\.v2\\.c3p0\\..*Proxy"))
  128. .or(matchesConfiguredExcludes());
  129. for (final AgentBuilder.Listener listener : listeners) {
  130. agentBuilder = agentBuilder.with(listener);
  131. }
  132. int numInstrumenters = 0;
  133. for (final Instrumenter instrumenter : ServiceLoader.load(Instrumenter.class)) {
  134. log.debug("Loading instrumentation {}", instrumenter.getClass().getName());
  135. agentBuilder = instrumenter.instrument(agentBuilder);
  136. numInstrumenters++;
  137. }
  138. log.debug("Installed {} instrumenter(s)", numInstrumenters);
  139. return agentBuilder.installOn(inst);
  140. }
  141. private static ElementMatcher.Junction<Object> matchesConfiguredExcludes() {
  142. final List<String> excludedClasses = Config.get().getExcludedClasses();
  143. ElementMatcher.Junction matcher = none();
  144. for (String excludedClass : excludedClasses) {
  145. excludedClass = excludedClass.trim();
  146. if (excludedClass.endsWith("*")) {
  147. // remove the trailing *
  148. final String prefix = excludedClass.substring(0, excludedClass.length() - 1);
  149. matcher = matcher.or(nameStartsWith(prefix));
  150. } else {
  151. matcher = matcher.or(named(excludedClass));
  152. }
  153. }
  154. return matcher;
  155. }
  156. private static void registerWeakMapProvider() {
  157. if (!WeakMap.Provider.isProviderRegistered()) {
  158. WeakMap.Provider.registerIfAbsent(new WeakMapSuppliers.WeakConcurrent());
  159. }
  160. // WeakMap.Provider.registerIfAbsent(new WeakMapSuppliers.WeakConcurrent.Inline());
  161. // WeakMap.Provider.registerIfAbsent(new WeakMapSuppliers.Guava());
  162. }
  163. @Slf4j
  164. static class RedefinitionLoggingListener implements AgentBuilder.RedefinitionStrategy.Listener {
  165. @Override
  166. public void onBatch(final int index, final List<Class<?>> batch, final List<Class<?>> types) {}
  167. @Override
  168. public Iterable<? extends List<Class<?>>> onError(
  169. final int index,
  170. final List<Class<?>> batch,
  171. final Throwable throwable,
  172. final List<Class<?>> types) {
  173. if (log.isDebugEnabled()) {
  174. log.debug(
  175. "Exception while retransforming " + batch.size() + " classes: " + batch, throwable);
  176. }
  177. return Collections.emptyList();
  178. }
  179. @Override
  180. public void onComplete(
  181. final int amount,
  182. final List<Class<?>> types,
  183. final Map<List<Class<?>>, Throwable> failures) {}
  184. }
  185. @Slf4j
  186. static class TransformLoggingListener implements AgentBuilder.Listener {
  187. @Override
  188. public void onError(
  189. final String typeName,
  190. final ClassLoader classLoader,
  191. final JavaModule module,
  192. final boolean loaded,
  193. final Throwable throwable) {
  194. log.debug(
  195. "Failed to handle {} for transformation on classloader {}: {}",
  196. typeName,
  197. classLoader,
  198. throwable.getMessage());
  199. }
  200. @Override
  201. public void onTransformation(
  202. final TypeDescription typeDescription,
  203. final ClassLoader classLoader,
  204. final JavaModule module,
  205. final boolean loaded,
  206. final DynamicType dynamicType) {
  207. log.debug("Transformed {} -- {}", typeDescription.getName(), classLoader);
  208. }
  209. @Override
  210. public void onIgnored(
  211. final TypeDescription typeDescription,
  212. final ClassLoader classLoader,
  213. final JavaModule module,
  214. final boolean loaded) {
  215. // log.debug("onIgnored {}", typeDescription.getName());
  216. }
  217. @Override
  218. public void onComplete(
  219. final String typeName,
  220. final ClassLoader classLoader,
  221. final JavaModule module,
  222. final boolean loaded) {
  223. // log.debug("onComplete {}", typeName);
  224. }
  225. @Override
  226. public void onDiscovery(
  227. final String typeName,
  228. final ClassLoader classLoader,
  229. final JavaModule module,
  230. final boolean loaded) {
  231. // log.debug("onDiscovery {}", typeName);
  232. }
  233. }
  234. /**
  235. * Register a callback to run when a class is loading.
  236. *
  237. * <p>Caveats: 1: This callback will be invoked by a jvm class transformer. 2: Classes filtered
  238. * out by {@link AgentInstaller}'s skip list will not be matched.
  239. *
  240. * @param className name of the class to match against
  241. * @param classLoadCallback runnable to invoke when class name matches
  242. */
  243. public static void registerClassLoadCallback(
  244. final String className, final Runnable classLoadCallback) {
  245. classLoadCallbacks.put(className, classLoadCallback);
  246. }
  247. private static class ClassLoadListener implements AgentBuilder.Listener {
  248. @Override
  249. public void onDiscovery(
  250. final String typeName,
  251. final ClassLoader classLoader,
  252. final JavaModule javaModule,
  253. final boolean b) {
  254. for (final Map.Entry<String, Runnable> entry : classLoadCallbacks.entrySet()) {
  255. if (entry.getKey().equals(typeName)) {
  256. entry.getValue().run();
  257. }
  258. }
  259. }
  260. @Override
  261. public void onTransformation(
  262. final TypeDescription typeDescription,
  263. final ClassLoader classLoader,
  264. final JavaModule javaModule,
  265. final boolean b,
  266. final DynamicType dynamicType) {}
  267. @Override
  268. public void onIgnored(
  269. final TypeDescription typeDescription,
  270. final ClassLoader classLoader,
  271. final JavaModule javaModule,
  272. final boolean b) {}
  273. @Override
  274. public void onError(
  275. final String s,
  276. final ClassLoader classLoader,
  277. final JavaModule javaModule,
  278. final boolean b,
  279. final Throwable throwable) {}
  280. @Override
  281. public void onComplete(
  282. final String s,
  283. final ClassLoader classLoader,
  284. final JavaModule javaModule,
  285. final boolean b) {}
  286. }
  287. private AgentInstaller() {}
  288. }