22
33import java .util .Arrays ;
44import java .util .Comparator ;
5+ import java .util .EnumSet ;
56import java .util .HashSet ;
67import java .util .List ;
78import java .util .Map ;
@@ -107,25 +108,72 @@ public HookSite prepare(CtBehavior targetBehavior, Map<String, Object> hookConte
107108 return new HookSite (this , behaviorOrdinal , binding );
108109 }
109110
110- public static void apply (CtBehavior targetBehavior , List <HookSite > hookSites ) {
111+ public enum ApplicationAction {
112+ MARKED ,
113+ INSTRUMENTED
114+ }
115+
116+ // We only log the first exception to avoid flooding the logs at the debug level.
117+ // Note this variable is not thread safe, but this is okay; the worst that can happen is
118+ // that we log more than one exception in a multi-threaded scenario.
119+ private static boolean exceptionLogged = false ;
120+
121+ public static Set <ApplicationAction > apply (CtBehavior targetBehavior , List <HookSite > hookSites , boolean traceClass ) {
122+ Set <ApplicationAction > actions = EnumSet .noneOf (ApplicationAction .class );
111123 MethodInfo methodInfo = targetBehavior .getMethodInfo ();
112124 AnnotationsAttribute attr =
113125 (AnnotationsAttribute )methodInfo .getAttribute (AnnotationsAttribute .visibleTag );
114126
115- // If the behavior is marked as an app method, update the annotation with
116- // the behavior ordinals so the bytebuddy transformer can instrument it.
117- if (attr .getAnnotation (AppMapAppMethod .class .getName ()) != null ) {
118- setBehaviorOrdinals (targetBehavior , hookSites );
119- }
127+ if (attr != null ) {
128+ // If the behavior is marked as an app method, update the annotation with
129+ // the behavior ordinals so the bytebuddy transformer can instrument it.
130+ if (attr .getAnnotation (AppMapAppMethod .class .getName ()) != null ) {
131+ setBehaviorOrdinals (targetBehavior , hookSites );
132+ actions .add (ApplicationAction .MARKED );
133+ if (traceClass ) {
134+ logger .debug ("tracing {}.{}{}" ,
135+ targetBehavior .getDeclaringClass ().getName (),
136+ targetBehavior .getName (),
137+ targetBehavior .getMethodInfo ().getDescriptor ());
138+ }
139+ }
120140
121- // If it's (also) marked as an agent method, it needs to be instrumented
122- // by javassist.
123- if (attr .getAnnotation (AppMapAgentMethod .class .getName ()) != null ) {
124- instrument (targetBehavior , hookSites );
141+ // If it's (also) marked as an agent method, it needs to be instrumented
142+ // by javassist.
143+ if (attr .getAnnotation (AppMapAgentMethod .class .getName ()) != null ) {
144+ try {
145+ instrument (targetBehavior , hookSites );
146+ actions .add (ApplicationAction .INSTRUMENTED );
147+ if (traceClass ) {
148+ String hooks = hookSites .stream ()
149+ .map (h -> h .getHook ().toString ())
150+ .collect (Collectors .joining (", " ));
151+ logger .debug ("{}.{}{} instrumented with hooks: {}" ,
152+ targetBehavior .getDeclaringClass ().getName (),
153+ targetBehavior .getName (),
154+ targetBehavior .getMethodInfo ().getDescriptor (),
155+ hooks );
156+ }
157+ } catch (CannotCompileException | NotFoundException e ) {
158+ String msg = String .format ("failed to instrument %s.%s: %s" ,
159+ targetBehavior .getDeclaringClass ().getName (), targetBehavior .getName (), e .getMessage ());
160+ if (!exceptionLogged ) {
161+ logger .debug (e , msg );
162+ exceptionLogged = true ;
163+ } else {
164+ // Log at trace level after the first one to avoid flooding the debug logs
165+ logger .trace (e , msg );
166+ logger .debug (msg );
167+ }
168+ }
169+ }
125170 }
171+
172+ return actions ;
126173 }
127174
128- public static void instrument (CtBehavior targetBehavior , List <HookSite > hookSites ) {
175+ public static void instrument (CtBehavior targetBehavior , List <HookSite > hookSites )
176+ throws CannotCompileException , NotFoundException {
129177 final CtClass returnType = getReturnType (targetBehavior );
130178 final Boolean returnsVoid = (returnType == CtClass .voidType );
131179
@@ -150,44 +198,36 @@ public static void instrument(CtBehavior targetBehavior, List<HookSite> hookSite
150198
151199 }
152200
153- try {
154- String beforeSrcBlock = beforeSrcBlock (uniqueLocks .toString (),
155- invocations [MethodEvent .METHOD_INVOCATION .getIndex ()]);
156- logger .trace ("{}: beforeSrcBlock:\n {}" , targetBehavior ::getName , beforeSrcBlock ::toString );
157- targetBehavior .insertBefore (
158- beforeSrcBlock );
159-
160- String afterSrcBlock = afterSrcBlock (invocations [MethodEvent .METHOD_RETURN .getIndex ()]);
161- logger .trace ("{}: afterSrcBlock:\n {}" , targetBehavior ::getName , afterSrcBlock ::toString );
162-
163- targetBehavior .insertAfter (
164- afterSrcBlock );
165-
166- ClassPool cp = AppMapClassPool .get ();
167- String exitEarlyCatchSrc = "{com.appland.appmap.process.ThreadLock.current().exit();return;}" ;
168- if (returnsVoid ) {
169- targetBehavior .addCatch (exitEarlyCatchSrc ,
170- cp .get ("com.appland.appmap.process.ExitEarly" ));
171- } else if (!returnType .isPrimitive ()) {
172- exitEarlyCatchSrc = "{com.appland.appmap.process.ThreadLock.current().exit();return("
173- + returnType .getName () + ")$e.getReturnValue();}" ;
174- targetBehavior
175- .addCatch (exitEarlyCatchSrc , cp .get ("com.appland.appmap.process.ExitEarly" ));
176- }
177- logger .trace ("{}: catch1Src:\n {}" , targetBehavior ::getName , exitEarlyCatchSrc ::toString );
178-
179- String catchSrcBlock = catchSrcBlock (invocations [MethodEvent .METHOD_EXCEPTION .getIndex ()]);
180- targetBehavior .addCatch (
181- catchSrcBlock ,
182- cp .get ("java.lang.Throwable" ));
183- logger .trace ("{}: catchSrcBlock:\n {}" , targetBehavior ::getName , catchSrcBlock ::toString );
184-
185- } catch (CannotCompileException e ) {
186- logger .debug (e , "failed to compile {}.{}" , targetBehavior .getDeclaringClass ().getName (),
187- targetBehavior .getName ());
188- } catch (NotFoundException e ) {
189- logger .debug (e );
201+ String beforeSrcBlock = beforeSrcBlock (uniqueLocks .toString (),
202+ invocations [MethodEvent .METHOD_INVOCATION .getIndex ()]);
203+ logger .trace ("{}: beforeSrcBlock:\n {}" , targetBehavior ::getName , beforeSrcBlock ::toString );
204+ targetBehavior .insertBefore (
205+ beforeSrcBlock );
206+
207+ String afterSrcBlock = afterSrcBlock (invocations [MethodEvent .METHOD_RETURN .getIndex ()]);
208+ logger .trace ("{}: afterSrcBlock:\n {}" , targetBehavior ::getName , afterSrcBlock ::toString );
209+
210+ targetBehavior .insertAfter (
211+ afterSrcBlock );
212+
213+ ClassPool cp = AppMapClassPool .get ();
214+ String exitEarlyCatchSrc = "{com.appland.appmap.process.ThreadLock.current().exit();return;}" ;
215+ if (returnsVoid ) {
216+ targetBehavior .addCatch (exitEarlyCatchSrc ,
217+ cp .get ("com.appland.appmap.process.ExitEarly" ));
218+ } else if (!returnType .isPrimitive ()) {
219+ exitEarlyCatchSrc = "{com.appland.appmap.process.ThreadLock.current().exit();return("
220+ + returnType .getName () + ")$e.getReturnValue();}" ;
221+ targetBehavior
222+ .addCatch (exitEarlyCatchSrc , cp .get ("com.appland.appmap.process.ExitEarly" ));
190223 }
224+ logger .trace ("{}: catch1Src:\n {}" , targetBehavior ::getName , exitEarlyCatchSrc ::toString );
225+
226+ String catchSrcBlock = catchSrcBlock (invocations [MethodEvent .METHOD_EXCEPTION .getIndex ()]);
227+ targetBehavior .addCatch (
228+ catchSrcBlock ,
229+ cp .get ("java.lang.Throwable" ));
230+ logger .trace ("{}: catchSrcBlock:\n {}" , targetBehavior ::getName , catchSrcBlock ::toString );
191231 }
192232
193233 private static void setBehaviorOrdinals (CtBehavior behavior ,
0 commit comments