Learn more about Streama© – the foundational technology behind our stateful streaming data platform. Learn More

Best Practices for Logging in Kotlin

  • Luke Nolan
  • December 6, 2020
Kotlin logging best practices

If you’re reading this, you have probably been convinced that taking on Kotlin for your mobile application is the most sensible choice. Now that you’ve come to this decision, it’s imperative to know what you need to do to stay on top of your monitoring and logging. Like with any application or system, they are essential, cornerstone qualities of any successful project. 

It is a common myth that mobile applications don’t require logging frameworks, or any logging for that matter – absolutely false! This unfounded myth is only the result of a lack of suitable frameworks and resources to do it effectively. Logging is absolutely an essential part of any stack, even when developing a mobile application.

For Android development, the android.util.log is the default mechanism to do your logging. It includes helpful features like severity levels and regex search capabilities. Further to this, if we recall that Kotlin and Java are interoperable, this would mean the classic Java logging frameworks can be suitable. You can read more of my thoughts on best practices logging in Java here.

The frameworks discussed in that article can also be used in any Kotlin project with some minor tweaking.

The standard Java logging frameworks prove to be limited in the context of Mobile App Development in Kotlin. This article will go over some of the main Kotlin logging libraries, and we will be delving into the reasons why a specialized Kotlin logger is advantageous of a simple Java implementation of a framework like SLF4J or LOG4J.

Introducing kotlin-logging framework for Kotlin

Kotlin-logging is a “lightweight logging framework for Kotlin, written 100% in Kotlin”. The github page has a more detailed reference. The kotlin-logging library was built by wrapping SLF4J with custom Kotlin extensions. Essentially, it is straight to the point and allows you to make highly replicable statements that are easy to define and use to make log method calls.

Simple kotlin-logging implementation

Without further ado, let’s jump into some practical examples of how kotlin-logging works. It is very straightforward and can easily be inserted into an existing Kotlin project.

If you are using Maven, keep your project structure and insert our kotlin-logging dependency to your pom.xml file:

<dependency>
  <groupId>io.github.microutils</groupId>
  <artifactId>kotlin-logging</artifactId>
  <version>2.0.3</version>
</dependency>

NOTE: You need to have SLF4J as a dependency as well.

Add it to the pom.xml file:

   <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.29</version>
    </dependency>

So, as it stands we have two dependencies: The SLF4J API and the Kotlin-logging framework.

The logger is actually extremely simple to use. The safest way, according to the documentation, is to invoke the logger as follows:

private val logger = KotlinLogging.logger {}

After the logger is obtained, it can be used in multiple different ways.

Below are a few examples:

  • A simple informational log statement notifying of a successful API call:
logger.info("Successful call to the /get route")
  • A log statement that is set to only show if the “debug” severity is enabled, and evaluates the string $message if and only if that is the case:
logger.debug{"Only show if debug flag: $message"}
  • An effective way to log errors, with interpolated variables:
logger.error(exception) { "There was an error: $exception" }

As we can see for these few use cases, the kotlin-logger is concise and effective. For everything ranging from informational logs to interpolated error logs, things are kept simple and intuitive.

Mapped Diagnostic Context (or, MDC)

When dealing with a more complex mobile application, one containing user information for example or any with valuable meta information such as a sessionID, then simple logs are not enough to fulfill our needs. It is important to enrich our logs with useful information. Luckily, kotlin-logging also includes ways to set MDC properties that can be used later on by the Logger().

An example of this can be seen using the following:

withLoggingContext("userId" to userId) {
  doSomething()
}

This fragment taken from an example in the documentation shows exactly how MDC properties can be set and later utilized by the invoked logger. In the above, we would be populating the MDC meta information with a pair of variables of our choice. This is especially important if we are dealing with usernames and sessionIDs. In the above example, we would be setting the userId then using the lambda function to retrieve them.

Combining the simple logging calls we saw above with the MDC features allows you to fully enrich your logged output, which can save you a lot of time and headache if you are spending ages digging deep to find the root cause of a bug.

Pros and Cons of kotlin-logger

Kotlin-logger is simple to get started with, easily fitting into a new or existing Kotlin project. From simple use cases all the way to storing metadata like sessionID and usernames, kotlin-logger can get the job done.

Pro – Enriched logs 

Another benefit is having a logger without the class name itself in it. That would be a requirement if we were dealing with a Java logging framework like LOG4J. We are able to have a slightly cleaner codebase by going with the pure Kotlin approach.

Con – Small Community

The one drawback I would see to kotlin-logger is the lack of maintenance support it has on GitHub. This criticism might be a reflection on the fact that Kotlin is still not as popular as some of its counterparts, rather than the lack of popularity of the library itself. 

In fact, this seems to be the only well-structured, pure Kotlin logging library out there. It is easy to see why there are not many pure Kotlin logging libraries out there given its 100% interoperability with Java as previously mentioned. 

TIP: As of the writing of this article, kotlin-logger is safe for use with a Kotlin version dating up to the 20th of September 2020.

Common Pitfalls and Best Practices

So now that we have established how we can log in Kotlin, including logging of precious meta information, we can discuss some of the common pitfalls that happen when logging in Kotlin and how we can turn these into best practices.

Lack of Organization/Too Many Logs

If we are dealing with a big application, with hundreds of user requests happening a minute, not every log is important. If you find yourself with a very flooded logfile or console of logs, it might be time to either cut down the amount of log statements or split them into dedicated log files. Either way, organizing where your logs get sent to is very important for troubleshooting or optimization scenarios.

Also, not everything is important. Some things simply do not need to be logged. Overusing log statements is not only bad practice, but can also be potentially harmful to the running of your application, given the response time adding up.

Lack of Configurable Logging Levels

The use of severity levels, as featured in kotlin-logging for example, can be a very powerful tool. Flagging certain things as debug only, or some errors as fatal is much more useful than a huge mass of logs that are all labelled with the same level. If you have everything labelled as an error, the log file/stream can quickly get flooded with false positives.

The conditional approach to these severity levels can save time, and not to mention storage space on the lack of irrelevant logs.

Consistent Formatting

When logging in Kotlin, or any language for that matter, consistency is key within your logs. You want the format to be generally identical apart from key fields. This allows for easy categorization of your data, which will make analysis much easier in the long run as the data is collected.

An example of a logger with a custom formatted output could be:

TIMESTAMP | Severity Level | Function Name | Occurrence

If all of your logged outputs were of the form above, it would make it very easy to sort and filter the data.

Conclusion

The article has broken down why Kotlin is an increasingly popular choice for developers, as well as some of the best practices when it comes down to logging in the context of a Mobile Application. It should be clear as to how kotlin-logging works, how it can be added to a new or existing Kotlin project, and how we can extract valuable meta information into our logs through MDC.

We also looked at why severity levels and organized logs are important for a successful application, and how consistent formatting can be advantageous when it comes down to analysing logs, as it allows you to easily sort and search your logs.

Stateful streaming analytics for observability data