Fluent API

A fluent builder interface is an API that relies heavily on method chaining to build up an expression.

The fluent API has an immediate advantage in that there’s less overloading in the API, and there’s more room to chain.

Accessing Fluent Logger

The easiest way of getting a fluent logger is to use the LoggerFactory and then call the fluent method:

import com.tersesystems.blindsight.fluent.FluentLogger

val logger                     = com.tersesystems.blindsight.LoggerFactory.getLogger(getClass)
val fluentLogger: FluentLogger = logger.fluent

The fluent logger builds up the logging statement by calling marker, argument, message, cause, and then finally log() or logWithPlaceholders() to execute the statement. You may call each of these methods repeatedly, and they build up markers, arguments, or a concatenated message (note that exceptions are, well, an exception). You may call them in any order. If you call log() then the statement is executed exactly as written. If you call logWithPlaceholders() then the number of arguments is counted and “{}” format placeholders are appended to the statement’s message so that all arguments are visible.

fluentLogger.info
  .marker(someMarker)
  .message("some message {}")
  .argument(args)
  .message("exception {}")
  .cause(exception)
  .log()

Markers

You can log with a marker alone and then log:

import net.logstash.logback.marker.{Markers => LogstashMarkers}
import org.slf4j.Marker
val someMarker: Marker = LogstashMarkers.append("user", "will")

fluentLogger.info.marker(someMarker).log()

This will write out an empty string as the message, and a logstash marker.

All markers are in a Markers instance internally, but are not accessible from the builder.

Message

A message is the part of the statement that is written out as a string.

fluentLogger.message("some message").log()

Messages can be concatenated together by calling message repeatedly:

fluentLogger.message("hello").message("world").log()

You can pass in your own custom instances, of course.

case class Person(name: String)

implicit val personToMessage: ToMessage[Person] = ToMessage { person =>
  Message(s"My name is ${person.name}")
}
fluentLogger.message(person).log()

Arguments

Arguments can be added with argument. You do not need to define a message, but you should call logWithPlaceholders in that case so the argument is visible.

fluentLogger.argument(someArgument).logWithPlaceholders()

Custom formatting is done with ToArgument.

case class Person(name: String)

implicit val personToArgument: ToArgument[Person] = ToArgument { person =>
  Argument(bobj("person" -> person.name))
}

val person = Person("Felix")
fluentLogger.message("I was talking to {}").argument(person).log()

Exceptions

Exceptions are added using cause. Exceptions are of type Throwable, just like the SLF4J API.

fluentLogger.message("the exception was {}").cause(someException).log()

Technically, an exception is the last argument in a logging statement. Exception does not compose, and if you call exception repeatedly, the last exception will be set.

The source code for this page can be found here.