Simple, powerful method tracing for JVM and Android(JVM Tests)
See exactly what your code is doing with clean, visual method traces. Perfect for debugging, understanding complex codebases, and visualizing execution flow.
Just wrap your code and see what happens:
codePathTrace {
val calculator = Calculator()
calculator.complexCalculation(5, 3)
}Output:
→ Calculator.<init>()
← Calculator.<init>
→ Calculator.complexCalculation(5, 3)
→ Calculator.add(5, 3)
← Calculator.add = 8
→ Calculator.multiply(8, 2)
← Calculator.multiply = 16
→ Calculator.add(16, 12)
← Calculator.add = 28
← Calculator.complexCalculation = 28
Ever wondered "which view actually handled my touch event?" CodePathTracer shows you:
@get:Rule
val traceRule = CodePathTracer.Builder()
.filter { event -> event.methodName.contains("TouchEvent") }
.asJUnitRule()Output reveals the touch event flow:
→ PhoneWindow.superDispatchTouchEvent(MotionEvent)
→ ViewGroup.onInterceptTouchEvent(MotionEvent)
← ViewGroup.onInterceptTouchEvent = false
→ TextView.onTouchEvent(MotionEvent)
← TextView.onTouchEvent = true ✅
← PhoneWindow.superDispatchTouchEvent = true
Result: TextView handled the touch! Mystery solved. 🎯
We can even understand why recomposition occurs.
Traditional debugging tools can be challenging for AI developers and complex scenarios:
- AI limitations with debuggers - AI assistants struggle to use breakpoints and step-through debugging effectively
- Coverage tools miss the why - Code coverage shows what was executed but not how the execution flowed
- Complex debugging scenarios - From Android touch event handling to deep method chains and callbacks, understanding execution flow is notoriously difficult
Code Path Tracer solves these problems by providing visual execution traces that show exactly how your code flows, making debugging accessible to both humans and AI tools.
Add to your build.gradle dependencies:
dependencies {
testImplementation("io.github.takahirom.codepathtracer:code-path-tracer:[latest version]")
}- 🎯 Zero-config tracing - Add implementation and call one method
- 🎨 Beautiful visual output - Clean arrows show method entry/exit with depth indentation
- 🔧 Flexible filtering - Trace only what you care about
Show preceding method calls even when they don't match your filter:
.filter { event -> event.methodName == "inner" }
.beforeContextSize(1) // Show 1 preceding event for contextOutput:
→ SimpleNestedHierarchy.middle() // Context (doesn't match filter)
→ SimpleNestedHierarchy.inner() // Matches filter
← SimpleNestedHierarchy.inner
← SimpleNestedHierarchy.middle
Useful for debugging: See what led to specific method calls, track where null values originated, or understand deep call chains.
Want custom formatting? Configure it easily:
@get:Rule
val methodTraceRule = CodePathTracer.Builder()
.filter { event -> event.className.contains("Calculator") }
.formatter { event ->
when (event) {
is TraceEvent.Enter -> "➤ ${event.shortClassName}.${event.methodName}(${event.args.size})"
is TraceEvent.Exit -> "⬅ ${event.shortClassName}.${event.methodName} = ${event.returnValue}"
}
}
.asJUnitRule()
@Test
fun testCalculator() {
calculator.complexCalculation(5, 3)
}Output:
➤ SampleCalculator.complexCalculation(2)
➤ SampleCalculator.add(2)
⬅ SampleCalculator.add = 8
➤ SampleCalculator.multiply(2)
⬅ SampleCalculator.multiply = 16
➤ SampleCalculator.add(2)
⬅ SampleCalculator.add = 28
⬅ SampleCalculator.complexCalculation = 28
Or create a custom tracer:
val customTracer = CodePathTracer.Builder()
.filter { event -> event.className.contains("Calculator") }
.formatter { event ->
when (event) {
is TraceEvent.Enter -> "➤ ${event.shortClassName}.${event.methodName}(${event.args.size})"
is TraceEvent.Exit -> "⬅ ${event.shortClassName}.${event.methodName} = ${event.returnValue}"
}
}
.build()
codePathTrace(customTracer) {
calculator.complexCalculation(5, 3)
}Verify everything works:
./gradlew test # Run all tests with tracing examplesThis confirms that method tracing works across:
- ✅ JVM applications
- ✅ Android applications (via Robolectric)
- ✅ Complex business logic chains
./gradlew build # Build everything
./gradlew :code-path-tracer:test # Run core tests
./gradlew test # Verify tracing worksWhen developing locally and testing changes in another project, you can use Gradle's dependency substitution:
Add to your project's settings.gradle.kts:
// Substitute published artifact with local code-path-tracer project
val localCodePathTracerPath = file("/path/to/local/code-path-tracer")
if (localCodePathTracerPath.exists()) {
includeBuild(localCodePathTracerPath) {
dependencySubstitution {
substitute(module("io.github.takahirom.codepathtracer:code-path-tracer"))
.using(project(":code-path-tracer"))
}
}
}Or use Maven Local:
# Publish to local Maven repository
./gradlew publishToMavenLocal
# In your project's build.gradle.kts, add mavenLocal() to repositories:
repositories {
mavenLocal() // Check local Maven first
mavenCentral()
}The dependency substitution approach is preferred for active development as changes are reflected immediately without republishing.
Built with ByteBuddy • Made for developers who care about understanding their code