Exception With Log Fields
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
tocause.message
. This lets you:Wrap a cause exception with log fields, and use the cause exception's message
Extend
ExceptionWithLogFields
and overridemessage
, without having to pass it through the constructor(vararg logFields: LogField, cause: Throwable?)
Combines the two previous constructors, to let you extend
ExceptionWithLogFields
and overridemessage
while also passing log fields as varargs