Kotlin Compiler plugin that gives you the ability to be notified every time annotated function is called.
General purpose is for effortless logging or analytics, but it can (but probably shouldn't) be used for more advanced
use-cases.
During compilation, Krang Compiler Plugin injects a small piece of code at the beginning of a function body,
that's simply notifying Krang Runtime that a function with a given name and parameters has been called.
This effectively means Krang is transforming your code in a following way:
Before compilation | After compilation |
---|---|
class Foo {
@Intercept
fun bar(baz: String) {
//Rest of your code
}
} |
class Foo {
fun bar(baz: String) {
Krang.notifyListeners("bar", baz)
//Rest of your code
}
} |
Note that this is all done during transformation phase of the compilation, your source code won't be polluted by Krang
To get notified every time when a function is called simply annotate that function with @Intercept
annotation.
Note that Krang supports any valid function, for example extension, nested, inline functions, etc.
fun main() {
// Register krang listener
Krang.addListener { functionName, parameters ->
println("Function with name:$functionName and ${parameters.joinToString()} invoked")
}
// Call annotated function - Will print Function with name:bar and bzz invoked
Foo().bar("bzz")
}
class Foo {
// Decorate a function with @Intercept annotation
@Intercept
fun bar(baz: String) {
}
}
In some cases its might be useful to intercept all function calls in a given class.
To do this, simply annotate desired class with @Intercept
annotation
fun main() {
// Register krang listener
Krang.addListener { functionName, parameters ->
println("Function with name:$functionName and ${parameters.joinToString()} invoked")
}
// Call annotated function - Will print:
// Function with name: <Init> invoked for constructor call
// Function with name bar invoked
Foo().bar("bzz")
}
// Decorate a class with @Intercept annotation
@Intercept
class Foo {
fun bar(baz: String) {
}
}
If you want to omit specific parameters from being reported to krang, annotate desiered value parameter with @Redact
annotation
fun main() {
// Register krang listener
Krang.addListener { functionName, parameters ->
println("Function with name:$functionName and ${parameters.joinToString()} invoked")
}
// Call annotated function - Will print Function with name:bar invoked
Foo().bar("bzz")
}
class Foo {
// Decorate a function with @Intercept annotation
// Value parameters market with Redact annotation will not be passed to Krang
@Intercept
fun bar(@Redact baz: String) {
}
}
Krang supports inheritance for both class and function transformations.
This effectively means that Krang will check if a class or a function overrides a type that has @Intercept or @Redact
annotations and will apply a transformation based on that.
fun main() {
// Register krang listener
Krang.addListener { functionName, parameters ->
println("Function with name:$functionName and ${parameters.joinToString()} invoked")
}
// Call annotated function - Will print Function with name:bar invoked
Foo().bar(Test())
}
// Your custom type whose children should be intercepted
@Intercept
interface Interceptable
// Your custom type that should always be omitted from Krang
@Redact
data class Test(val test: Int = 1)
class Foo : Interceptable {
fun bar(test: Test) {
}
}
If you want to intercept all functions in a codebase enable godMode
// In your build.gradle
krang {
enabled.set(true)
godMode.set(true) // false by default
}
// In your source
fun main() {
// Register krang listener
Krang.addListener { functionName, parameters ->
println("Function with name:$functionName and ${parameters.joinToString()} invoked")
}
// Call annotated function - Will print:
// Function with name: <Init> invoked for constructor call
// Function with name bar invoked
Foo().bar("bzz")
}
// You don't have to annotate anything, everything will be transformed
class Foo {
fun bar(baz: String) {
}
}
fun main() {
Krang.enabled = false
}
// In your build.gradle
krang {
enabled = false
}
Plugin is published on Maven central.
Note that runtime dependency is automatically applied, and you don't have to add anything explicitly.
Kotlin
//Kotlin
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("com.github.milis92.krang:krang-gradle-plugin:$latest_version_here")
}
}
apply(plugin = "com.github.milis92.krang")
krang {
enabled.set(true) // true by default
godMode.set(true) // false by default
}
Groovy
//Groovy
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "com.github.milis92.krang:krang-gradle-plugin:$latest_version_here"
}
}
apply plugin: "com.github.milis92.krang"
Snapshots
buildscript {
repositories {
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
classpath("com.github.milis92.krang:krang-gradle-plugin:$latest_version_here")
}
}
apply(plugin = "com.github.milis92.krang")
Krang Gradle plugin supports different configurations per variant.
This is particularly interesting for Android project, if you for example want to disable Krang for release builds.
krang {
enabled.set(true)
godMode.set(true)
variantFilter {
val kotlinCompilation: KotlinCompilation<*> = this
when (kotlinCompilation) {
is KotlinJvmAndroidCompilation -> {
if (kotlinCompilation.androidVariant.buildType.name == "release") {
enabled.set(false)
}
}
}
}
}
This plugin works only with kotlin IR compiler backend witch is enabled by default from Kotlin > 1.5!
//For kotlin < 1.5
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
useIR = true
}
}
target {
js(IR) {
}
}
IR already enabled by default!
Big shutout to Brian Norman and his awesome blog series on Compiler plugins