
Flavored logging offering story-driven primitives: single-event notes and contextual scrolls, best-effort non-suspending variants, customizable note/scroll/entry savers, and scroll lifecycle enrichment via margins.
A flavored Kotlin Multiplatform logging library
Scribe is a Kotlin Multiplatform logging library built around the ideas from loggingsucks.com, so structured logs can model both single events and longer contextual flows.
| Documentation Page |
newScroll(...) and immediate-seal one-shot scrollsArchivist instances receiving Entry snapshotsMargin
Scribe objects for applications and imported librariesAdd Scribe to your commonMain dependencies:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("com.rafambn:scribe:0.7.0")
}
}
}Create a Scribe object, start processing its private buffer, and emit a scroll:
object AppScribe : Scribe() {
override val archivists: List<Archivist> = listOf(
Archivist { entry ->
println(entry)
}
)
}
AppScribe.hire()
val scroll = AppScribe.newScroll()
scroll["tag"] = JsonPrimitive("payments")
scroll["message"] = JsonPrimitive("starting checkout")
scroll["level"] = JsonPrimitive("INFO")
scroll.seal(AppScribe)Use a scroll when you need shared context for a longer flow:
object BillingScribe : Scribe() {
override val archivists: List<Archivist> = listOf(
Archivist { entry -> println(entry) }
)
override val imprint = mapOf(
"service" to JsonPrimitive("billing"),
"environment" to JsonPrimitive("production"),
)
}
BillingScribe.hire()
val scroll = BillingScribe.newScroll(id = "checkout-42")
scroll["gateway"] = JsonPrimitive("stripe")
scroll["attempt"] = JsonPrimitive(1)
scroll["retry"] = JsonPrimitive(false)
scroll.seal(BillingScribe)Each Scribe object has independent configuration and delivery lifecycle. A Scroll is a mutable JSON-element map initialized by newScroll(...); pass the runtime that should enrich and deliver it to scroll.seal(scribe). Each seal(...) call emits a separate snapshot of the scroll data.
Version 0.7.0 publishes the core scribe module for exactly these 22 targets:
jvm, android
js, wasmJs, wasmWasi
androidNativeArm32, androidNativeArm64, androidNativeX86, androidNativeX64
iosArm64, iosSimulatorArm64, iosX64, macosArm64, tvosArm64, tvosSimulatorArm64, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64
linuxArm64, linuxX64, mingwX64
The js and wasmJs targets support both browser and Node.js execution; wasmWasi is configured for Node.js. The project inherits Kotlin 2.4.10, kotlinx.serialization 1.11.0, and kotlinx.coroutines 1.11.0 runtime requirements. The scribe-slf4j module remains JVM-only.
Both artifacts are consumed from Maven Central. Scribe does not publish an npm package; Kotlin/JS and Kotlin/Wasm consumers use the Gradle Multiplatform dependency above.
For JVM applications, add the SLF4J provider:
dependencies {
implementation("com.rafambn:scribe-slf4j:0.7.0")
}Select exactly one application-wide backend with @ScribeBackend:
@ScribeBackend
object AppScribe : Slf4jScribe() {
override val bufferCapacity = 1_024
override val bufferOverflow = BufferOverflow.DROP_OLDEST
override val onArchiveFailure: ((Archivist, Entry, Throwable) -> Unit)? = null
override val archivists = listOf(
Archivist { entry -> println(entry) },
)
override fun isEnabled(
loggerName: String,
level: Level,
marker: Marker?,
): Boolean = level.toInt() >= Level.INFO.toInt()
}The provider discovers the annotated backend once on the first SLF4J access and registers a JVM shutdown hook. Intake starts open, so early calls accumulate in the private buffer; the application calls AppScribe.hire() when processing should begin. The shutdown hook retires the Scribe and drains accepted entries automatically. Initialization fails with a descriptive error when no backend is present, multiple backends are annotated, or the annotation is not placed on a Kotlin object extending Slf4jScribe.
scribe-slf4j is a standalone SLF4J provider. Do not include another provider such as logback-classic in the same runtime classpath.
See the full documentation for lifecycle controls, overflow behavior, margins, and SLF4J field mapping.
Scribe is designed for high-throughput and thread-safe concurrent logging.
The repository includes JVM throughput tests for concurrent in-memory ingestion and serialized file writing. Results depend on the machine, runtime, buffer configuration, and archivist implementation; run the tests in your target environment before using them for capacity planning.
A flavored Kotlin Multiplatform logging library
Scribe is a Kotlin Multiplatform logging library built around the ideas from loggingsucks.com, so structured logs can model both single events and longer contextual flows.
| Documentation Page |
newScroll(...) and immediate-seal one-shot scrollsArchivist instances receiving Entry snapshotsMargin
Scribe objects for applications and imported librariesAdd Scribe to your commonMain dependencies:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("com.rafambn:scribe:0.7.0")
}
}
}Create a Scribe object, start processing its private buffer, and emit a scroll:
object AppScribe : Scribe() {
override val archivists: List<Archivist> = listOf(
Archivist { entry ->
println(entry)
}
)
}
AppScribe.hire()
val scroll = AppScribe.newScroll()
scroll["tag"] = JsonPrimitive("payments")
scroll["message"] = JsonPrimitive("starting checkout")
scroll["level"] = JsonPrimitive("INFO")
scroll.seal(AppScribe)Use a scroll when you need shared context for a longer flow:
object BillingScribe : Scribe() {
override val archivists: List<Archivist> = listOf(
Archivist { entry -> println(entry) }
)
override val imprint = mapOf(
"service" to JsonPrimitive("billing"),
"environment" to JsonPrimitive("production"),
)
}
BillingScribe.hire()
val scroll = BillingScribe.newScroll(id = "checkout-42")
scroll["gateway"] = JsonPrimitive("stripe")
scroll["attempt"] = JsonPrimitive(1)
scroll["retry"] = JsonPrimitive(false)
scroll.seal(BillingScribe)Each Scribe object has independent configuration and delivery lifecycle. A Scroll is a mutable JSON-element map initialized by newScroll(...); pass the runtime that should enrich and deliver it to scroll.seal(scribe). Each seal(...) call emits a separate snapshot of the scroll data.
Version 0.7.0 publishes the core scribe module for exactly these 22 targets:
jvm, android
js, wasmJs, wasmWasi
androidNativeArm32, androidNativeArm64, androidNativeX86, androidNativeX64
iosArm64, iosSimulatorArm64, iosX64, macosArm64, tvosArm64, tvosSimulatorArm64, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64
linuxArm64, linuxX64, mingwX64
The js and wasmJs targets support both browser and Node.js execution; wasmWasi is configured for Node.js. The project inherits Kotlin 2.4.10, kotlinx.serialization 1.11.0, and kotlinx.coroutines 1.11.0 runtime requirements. The scribe-slf4j module remains JVM-only.
Both artifacts are consumed from Maven Central. Scribe does not publish an npm package; Kotlin/JS and Kotlin/Wasm consumers use the Gradle Multiplatform dependency above.
For JVM applications, add the SLF4J provider:
dependencies {
implementation("com.rafambn:scribe-slf4j:0.7.0")
}Select exactly one application-wide backend with @ScribeBackend:
@ScribeBackend
object AppScribe : Slf4jScribe() {
override val bufferCapacity = 1_024
override val bufferOverflow = BufferOverflow.DROP_OLDEST
override val onArchiveFailure: ((Archivist, Entry, Throwable) -> Unit)? = null
override val archivists = listOf(
Archivist { entry -> println(entry) },
)
override fun isEnabled(
loggerName: String,
level: Level,
marker: Marker?,
): Boolean = level.toInt() >= Level.INFO.toInt()
}The provider discovers the annotated backend once on the first SLF4J access and registers a JVM shutdown hook. Intake starts open, so early calls accumulate in the private buffer; the application calls AppScribe.hire() when processing should begin. The shutdown hook retires the Scribe and drains accepted entries automatically. Initialization fails with a descriptive error when no backend is present, multiple backends are annotated, or the annotation is not placed on a Kotlin object extending Slf4jScribe.
scribe-slf4j is a standalone SLF4J provider. Do not include another provider such as logback-classic in the same runtime classpath.
See the full documentation for lifecycle controls, overflow behavior, margins, and SLF4J field mapping.
Scribe is designed for high-throughput and thread-safe concurrent logging.
The repository includes JVM throughput tests for concurrent in-memory ingestion and serialized file writing. Results depend on the machine, runtime, buffer configuration, and archivist implementation; run the tests in your target environment before using them for capacity planning.