Skip to content

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)
    }
}