SLF4J API
The default Logger API has the same logger methods and (roughly) the type signature as SLF4J.
The biggest difference is that methods take a type class instance of Markers
and Arguments
, if you have them defined.
Markers
Markers work the same way, but must be an instance of Markers
.
val marker = MarkerFactory.getDetachedMarker("foo")
logger.info(Markers(marker), "hello")
Using the DSL with marker enrichment is encouraged here, as it can make marker specification much easier:
import com.tersesystems.blindsight._
import com.tersesystems.blindsight.MarkersEnrichment._
logger.info(bobj("markerKey" -> "markerValue").asMarkers, "marker and argument")
Generally, you should not need to use markers explicitly in messages, as they can be used with context more effectively.
Arguments
Arguments in Blindsight are type checked, in constrast to the SLF4J API, which takes an Any
. There must be a type class instance of ToArgument
in scope. This is to prevent awkward toString
matches on object instances, and ensure that structured logging is taking place.
// Will not compile, because no ToArgument[SomeRandomObject] is found in implicit scope!
logger.info("one argument {}", new SomeRandomObject())
Default ToArgument
are determined for the primitives (String
, Int
, etc):
logger.info("one argument {}", 42) // works, because default
If you have more than two arguments, you will need to wrap them so they are provided as a single Arguments
instance:
logger.info("arg {}, arg {}, arg {}", Arguments(1, "2", false))
comes out as:
FgEddUhGnXE6O0Qbm7EAAA 17:36:55.142 [INFO ] e.s.Slf4jMain$ - arg 1, arg 2, arg false
Exceptions come after arguments, and are not included in the list. For example:
val e = new Exception("something is horribly wrong")
logger.error("this is an error with argument {}", Arguments("a" -> "b"), e)
Lazy Blocks
There is a block oriented API available. This is useful for diagnostic logging and conditional logging to avoid unnecessary memory allocation. The block is only executed if the conditions for logging have been met, and returns a handle to the method itself.
// block only executed if logger is DEBUG or TRACE level.
logger.debug { debug =>
val debugInfo = ...
debug(st"I am a debugging statement with lots of extra $debugInfo")
}
Unchecked API
An unchecked API is available that does not use type class inference at all, and looks just like SLF4J with the additions of conditions and markers.
This is easier to use, but since Any
cannot be type checked, it can output information you don’t expect. For example, it could output credit card information because calling toString
exposes the case class data:
val logger = LoggerFactory.getLogger
val unchecked: SLF4JLogger[UncheckedSLF4JMethod] = logger.unchecked
// Uses Any, renders credit card as "toString"
val creditCard = CreditCard("4111111111111")
// case class tostring renders CC number, which is unsafe!
unchecked.info("this is risky unchecked {}", creditCard)
Where there are several arguments, the Arguments
must be used rather than Seq
or Array
, as it is very awkward to use Seq
and Array
with varadic input:
unchecked.info("this is risky unchecked {}, {}, {}", Arguments("1", 2, true))
In the unchecked API, you can set -Dblindsight.anywarn=true
as a system property, and output will be written to System.out.error
when calls to Any
are made.