ExceptionWithLogFields

open class ExceptionWithLogFields(val message: String?, logFields: List<LogField> = emptyList(), val cause: Throwable? = null) : RuntimeException, HasLogFields(source)

Base exception class to allow you to attach log fields to exceptions. When passing a cause exception to one of the methods on Logger, it will check if the given exception is an instance of this class, and if it is, these fields will be added to the log.

Use the field/rawJsonField functions to construct log fields.

The exception also includes any log fields from withLoggingContext, from the scope in which the exception is constructed. This way, we don't lose any logging context if the exception escapes the context it was thrown from. If you don't want this behavior, you can create a custom exception and implement the HasLogFields interface.

This class is useful when you are throwing an exception from somewhere down in the stack, but do logging further up the stack, and you have structured data that you want to attach to the exception log. In this case, one may typically resort to string concatenation, but this class allows you to have the benefits of structured logging for exceptions as well.

Example

import dev.hermannm.devlog.ExceptionWithLogFields
import dev.hermannm.devlog.field
import dev.hermannm.devlog.getLogger

private val log = getLogger()

fun example(event: OrderUpdateEvent) {
try {
processOrderUpdate(event)
} catch (e: Exception) {
log.error(e) { "Failed to process order update event" }
}
}

fun processOrderUpdate(event: OrderUpdateEvent) {
withLoggingContext(field("event", event)) {
val order = getOrder(event.orderId)

if (!order.canBeUpdated()) {
throw ExceptionWithLogFields(
"Received update event for finalized order",
field("order", order),
)
}
}
}

The log.error would then give the following log output (using logstash-logback-encoder), with both the order field from the exception and the event field from the logging context:

{
"message": "Failed to process order update event",
"stack_trace": "...ExceptionWithLogFields: Received update event for finalized order...",
"order": { ... },
"event": { ... },
// ...timestamp etc.
}

Constructors

ExceptionWithLogFields provides 4 constructor overloads:

  • (message: String?, logFields: List<LogField>, cause: Throwable?)

    • Primary constructor taking an exception message, a list of log fields and an optional cause exception

  • (message: String?, vararg logFields: LogField, cause: Throwable?)

    • Takes log fields as varargs, so you don't have to wrap them in a list

    • To pass cause, use a named argument

  • (logFields: List<LogField>, cause: Throwable?)

    • Defaults message to cause.message. This lets you:

      • Wrap a cause exception with log fields, and use the cause exception's message

      • Extend ExceptionWithLogFields and override message, without having to pass it through the constructor

  • (vararg logFields: LogField, cause: Throwable?)

    • Combines the two previous constructors, to let you extend ExceptionWithLogFields and override message while also passing log fields as varargs

Constructors

Link copied to clipboard
constructor(message: String?, vararg logFields: LogField, cause: Throwable? = null)
constructor(logFields: List<LogField> = emptyList(), cause: Throwable? = null)
constructor(vararg logFields: LogField, cause: Throwable? = null)
constructor(message: String?, logFields: List<LogField> = emptyList(), cause: Throwable? = null)

Properties

Link copied to clipboard
open override val cause: Throwable? = null

The cause of the exception. If you're throwing this exception after catching another, you should include the original exception here.

Link copied to clipboard
override val logFields: List<LogField>

Will be attached to the log when passed through cause to one of Logger's methods.

Link copied to clipboard
open override val message: String?

The exception message.