DEV Community

Cover image for barK: A Lightweight Logging Library for Android
Ivan Garza
Ivan Garza

Posted on • Originally published at Medium

barK: A Lightweight Logging Library for Android

Today, I would like to introduce a project I’ve been working on these last few weeks: barK. A lightweight logging library for Android and Kotlin Multiplatform projects.

The idea behind barK came to me very recently while working on an SDK. I noticed that neither android.util.Log nor some of the popular logging libraries, were addressing all of my needs at once:

  • To be able to turn off logs for release builds;
  • To call android.util.Log during regular runs, but use print() during unit test runs;
  • To allow for integrating clients to leverage the logging solution, edit it, or mute it entirely.

As such, barK was conceived: A lightweight logging library, written in Kotlin for Android and KMP projects. This library is capable of being turned off easily for release builds, muted by integrating clients at will, and is able to use print() statements during unit test runs enabling maximal debugging capabilities all around.

In this article, I will introduce barK, while focusing on the Android side of the implementation. I will highlight some of the main features that are currently available, and close it off with an example on how to get started logging with the barK library!

Features

Let’s start talking about features. There are three main aspects behind barK that I intend to cover for this article: Tagging, Training & Logging.

Tagging

We’ll begin with tagging. BarK manages log tags in two different ways: Either you (1) allow the default, auto-detection tag feature to take care of tagging, or (2) you set your own global tag for the entire logging strategy.

Tag Auto-Detection

By default, the barK library automatically detects the calling class, and uses such class’ name as the tag for all the logs inside it.

