Как перехватить Kotlin Coroutines?

Я пытаюсь инструментировать сопрограммы Kotlin, аналогично тому, что здесь делается с помощью Javaagent. Я не хочу Javaagent.

Первый шаг - перехватить создание, приостановку и возобновление сопрограмм, определенных в DebugProbes. Код для этого следующий:

public class Instrumentor {
    private static final Logger LOG = LoggerFactory.getLogger(Instrumentor.class);

    public static void install() {
        TypeDescription typeDescription = TypePool.Default.ofSystemLoader()
                .describe("kotlin.coroutines.jvm.internal.DebugProbesKt")
                .resolve();
        new ByteBuddy()
                .redefine(typeDescription, ClassFileLocator.ForClassLoader.ofSystemLoader())
                .method(ElementMatchers.named("probeCoroutineCreated").and(ElementMatchers.takesArguments(1)))
                .intercept(MethodDelegation.to(CoroutineCreatedAdvice.class))
                .method(ElementMatchers.named("probeCoroutineResumed").and(ElementMatchers.takesArguments(1)))
                .intercept(MethodDelegation.to(CoroutineResumedAdvice.class))
                .method(ElementMatchers.named("probeCoroutineSuspended").and(ElementMatchers.takesArguments(1)))
                .intercept(MethodDelegation.to(CoroutineSuspendedAdvice.class))
                .make()
                .load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.INJECTION);

        DebugProbes.INSTANCE.install();
    }

    public static void uninstall() {
        DebugProbes.INSTANCE.uninstall();
    }

    public static class CoroutineCreatedAdvice {
        @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
        public static Continuation<Object> exit(@Advice.Return(readOnly = false) Continuation<Object> retVal) {
            LOG.info("Coroutine created: {}", retVal);
           
            return retVal;
        }
    }

    public static class CoroutineResumedAdvice {
        @Advice.OnMethodEnter(suppress = Throwable.class)
        public static void enter(@Advice.Argument(0) final Continuation<Object> continuation) {
            LOG.info("Coroutine resumed: {}", continuation);
        }
    }

    public static class CoroutineSuspendedAdvice {
        @Advice.OnMethodEnter(suppress = Throwable.class)
        public static void enter(@Advice.Argument(0) final Continuation<Object> continuation) {
            LOG.info("Coroutine suspended: {}", continuation);
        }
    }
}

Тест JUnit5 для запуска перехвата:

class CoroutineInstrumentationTest {
    companion object {
        @JvmStatic
        @BeforeAll
        fun beforeAll() {
            Instrumentor.install()
        }

        @JvmStatic
        @AfterAll
        fun afterAll() {
            Instrumentor.uninstall()
        }
    }

    @Test
    fun testInterception() {
        runBlocking {
            println("Test")
        }
    }
}

Однако перехвата не происходит (что подтверждается отсутствием операторов журнала и использованием отладчика). Я новичок в Byte Buddy, поэтому, возможно, я что-то упускаю. Есть идеи?

Kotlin v1.4.10, Kotlin Coroutines v1.3.9, Byte Buddy v1.10.17.

1 ответ

Вы уверены, что класс еще не загружен? Попробуйте установить точку останова в ClassInjector.UsingReflection чтобы увидеть, действительно ли вы проходите через него, или инъекция прервана из-за ранее загруженного класса.

Более чистым решением был бы агент Java. Вы можете использовать byte-buddy-agent для его динамического создания ByteBuddyAgent.install() а затем зарегистрируйте AgentBuilder в теме.

Другие вопросы по тегам