Skip to content
Kona

Logging

Kona’s Log facade sits on top of SLF4J / Logback. When an application boots, a LogManager resolves named channels from config — including Laravel-shaped drivers such as stack, daily, single, stderr, stdout, syslog, errorlog, and null.

Log.info("Application started")
Log.channel("orders").warning("Retrying charge")
Log.error("Payment failed", exception)

Bootstrap binds LogManager into the container and onto the Log facade. You rarely construct a manager yourself.

On boot, Kona also attaches a daily rolling file appender to the root logger (KONA_FILE):

Setting Config key Env Default
Write to file logging.to_file LOG_TO_FILE true
File name logging.file LOG_FILE kona.log (under storage/logs unless absolute)
Root level logging.level LOG_LEVEL info
Default channel logging.default LOG_CHANNEL stack

Scaffolded apps include storage/logs/. After ./grind serve (or any boot that logs), look for storage/logs/kona.log. Rolled history uses kona.yyyy-MM-dd.log (14 days retained on the root appender).

Set LOG_TO_FILE=false when you only want the console. For custom layouts or extra appenders, add src/main/resources/logback.xml — Kona refreshes KONA_FILE and does not replace your console configuration.

A channel is a named destination resolved by LogManager. The default channel name comes from logging.default (normally stack).

API Role
Log.info / debug / warning / warn / notice / error / critical / alert / emergency Write on the default channel
Log.log(level, message) Same, with a LogLevel or string level
Log.channel(name) Resolve a named channel
Log.channel() Default channel
Log.stack("a", "b") Ephemeral fan-out stack (not cached under a config key)
Log.setDefaultChannel(name) Override default for this process
Log.shareContext / withContext / flushSharedContext Request-scoped fields (see below)
Log.manager() Bound LogManager, or null before boot

Without a bound manager (early boot / unit tests), Log.channel(name) falls back to a named SLF4J logger.

Log.channel("orders").info("Order placed")
Log.setDefaultChannel("api")
Log.info("uses the api channel")
Log.stack("single", "stderr").warning("fan-out")

Every channel exposes the same level methods, plus with(map) to merge shared context and return itself for chaining.

Configure channels under logging.channels.{name}.*. On boot, LogManager.seedDefaults registers a full starter map when keys are missing:

Channel Driver Notes
stack stack Fans out to logging.channels.stack.channels (env LOG_STACK, default single)
single single One file (path defaults to logging.file)
daily daily Time-based rolling file (days / max_files, env LOG_DAILY_DAYS, default 14)
stderr / console stderr Console appender → System.err
stdout stdout Console appender → System.out
syslog syslog Logback SyslogAppender (host, facility)
errorlog errorlog Console err (or JUL if Logback context is unavailable)
null null Discards every message
emergency (path only) Last-resort file used when a default-channel write fails

You can add your own names:

// config (executable ConfigFile or .configure { })
logging.channels.payments.driver = "daily"
logging.channels.payments.path = "payments.log"
logging.channels.payments.level = "debug"
logging.channels.payments.days = 30

stack — channels may be a comma string, a list, or an array of child channel names. Optional ignore_exceptions (boolean): when true, a failing child does not abort the rest.

single / daily — path (relative → storage/logs/…, or absolute), optional level. Daily also uses days or max_files. When the resolved path matches the root KONA_FILE appender, Kona reuses that appender so two writers do not fight over the same file.

stderr / stdout / console / errorlog / syslog — optional level; syslog also host (default localhost) and facility (default USER).

null — no I/O.

Unknown driver values throw IllegalArgumentException. Circular stack graphs throw IllegalStateException.

val logs = app.resolve(LogManager::class.java)
logs.extend("memory") { name, config ->
object : LogChannel() {
override fun write(level: LogLevel, message: String, throwable: Throwable?) {
// capture in tests / buffers
}
}
}
app.config.set("logging.channels.mem.driver", "memory")
logs.tap("mem") { channel ->
// runs once when the channel is first resolved (and immediately if already resolved)
}
logs.set("orders", NullLogChannel) // tests / advanced wiring
logs.forget("orders")
logs.flush() // drop all cached channels

If a channel name matches a built-in driver (daily, stderr, …) but has no driver key, LogManager treats the name itself as the driver hint.

LogLevel mirrors common severity names. String forms accepted by LogLevel.parse / log(level, …):

Level Aliases
EMERGENCY emergency, emerg
ALERT alert
CRITICAL critical, crit
ERROR error, err
WARNING warning, warn
NOTICE notice
INFO info
DEBUG debug

Channel level and logging.level are passed to Logback’s Level.toLevel (default debug when unset on a prepared channel logger).

Attach request-scoped fields once; every subsequent message on a channel includes them until you flush. Values are also mirrored into SLF4J MDC (null → the string "null").

Log.shareContext(mapOf("requestId" to "r-1", "userId" to 42))
Log.info("Handling checkout")
// message includes {requestId=r-1, userId=42}
Log.channel("orders").with(mapOf("orderId" to 9)).info("charged")
Log.flushSharedContext()

withContext merges values and returns the default channel for chaining. Prefer flushing at the end of each HTTP request (for example in middleware) so context does not leak across virtual threads.

If writing to the default channel throws while a LogManager is bound, Log calls LogManager.writeEmergency. That appends a timestamped line to logging.channels.emergency.path (else logging.file, else kona.log). If even that fails, the message goes to System.err.

DefaultExceptionHandler logs unexpected failures through Log.error unless you install a custom report callback — see Exceptions.

  • Use the null channel or logs.extend("memory", …) to assert log traffic without touching disk.
  • FileLogging.resetForTests() / Log.unbind() / Kona.reset() keep suites isolated (as in kona-core’s own tests).
  • Prefer absolute path values under @TempDir when asserting file contents.