class MainActivity {
    override fun onCreate() {
        super()
        Bark.d("MainActivity onCreate()") // tag: MainActivity
}

class LoggingRepository {
    init {
        Bark.v("Tag is automatically detected") // tag: LoggingRepository
    }
}
Enter fullscreen mode Exit fullscreen mode

Global Tag

On the other hand, setting a global tag on the barK instance will override the tag auto-detection feature.

class MainActivity {
    override fun onCreate() {
        super()
        Bark.tag("MyApp")
        Bark.d("MainActivity onCreate()") // tag: MyApp
}

class LoggingRepository {
    init {
        Bark.v("Tag is automatically detected") // tag: MyApp
    }
}
Enter fullscreen mode Exit fullscreen mode

On the contrary, calling Bark.untag() will revert the global tag utility, and switch back to detecting the calling class’ tag automatically.

fun tag(globalTag: String)
fun untag()
Enter fullscreen mode Exit fullscreen mode

Training

The Bark system requires being ‘trained’ in order to know how to deal with the incoming logging request.

fun train(trainer: Trainer)
fun untrain(trainer: Trainer)
fun releaseAllTrainers()
Enter fullscreen mode Exit fullscreen mode

Bundled Trainers

As such, the barK library provides a series of pre-made Trainer classes to get started.

// Android-specific Trainers
AndroidLogTrainer() // use android.util.Log to logs
AndroidTestTrainer() // use android.util.Log to log, even on test runs
UnitTestTrainer() // use print() to log
ColoredUnitTestTrainer() // use print() to log with colored levels
Enter fullscreen mode Exit fullscreen mode

All trainers come with bothpack: Pack and volume: Level fields, meant to define what pack they belong to (see below) and how best to discriminate between the different types of logs.

interface Trainer {
    val volume: Level
    val pack: Pack
    fun handle(level: Level, tag: String, message: String, throwable: Throwable?)
}
Enter fullscreen mode Exit fullscreen mode

Trainer’s Pack

Trainers in barK all belong to a Pack, and this field is used to distinguish between the different output types of each logging style.

Where, CONSOLE logs to the environment console using print() statements; SYSTEM logs using the platform’s main logging library, such as Android’s android.util.Log, or iOS’s NSLog; FILE denotes writing to a file; finally, CUSTOM is an open type, envisioned to be used for any other kind of Trainer.

enum class Pack { 
    CONSOLE, // Logs to the environment console [print()]
    SYSTEM, // Logs to the platforms logging library [android.util.Log]
    FILE, // Logs to a file writter system
    CUSTOM // Custom loggers that allow for duplicative trainers
}
Enter fullscreen mode Exit fullscreen mode

The Bark system uses the trainers’ Pack field to avoid having duplicative Trainer objects logging to the same output at once. All but the Pack.CUSTOM type, which is capable of having multiple Trainer instances of the same Pack at once inside Bark; this allows for maximum flexibility and customability for integrating clients.

Custom Trainers

Moreover, barK’s Trainer interface can be implemented to create custom trainers.

// Example: Custom trainer for capturing warnings and errors
class CustomErrorTrainer(
    override val volume: Level = Level.WARNING,
) : Trainer {
    override val pack: Pack = Pack.SYSTEM

    override fun handle(level: Level, tag: String, message: String, throwable: Throwable?) {
        if (level.ordinal >= volume.ordinal) {
            // Handle error
        }
    }
}

// Example: Custom trainer to send Slack messages
class SlackTrainer(
    override val volume: Level = Level.INFO,
    private val webhookUrl: String
) : Trainer {
    override val pack: Pack = Pack.CUSTOM

    override fun handle(level: Level, tag: String, message: String, throwable: Throwable?) {
        if (level.ordinal >= volume.ordinal) {
            // Send to Slack
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

These trainers can belong to any Pack, based on what kind of output they envision to log to, and how they get affected by the rule on duplicate trainers.

Furthermore, custom trainers may also update their volume Level, and handle() their incoming log messages as they see fit.

Logging

Finally, and with the tagging and training taken care of, logging ends up being pretty straightforward with the barK library. Similarly to android.util.Log — or your favorite plant-based logging library — logging consists of calling the Bark class with one of its logging functions.

fun v(message: String, throwable: Throwable? = null)
fun d(message: String, throwable: Throwable? = null)
fun i(message: String, throwable: Throwable? = null)
fun w(message: String, throwable: Throwable? = null)
fun e(message: String, throwable: Throwable? = null)
Enter fullscreen mode Exit fullscreen mode

Muzzling

BarK also offers a simple way of ‘muzzling’ (or muting) the library during runtime, by simply calling the muzzle() function.

fun muzzle()
fun umuzzle()
Enter fullscreen mode Exit fullscreen mode

NOTE: While this function is similar to not training your Bark instance, or untraining, muzzling during runtime could be particular useful when dealing with flow containing sensitive data, for example.

Example Usage

With the main barK API now defined, let’s now see how to integrate the barK system into your own application!

Initialization

First, update your Gradle’s dependency resolution repositories block so that it can fetch from JitPack’s Maven integration.

// dependencyResolutionManagement
repositories {
    maven { url = uri("https://jitpack.io") }
}
Enter fullscreen mode Exit fullscreen mode

Next, add the dependency into your app’s Gradle build file (build.gradle.kts or build.gradle) using the appropriate version of the library.

// build.gradle.kts
dependencies {
    ...
    implementation("com.ivangarzab:bark:<version>")
}

// build.gradle
dependencies {
    ...
    implementation 'com.ivangarzab:bark:<version>'
}
Enter fullscreen mode Exit fullscreen mode

You can find the latest releases and its notes in barK’s GitHub repository.

Logging in Android

With the library’s dependencies settled, we’re now ready to start logging inside our Android app!

Simply, train your Bark instance so that it knows it should start working.

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        // DEBUG builds logs from Level.DEBUG and up;
        // optionally, RELEASE builds log Level.WARNING.
        Bark.train(AndroidLogTrainer(
            volume = if (BuildConfig.DEBUG) Level.DEBUG else Level.WARNING
        ))
        Bark.d("barK has been set up successfully")
    }
}
Enter fullscreen mode Exit fullscreen mode

And start barking those logs!

Bark.d("This is our first log!")
Bark.w("Something is not going well...")
Bark.e("We've found an error!")
Enter fullscreen mode Exit fullscreen mode

Regular Android SYSTEM logs using barK

Logging in Android’s Unit Test runs

Similarly, barK can be used to log to the console while running unit tests.

In order to set up logs during unit test runs, make sure to train your Bark instance on the test cases that you want to see logs for.

@Before
fun setup() {
    Bark.releaseAllTrainers() // Release all trainers for a clean run
    Bark.train(
        UnitTestTrainer(volume = Level.DEBUG)
    )
    Bark.d("barK has been set up for unit tests succesfully")
}
Enter fullscreen mode Exit fullscreen mode

NOTE: Logging during test runs will print ALL of your logs; that includes the main code’s logs, as well as anything you add for each particular test case. Thus, giving you maximal visibility over your application’s execution.

Unit Test CONSOLE logs using barK

Afterthought

I have now introduced the barK library, as a lightweight logging solution for Android and KMP projects.

The idea behind barK is not only to make logging easier to client applications, but also for SDK libraries and their integrating clients.

Therefore, I will publish another article diving deeper into SDK usage.

Similarly, barK was also made with console printing in mind for unit test, as well as CI/CD runs. Thus, I shall publish yet another entry focusing on barK’s advantages in unit tests as well.

And lastly, barK is still in development, so full iOS support will have to come at a later time.

For now, let me know what you think of this little passion project of mine in the comments, and feel free to request any features (or fixes!) directly in the GitHub repo.

Thank you all for reading this far 🖤

barK: Because every log deserves a good home. 🐶🏠


👾 My profile 👾 | 🐺 My Website 🐺 | 🔗 All my links 🔗


Resources

Top comments (0)