Custom Trainers
The Trainer interface is barK's extension point. Implement it to send logs anywhere โ crash reporters, Slack, analytics pipelines, files, or any custom destination.
The Trainer Interface
Trainer.kt
interface Trainer {
val pack: Pack // Categorizes the trainer; controls replacement behavior
val minLevel: Level // Minimum level this trainer will handle
fun handle(level: Level, tag: String, message: String, throwable: Throwable?)
}
The Pack System
Pack categorizes trainers and enforces a one-per-category rule (except CUSTOM):
| Pack | Used by | Rule |
|---|---|---|
CONSOLE |
UnitTestTrainer, ColoredUnitTestTrainer |
Only one active at a time |
SYSTEM |
AndroidLogTrainer, NSLogTrainer |
Only one active at a time |
FILE |
FileTrainer |
Only one active at a time |
CUSTOM |
Your custom trainers | Multiple allowed |
Choosing a Pack
Training a new trainer into an occupied pack replaces the existing one:
Bark.train(AndroidLogTrainer()) // SYSTEM slot: AndroidLogTrainer
Bark.train(UnitTestTrainer()) // SYSTEM slot replaced โ now UnitTestTrainer
// But CUSTOM trainers stack
Bark.train(SlackTrainer()) // CUSTOM #1
Bark.train(ErrorTracker()) // CUSTOM #2 โ both remain active
Use Pack.CUSTOM for most custom trainers โ it allows multiple instances to coexist:
Bark.train(SlackTrainer()) // CUSTOM #1
Bark.train(ErrorTracker()) // CUSTOM #2
Bark.train(AnalyticsLogger()) // CUSTOM #3 โ all three remain active
The Pack.FILE is reserved for a single file logger, as to avoid having multiple file writers:
Bark.train(FileTrainer(logFile = File("v1.log")))
Bark.train(FileTrainer(logFile = File("v2.log"))) // Replaces the first one
Log Level Filtering
barK calls handle() for every log โ your trainer is responsible for its own threshold check, or you can rely on barK's built-in filtering by setting minLevel correctly:
class MyTrainer(
override val minLevel: Level = Level.INFO,
) : Trainer {
override val pack = Pack.CUSTOM
override fun handle(level: Level, tag: String, message: String, throwable: Throwable?) {
// handle minLevel filter for custom functionality
if (level.ordinal < minLevel.ordinal) return
// ... send log
}
}
Examples
Error Tracking (e.g. Sentry, Crashlytics)
CrashReportingTrainer.kt
class CrashReportingTrainer(
override val minLevel: Level = Level.ERROR,
) : Trainer {
override val pack: Pack = Pack.CUSTOM
override fun handle(level: Level, tag: String, message: String, throwable: Throwable?) {
ErrorTracker.log(level, tag, message, throwable)
}
}
Slack Notifications
SlackTrainer.kt
class SlackTrainer(
override val minLevel: Level = Level.WARNING,
private val webhookUrl: String,
) : Trainer {
override val pack: Pack = Pack.CUSTOM
override fun handle(level: Level, tag: String, message: String, throwable: Throwable?) {
SlackClient.send(webhookUrl, "[${level.name}] $tag: $message")
}
}
File Logging
FileTrainer.kt
class FileTrainer(
override val minLevel: Level = Level.WARNING,
private val logFile: File,
) : Trainer {
override val pack: Pack = Pack.FILE
override fun handle(level: Level, tag: String, message: String, throwable: Throwable?) {
logFile.appendText("${level.name} [$tag]: $message\n")
throwable?.let { logFile.appendText(it.stackTraceToString() + "\n") }
}
}
Registering Custom Trainers
Bark.train(CrashReportingTrainer())
Bark.train(SlackTrainer(webhookUrl = "https://hooks.slack.com/..."))
Bark.train(FileTrainer(logFile = File("app.log")))
Testing Your Trainer
class MyTrainerTest {
@Test
fun `trainer handles messages above minLevel threshold`() {
val received = mutableListOf<String>()
val trainer = object : Trainer {
override val pack = Pack.CUSTOM
override val minLevel = Level.WARNING
override fun handle(level: Level, tag: String, message: String, throwable: Throwable?) {
received.add(message)
}
}
Bark.releaseAllTrainers()
Bark.train(trainer)
Bark.d("Should be ignored")
Bark.w("Should appear")
Bark.e("Should appear too")
assertEquals(2, received.size)
}
}