
Auto-injects declaration-scoped loggers via a compiler plugin, rewriting calls to the backend while preserving caller lines; supports lazy lambda messages, compile-time level pruning, test capture, coroutine-scoped logging.
Log in Kotlin without declaring a logger. The compiler plugin derives its name from the surrounding code.
Requires Kotlin 2.4.0 or newer.
import io.akki.log
class JobProcessor {
fun run(job: Job) {
log.info { "started ${job.id}" }
}
}For a JobProcessor in com.example, the plugin turns log into a read of a static field holding the
com.example.JobProcessor logger. It also rewrites logging calls to call the backend directly, preserving the
caller line. An optional compile-time threshold removes calls below the selected level.
akki-core: the logging API.
log and logger() use the enclosing declaration's logger and require the plugin.Log.of<T>() and Log.named("...") work without the plugin. Java can use Log.of(Class) and Log.named.Logger provides eager and lazy overloads of trace, debug, info, warn, and error.LogBackend connects a backend; LogScope selects one for the current thread.akki-slf4j: backend for SLF4J 2, enabled when an SLF4J 2 provider is available at runtime.akki-compiler: Kotlin compiler plugin, one artifact for every supported Kotlin release.akki-gradle: sets up the compiler plugin with akki-core and akki-slf4j and keeps every akki module at the
plugin version.akki-test: captures test logs with recordLogs { } or RecordingBackend.akki-coroutines: carries a LogScope in a coroutine context via LogScope.asContextElement().akki-test-coroutines: provides suspending recordLogs { } for coroutine tests.akki-bom: supplies the version of every akki artifact to Gradle platforms and Maven dependencyManagement.Add the plugin to your build:
plugins {
kotlin("jvm") version "2.4.20"
id("io.github.wordyarc.akki") version "0.3.0"
}Both akki-core and akki-slf4j are added by the plugin. Logging uses the application's SLF4J 2 provider;
Spring Boot, for example, includes logback-classic. If no provider is available, akki prints a one-time notice and
sends records at INFO or above to stderr. A library, or an application with its own LogBackend, leaves
akki-slf4j out with akki { jvm { includeSlf4jBackend = false } }. You can add a provider explicitly:
dependencies {
runtimeOnly("ch.qos.logback:logback-classic:1.5.20")
}To capture logs in tests:
dependencies {
testImplementation("io.github.wordyarc:akki-test:0.3.0")
}With akki-bom as a platform, akki modules are declared without a version; in a Kotlin Multiplatform source set,
write project.dependencies.platform(...):
dependencies {
testImplementation(platform("io.github.wordyarc:akki-bom:0.3.0"))
testImplementation("io.github.wordyarc:akki-test")
testImplementation("io.github.wordyarc:akki-test-coroutines")
}In the compilations it applies to, the plugin resolves every akki module at its own version: a module declared at
an older version is raised, a newer one that a dependency brings is lowered, and a newer one declared directly
fails the build. This pin is not published. Gradle builds without the plugin still get a single akki version: each
akki library depends on akki-bom, so Gradle resolves all of them at the newest akki version in the graph.
Import akki-bom and use the -jvm artifact of each multiplatform module (akki-core-jvm, akki-test-jvm,
akki-coroutines-jvm, akki-test-coroutines-jvm). Set -Xplugin to the akki-compiler jar of the same version:
<properties>
<akki.version>0.3.0</akki.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.github.wordyarc</groupId>
<artifactId>akki-bom</artifactId>
<version>${akki.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.github.wordyarc</groupId>
<artifactId>akki-core-jvm</artifactId>
</dependency>
<dependency>
<groupId>io.github.wordyarc</groupId>
<artifactId>akki-slf4j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.github.wordyarc</groupId>
<artifactId>akki-test-jvm</artifactId>
<scope>test</scope>
</dependency>
</dependencies>All akki artifacts use the same release version, following semantic versioning. Until 1.0,
minor releases may introduce breaking changes. Patch releases provide fixes and backward-compatible additions;
these can include support for an additional Kotlin release.
The plugin depends on the Kotlin compiler, whose API changes between releases. akki-compiler contains an adapter
for each compiler API it supports and selects one by the version of the compiler that runs it, so Gradle, Maven and
the command line use the same artifact. Beta and RC builds of Kotlin are accepted too, dev builds are not. In a
compiler older than every adapter the plugin fails the compilation and names the oldest supported version.
| akki | Kotlin | Compiler plugin |
|---|---|---|
0.3.0 |
2.4.0, 2.4.10, 2.4.20
|
akki-compiler |
0.2.0 |
2.4.0, 2.4.10, 2.4.20
|
akki-compiler-kotlin-2.4 |
0.1.0 |
2.4.x |
akki-compiler |
log.debug { "queue: ${queue.dump()}" } // the lambda runs only when the level is enabled
log.error(cause) { "job ${job.id} failed" }
log.warn(cause, mapOf("jobId" to job.id, "attempt" to attempt)) { "retrying" }
if (log.isEnabled(Level.TRACE)) { /* ... */ }The nearest enclosing declaration with a qualified name determines the logger name. Companion objects and enum
entry bodies share their class's logger. Lambdas, local classes, and anonymous classes use the enclosing class's
logger, or the file's logger at the top level. For example, top-level functions in Jobs.kt use com.example.Jobs.
Use logger() to store the declaration's logger in a property:
private val log = logger()
class JobProcessor {
private val log = logger()
}The Log factory also works without the plugin:
private val log = Log.of<JobProcessor>() // by type
private val log = Log.of(JobProcessor::class) // by class
private val log = Log.of(javaClass) // by runtime class, for abstract base classes
private val history = Log.named("jobs.history") // by an arbitrary nameThe plugin can fold constant factory calls into static field reads. A folded Log.of for the enclosing type shares
the field used by log. Log.of(javaClass) always looks up the logger at runtime.
akki-slf4j passes fields to SLF4J as key-value pairs and uses the exception as the cause. If a LogBackend is
registered with ServiceLoader, akki chooses it over akki-slf4j. Calling Log.install(backend) replaces the
backend for the whole process.
val records = recordLogs { processor.run(Job("nightly-report")) }
assertEquals("started nightly-report", records.single().message)Each capture is local to its thread, which keeps parallel tests independent. In coroutine tests, import recordLogs
from akki-test-coroutines to capture logs across dispatcher changes.
Inside akki {}, compiler plugin settings belong in compilerOptions {} and affect the generated code.
The other block, jvm {}, controls JVMs launched by Gradle without changing that code.
Set minLevel to remove lower-level records from the compiled code:
akki {
compilerOptions {
minLevel = io.akki.gradle.MinLevel.INFO
}
}By default, logger names use source notation, such as com.example.Outer.Inner. Launch the JVM with
-Dio.akki.loggerNameStyle=jvm-class to use JVM names instead: com.example.Outer$Inner and JobsKt for files.
The default value of this option is source. The Gradle plugin passes the option to the JVMs that Gradle starts,
such as test, run and bootRun:
akki {
jvm {
loggerNameStyle = io.akki.gradle.LoggerNameStyle.JVM_CLASS
}
}A JVM started outside Gradle, for example with java -jar or in a container, still needs the -D option. A value
that a task sets itself with systemProperty or jvmArgs takes precedence over loggerNameStyle.
includeSlf4jBackend, true by default, adds akki-slf4j to the runtime classpath of every JVM compilation. Set
it to false in a library, or in an application that registers its own LogBackend.
log and logger() compile without the plugin but throw when called. Use Log.of or Log.named in that case.-Xplugin to the akki-compiler
artifact and import akki-bom of the same version; the compiler plugin reports a mismatched akki-core.log produce synthetic line numbers. Java
calls report io.akki.Logger unless that class is registered in logback's frameworkPackages.recordLogs and LogScope capture the current thread. Executors and other threads use the global backend.loggerNameStyle reaches only the JVMs that Gradle starts, and
setting the style with System.setProperty in main is too late.minLevel cannot be restored through runtime logging configuration.Log.named retains its loggers, so applications need a bounded set of names.1.0 may break compatibility.Log in Kotlin without declaring a logger. The compiler plugin derives its name from the surrounding code.
Requires Kotlin 2.4.0 or newer.
import io.akki.log
class JobProcessor {
fun run(job: Job) {
log.info { "started ${job.id}" }
}
}For a JobProcessor in com.example, the plugin turns log into a read of a static field holding the
com.example.JobProcessor logger. It also rewrites logging calls to call the backend directly, preserving the
caller line. An optional compile-time threshold removes calls below the selected level.
akki-core: the logging API.
log and logger() use the enclosing declaration's logger and require the plugin.Log.of<T>() and Log.named("...") work without the plugin. Java can use Log.of(Class) and Log.named.Logger provides eager and lazy overloads of trace, debug, info, warn, and error.LogBackend connects a backend; LogScope selects one for the current thread.akki-slf4j: backend for SLF4J 2, enabled when an SLF4J 2 provider is available at runtime.akki-compiler: Kotlin compiler plugin, one artifact for every supported Kotlin release.akki-gradle: sets up the compiler plugin with akki-core and akki-slf4j and keeps every akki module at the
plugin version.akki-test: captures test logs with recordLogs { } or RecordingBackend.akki-coroutines: carries a LogScope in a coroutine context via LogScope.asContextElement().akki-test-coroutines: provides suspending recordLogs { } for coroutine tests.akki-bom: supplies the version of every akki artifact to Gradle platforms and Maven dependencyManagement.Add the plugin to your build:
plugins {
kotlin("jvm") version "2.4.20"
id("io.github.wordyarc.akki") version "0.3.0"
}Both akki-core and akki-slf4j are added by the plugin. Logging uses the application's SLF4J 2 provider;
Spring Boot, for example, includes logback-classic. If no provider is available, akki prints a one-time notice and
sends records at INFO or above to stderr. A library, or an application with its own LogBackend, leaves
akki-slf4j out with akki { jvm { includeSlf4jBackend = false } }. You can add a provider explicitly:
dependencies {
runtimeOnly("ch.qos.logback:logback-classic:1.5.20")
}To capture logs in tests:
dependencies {
testImplementation("io.github.wordyarc:akki-test:0.3.0")
}With akki-bom as a platform, akki modules are declared without a version; in a Kotlin Multiplatform source set,
write project.dependencies.platform(...):
dependencies {
testImplementation(platform("io.github.wordyarc:akki-bom:0.3.0"))
testImplementation("io.github.wordyarc:akki-test")
testImplementation("io.github.wordyarc:akki-test-coroutines")
}In the compilations it applies to, the plugin resolves every akki module at its own version: a module declared at
an older version is raised, a newer one that a dependency brings is lowered, and a newer one declared directly
fails the build. This pin is not published. Gradle builds without the plugin still get a single akki version: each
akki library depends on akki-bom, so Gradle resolves all of them at the newest akki version in the graph.
Import akki-bom and use the -jvm artifact of each multiplatform module (akki-core-jvm, akki-test-jvm,
akki-coroutines-jvm, akki-test-coroutines-jvm). Set -Xplugin to the akki-compiler jar of the same version:
<properties>
<akki.version>0.3.0</akki.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.github.wordyarc</groupId>
<artifactId>akki-bom</artifactId>
<version>${akki.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.github.wordyarc</groupId>
<artifactId>akki-core-jvm</artifactId>
</dependency>
<dependency>
<groupId>io.github.wordyarc</groupId>
<artifactId>akki-slf4j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.github.wordyarc</groupId>
<artifactId>akki-test-jvm</artifactId>
<scope>test</scope>
</dependency>
</dependencies>All akki artifacts use the same release version, following semantic versioning. Until 1.0,
minor releases may introduce breaking changes. Patch releases provide fixes and backward-compatible additions;
these can include support for an additional Kotlin release.
The plugin depends on the Kotlin compiler, whose API changes between releases. akki-compiler contains an adapter
for each compiler API it supports and selects one by the version of the compiler that runs it, so Gradle, Maven and
the command line use the same artifact. Beta and RC builds of Kotlin are accepted too, dev builds are not. In a
compiler older than every adapter the plugin fails the compilation and names the oldest supported version.
| akki | Kotlin | Compiler plugin |
|---|---|---|
0.3.0 |
2.4.0, 2.4.10, 2.4.20
|
akki-compiler |
0.2.0 |
2.4.0, 2.4.10, 2.4.20
|
akki-compiler-kotlin-2.4 |
0.1.0 |
2.4.x |
akki-compiler |
log.debug { "queue: ${queue.dump()}" } // the lambda runs only when the level is enabled
log.error(cause) { "job ${job.id} failed" }
log.warn(cause, mapOf("jobId" to job.id, "attempt" to attempt)) { "retrying" }
if (log.isEnabled(Level.TRACE)) { /* ... */ }The nearest enclosing declaration with a qualified name determines the logger name. Companion objects and enum
entry bodies share their class's logger. Lambdas, local classes, and anonymous classes use the enclosing class's
logger, or the file's logger at the top level. For example, top-level functions in Jobs.kt use com.example.Jobs.
Use logger() to store the declaration's logger in a property:
private val log = logger()
class JobProcessor {
private val log = logger()
}The Log factory also works without the plugin:
private val log = Log.of<JobProcessor>() // by type
private val log = Log.of(JobProcessor::class) // by class
private val log = Log.of(javaClass) // by runtime class, for abstract base classes
private val history = Log.named("jobs.history") // by an arbitrary nameThe plugin can fold constant factory calls into static field reads. A folded Log.of for the enclosing type shares
the field used by log. Log.of(javaClass) always looks up the logger at runtime.
akki-slf4j passes fields to SLF4J as key-value pairs and uses the exception as the cause. If a LogBackend is
registered with ServiceLoader, akki chooses it over akki-slf4j. Calling Log.install(backend) replaces the
backend for the whole process.
val records = recordLogs { processor.run(Job("nightly-report")) }
assertEquals("started nightly-report", records.single().message)Each capture is local to its thread, which keeps parallel tests independent. In coroutine tests, import recordLogs
from akki-test-coroutines to capture logs across dispatcher changes.
Inside akki {}, compiler plugin settings belong in compilerOptions {} and affect the generated code.
The other block, jvm {}, controls JVMs launched by Gradle without changing that code.
Set minLevel to remove lower-level records from the compiled code:
akki {
compilerOptions {
minLevel = io.akki.gradle.MinLevel.INFO
}
}By default, logger names use source notation, such as com.example.Outer.Inner. Launch the JVM with
-Dio.akki.loggerNameStyle=jvm-class to use JVM names instead: com.example.Outer$Inner and JobsKt for files.
The default value of this option is source. The Gradle plugin passes the option to the JVMs that Gradle starts,
such as test, run and bootRun:
akki {
jvm {
loggerNameStyle = io.akki.gradle.LoggerNameStyle.JVM_CLASS
}
}A JVM started outside Gradle, for example with java -jar or in a container, still needs the -D option. A value
that a task sets itself with systemProperty or jvmArgs takes precedence over loggerNameStyle.
includeSlf4jBackend, true by default, adds akki-slf4j to the runtime classpath of every JVM compilation. Set
it to false in a library, or in an application that registers its own LogBackend.
log and logger() compile without the plugin but throw when called. Use Log.of or Log.named in that case.-Xplugin to the akki-compiler
artifact and import akki-bom of the same version; the compiler plugin reports a mismatched akki-core.log produce synthetic line numbers. Java
calls report io.akki.Logger unless that class is registered in logback's frameworkPackages.recordLogs and LogScope capture the current thread. Executors and other threads use the global backend.loggerNameStyle reaches only the JVMs that Gradle starts, and
setting the style with System.setProperty in main is too late.minLevel cannot be restored through runtime logging configuration.Log.named retains its loggers, so applications need a bounded set of names.1.0 may break compatibility.