Getting Started¶
Add Scribe to commonMain¶
Use the library from shared code in your Kotlin Multiplatform module:
The published artifacts are available from Maven Central. Add the dependency to your shared source set:
Create a Minimal Scribe¶
Create an object that extends Scribe, configure its private buffer, then hire its processor.
object AppScribe : Scribe() {
override val archivists: List<Archivist> = listOf(Archivist { entry ->
println(entry)
})
}
AppScribe.hire()
Emit a Single Event¶
Every event is a scroll. For a standalone event, build a scroll and seal it immediately:
val scroll = AppScribe.newScroll()
scroll["tag"] = JsonPrimitive("payments")
scroll["message"] = JsonPrimitive("starting checkout")
scroll["level"] = JsonPrimitive("INFO")
scroll.seal(AppScribe)
With the archivist above, the log output looks like this:
Track a Flow with Scroll¶
Scroll is a mutable map of JSON elements initialized by newScroll(...).
When sealing it, supply the Scribe runtime that should apply its footer
margin and deliver the event. Each seal(...) call emits a new snapshot of
the scroll data at that moment.
You can also merge other scrolls or nest them:
val base = AppScribe.newScroll()
base["gateway"] = JsonPrimitive("stripe")
val checkout = AppScribe.newScroll(id = "checkout-42")
checkout.extend(base) // copies missing keys from base
val meta = AppScribe.newScroll(id = "checkout-meta")
meta["items"] = JsonPrimitive(3)
checkout.append("meta", meta)
val scroll = AppScribe.newScroll(id = "checkout-42")
scroll["gateway"] = JsonPrimitive("stripe")
scroll["attempt"] = JsonPrimitive(1)
scroll["retry"] = JsonPrimitive(false)
scroll["cart"] = Json.encodeToJsonElement(
CheckoutMeta.serializer(),
CheckoutMeta(itemCount = 3, subtotalCents = 249_900, featureFlag = "wide-events"),
)
scroll.seal(AppScribe)
Use Multiple Runtimes¶
Each object is independent. A library may define its own object, or an application may supply a configured object to a component.
object PaymentsScribe : Scribe() {
override val bufferCapacity = 256
override val bufferOverflow = BufferOverflow.DROP_OLDEST
override val onArchiveFailure: ((Archivist, Entry, Throwable) -> Unit)? = null
override val archivists: List<Archivist> = listOf(Archivist { sendPaymentsRecord(it) })
}
object AnalyticsScribe : Scribe() {
override val bufferCapacity = 256
override val bufferOverflow = BufferOverflow.DROP_OLDEST
override val onArchiveFailure: ((Archivist, Entry, Throwable) -> Unit)? = null
override val archivists: List<Archivist> = listOf(Archivist { sendAnalyticsRecord(it) })
}
PaymentsScribe.hire()
AnalyticsScribe.hire()
Dismissing PaymentsScribe pauses only its job and does not stop AnalyticsScribe.
onIgnition observes global uncaught failures after the first hire() and is
unregistered by retire(). wasmWasi has no portable global hook; configure
onIgnition = null there or hire() reports an unsupported-operation error.
The emitted event shape is the scroll map itself:
{
"scroll_id": "checkout-42",
"gateway": "stripe",
"attempt": 1,
"retry": false,
"cart": {
"item_count": 3,
"subtotal_cents": 249900,
"feature_flag": "wide-events"
}
}
Choose the Right Archivist¶
- Every archivist receives
Entrysnapshots Archivistis a functional interface:Archivist { entry -> ... }is all you need- Add multiple archivists to a
Scribeobject to fan out to several outputs
What to Read Next¶
- API Concepts for the core types and terminology
- Lifecycle and Delivery for intake, processing, retirement, and archivist error callbacks
- SLF4J Provider for using Scribe as a JVM SLF4J 2.x provider