Loggable typeclass
To represent a value in logs we use a concept of Loggable
(it's a typeclass). It describes how a value of some type
can be logged, both as a string representation in log message and as a component of structured logging. Given an
instance of Loggable
for a type, a value of the type can be converted into the final internal representation
called LoggedValue
and thus logged in a way that you provided.
There are multiple predefined ways to create an instance of Loggable
, many of them can be found
in tofu.logging.Loggable
object:
Loggable.empty
for no-op logging of valueLoggable.show
for usingcats.Show
instance as string representationLoggable.either
for logging either ofA
andB
- provided instances for all primitive types, as well as stdlib's collections and collections from Cats
- java.time.* instances
Of course, you can describe your Loggable
instance yourself by extending existing traits that pre-implement some
functionality:
DictLoggable
for multi-field objectsToStringLoggable
for using.toString
for loggingHideLoggable
to exclude value from logging
Loggable example
import tofu.logging._
import cats.syntax.semigroup._
case class User(name: String, surname: String)
implicit val userLoggable = new DictLoggable[User] {
override def fields[I, V, R, S](a: User, i: I)(implicit r: LogRenderer[I, V, R, S]): R = {
r.addString("name", a.name, i) |+| r.addString("surname", a.surname, i)
}
override def logShow(a: User): String = s"name = ${a.name}, surname = ${a.surname}"
}
Let's take a look at this example.
First, we define a loggable for our User
class as a DictLoggable
which means that we want to log it as a multi-field
object with structure.
Second, we define two methods that describe how User
should be logged:
fields
, that representsUser
as a structure, containing two fields with their respective names and valueslogShow
, that representsUser
as a string in a log message
Loggable derivation
a
Tofu has integration with derevo library. It allows you to easily generate
instances of Loggable[YourClass]
for case classes or ADTs:
import tofu.logging.derivation.loggable
import derevo.derive
@derive(loggable)
case class Query(value: String, size: Int, isTagged: Tagged, tag: Tag)
@derive(loggable)
case class Tagged(b: Option[Boolean])
@derive(loggable)
sealed trait Tag
@derive(loggable)
case class DatabaseTag(db: String) extends Tag
@derive(loggable)
case object InMemoryTag extends Tag
so when logged
Configuring Loggable generation
Tofu has several annotations to configure generation of Loggable
@hidden
import tofu.logging.derivation.{loggable, hidden}
import derevo.derive
@derive(loggable)
case class User(name: String, @hidden() password: String)User("tofu", "pass")
will be logged asUser{name=tofu}
@masked
import tofu.logging.derivation.{loggable, masked, MaskMode}
import derevo.derive
@derive(loggable)
case class Card(@masked(MaskMode.Erase) expirationDate: String, @masked(MaskMode.ForLength(4, 12)) cardNumber: String, @masked() owner: String)Card(737, "1244345632322311", "TOFU PETROV")
will be logged asCard{expirationDate=...,cardNumber=1244########2311,owner=**** ******}
@unembed
import tofu.logging.derivation.{loggable, unembed}
import derevo.derive
@derive(loggable)
case class Scientist(@unembed owner: Person, age: Option[Int])
@derive(loggable)
case class Person(name: String)Room(Person("Karl Sagan"), None)
will be logged asRoom{name=Karl Sagan, age=None}