Skip to content

Getting Started

Add Scribe to commonMain

Use the library from shared code in your Kotlin Multiplatform module:

kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation("com.rafambn:scribe:0.4.0")
        }
    }
}

Create a Minimal Scribe

Create an object that extends Scribe, override its savers, then hire that object's runtime with a Channel<Entry>.

object AppScribe : Scribe() {
    override val shelves: List<Saver<*>> = listOf(NoteSaver { note ->
        println("[${note.level}] ${note.tag}: ${note.message}")
    })
}

AppScribe.hire(
    channel = Channel(
        capacity = 256,
        onBufferOverflow = BufferOverflow.DROP_OLDEST,
    ),
)

Emit a Single Event

Use note(...) for standalone events:

AppScribe.note(
    tag = "payments",
    message = "starting checkout",
    level = Urgency.INFO,
)

With the saver above, the log output looks like this:

[INFO] payments: starting checkout

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 SealedScroll using a 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, success = true)

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 shelves: List<Saver<*>> = listOf(EntrySaver { sendPaymentsRecord(it) })
}

object AnalyticsScribe : Scribe() {
    override val shelves: List<Saver<*>> = listOf(EntrySaver { sendAnalyticsRecord(it) })
}

PaymentsScribe.hire(channel = Channel(256))
AnalyticsScribe.hire(channel = Channel(256))

Retiring PaymentsScribe does not stop AnalyticsScribe.

The emitted SealedScroll shape:

{
  "success": true,
  "data": {
    "scroll_id": "checkout-42",
    "gateway": "stripe",
    "attempt": 1,
    "retry": false,
    "cart": {
      "item_count": 3,
      "subtotal_cents": 249900,
      "feature_flag": "wide-events"
    }
  }
}

Choose the Right Saver

val noteSaver = NoteSaver { note -> println(note) }
val scrollSaver = ScrollSaver { scroll -> println(scroll) }
val entrySaver = EntrySaver { entry -> println(entry) }
  • NoteSaver handles only Note
  • ScrollSaver handles only SealedScroll
  • EntrySaver handles both