Skip to main content

Sending log events

Once you have a logger, you can call functions on it to send log events. The base function is log():

    logger.log(Level.INFO, "Application started")
info

All the information here applies to both the coroutine logger Klogger and the non-coroutine logger NoCoLogger.

Utility functions

These utility functions are a convenient way to send logs. They call log() with the appropriate level:

  • trace()
  • debug()
  • info()
  • warn()
  • error()
  • fatal()

Logging patterns

Klogging offers a range of patterns for different logging scenarios. The patterns shown here apply to log() and all utility functions.

String message

The simplest pattern is to send a string, for example:

    logger.info("Processing started")
// meaningful code
val response = callOtherService()
logger.info("User response was ${response.text}")

Message template

Message templates provide a convenient way to both create meaningful messages and to create structured log events:

    logger.info("User {userId} signed in", userId)

The resulting log event contains both:

  • the message populated with the value of userId; and
  • an item called userId with the same value.

For example, if userId has the value wonti321 then:

  • the message becomes User wonti321 signed in; and
  • the log event contains "userId": "wonti321".

The log event displayed in Splunk may look like this:

Example of structured event message in Splunk

Immediate context items

You can specify a map of items to add to the context of a single log event.

info

Immediate context items are useful with NoCoLogger instances, where there is no coroutine context available.

A simple example:

logger.info("Retrieved values from services", mapOf(
"serviceOneCount" to serviceOneResult.count,
"serviceTwoCount" to serviceTwoResult.count,
))

The resulting log event may look like something like this:

{
"@t": "2024-02-05T07:45:47.837117Z",
"@l": "INFO",
"@m": "Retrieved values from services",
"host": "589ef8fa",
"logger": "com.example.ServiceCombiner",
"context": "main",
"serviceOneCount": 9,
"serviceTwoCount": 17
}

Exception

To log exception information, include the exception object as the first argument in the function call:

    try {
// This might throw an exception
snurgle(id, agger)
} catch (ex: SnurgleException) {
logger.warn(ex, "Exception calling snurgle with id={id} and agger={agger}", id, agger)
}

The resulting log event will include any stack trace included in the exception.

Minimum level check

Every logger has a minimum level set by configuration, below which log events are not sent. See Log levels and checking for more details.

Klogging provides functions to check minimum levels to call before calling a logging function if the values to be logged are expensive to obtain.

Here is one example, where DEBUG logging might not be enabled for this logger in all environments:

    if (logger.isDebugEnabled()) {
val complex = calculateSpecialValueForDebugging()
val other = extractOtherValueFromSomewhereElse()
logger.debug("Calculated {complex} and {other}", complex, other)
}

Likewise, there are functions isTraceEnabled(), isInfoEnabled() etc.

Kotlin lambda

The idiomatic Kotlin way to send a log event that might be expensive is to use a lambda. The lambda is only called if the minimum level check evaluates true.

It is simple when logging a string message:

    logger.trace { "Starting run with ID=$runId" }

Here, a log event is only sent if logger.isTraceEnabled() evaluates true.

The lambda can only return a single value, so if you want to use a message template, you can call the e() function as the return value of the lambda:

    logger.debug {
val complex = calculateSpecialValueForDebugging()
val other = extractOtherValueFromSomewhereElse()
e("Calculated {complex} and {other}", complex, other)
}

Lambda and exception

Combine a lambda and an exception like this:

    logger.warn(ex) { "Exception processing payment" }