Semantic API
A semantic logging API is strongly typed and does not have the same construction oriented approach as the fluent API. Instead, the type of the instance is presumed to have a mapping directly to the attributes being logged.
The semantic API works against Statement
directly. The application is expected to handle the type class mapping to Statement
.
Here is an example:
sourceobject SemanticMain {
sealed trait UserEvent {
def name: String
}
final case class UserLoggedInEvent(name: String, ipAddr: String) extends UserEvent
object UserLoggedInEvent {
implicit val toStatement: ToStatement[UserLoggedInEvent] = ToStatement { instance =>
import com.tersesystems.blindsight.DSL._
Statement()
.withMessage("UserLoggedInEvent message with args {}")
.withArguments(
Arguments(
bobj(
"user-logged-out-event" ->
("name" -> instance.name) ~
("ipAddr" -> instance.ipAddr)
)
)
)
}
}
final case class UserLoggedOutEvent(name: String, reason: String) extends UserEvent
object UserLoggedOutEvent {
implicit val toStatement: ToStatement[UserLoggedOutEvent] = ToStatement { instance =>
import com.tersesystems.blindsight.DSL._
Statement()
.withMessage("UserLoggedOutEvent message with args {}")
.withArguments(
Arguments(
bobj(
"user-logged-out-event" ->
("name" -> instance.name) ~
("reason" -> instance.reason)
)
)
)
}
}
final case class UserIsUpLateEvent(name: String, excuse: String) extends UserEvent
object UserIsUpLateEvent {
implicit val toStatement: ToStatement[UserIsUpLateEvent] = ToStatement { instance =>
import com.tersesystems.blindsight.DSL._
Statement()
.withMessage(instance.toString)
.withArguments(
Arguments(
bobj(
"user-is-up-late-event" ->
("name" -> instance.name) ~
("excuse" -> instance.excuse)
)
)
)
}
}
def main(args: Array[String]): Unit = {
val userEventLogger: SemanticLogger[UserEvent] =
LoggerFactory.getLogger(getClass).semantic[UserEvent]
userEventLogger.info(UserLoggedInEvent("steve", "127.0.0.1"))
userEventLogger.info(UserLoggedOutEvent("steve", "timeout"))
userEventLogger.warn.when(LocalTime.now().isAfter(LocalTime.of(23, 0))) { log =>
log(UserIsUpLateEvent("will", "someone is WRONG on the internet"))
}
val onlyLoggedInEventLogger: SemanticLogger[UserLoggedInEvent] =
userEventLogger.refine[UserLoggedInEvent]
onlyLoggedInEventLogger.info(UserLoggedInEvent("mike", "10.0.0.1"))
}
}
in plain text:
FgEdhil2znw6O0Qbm7EAAA 2020-04-05T23:09:08.359+0000 [INFO ] example.semantic.SemanticMain$ in main - UserLoggedInEvent(steve,127.0.0.1)
FgEdhil2zsg6O0Qbm7EAAA 2020-04-05T23:09:08.435+0000 [INFO ] example.semantic.SemanticMain$ in main - UserLoggedOutEvent(steve,timeout)
FgEdhil2zsk6O0Qbm7EAAA 2020-04-05T23:09:08.436+0000 [INFO ] example.semantic.SemanticMain$ in main - UserLoggedInEvent(mike,10.0.0.1)
and in JSON:
{
"id": "FgEdhil2znw6O0Qbm7EAAA",
"relative_ns": -298700,
"tse_ms": 1586128148359,
"start_ms": null,
"@timestamp": "2020-04-05T23:09:08.359Z",
"@version": "1",
"message": "UserLoggedInEvent(steve,127.0.0.1)",
"logger_name": "example.semantic.SemanticMain$",
"thread_name": "main",
"level": "INFO",
"level_value": 20000,
"name": "steve",
"ipAddr": "127.0.0.1"
}
Refinement Types
Semantic Logging works very well with refinement types.
For example, you can add compile time limitations on the kinds of messages that are passed in:
sourceobject RefinedMain {
import com.tersesystems.blindsight._
import com.tersesystems.blindsight.semantic._
import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.collection.NonEmpty
import eu.timepit.refined.string._
implicit def stringToStatement[R]: ToStatement[Refined[String, R]] =
ToStatement { str =>
Statement().withMessage(str.value)
}
def main(args: Array[String]): Unit = {
val logger = LoggerFactory.getLogger
val notEmptyLogger: SemanticLogger[String Refined NonEmpty] =
logger.semantic[String Refined NonEmpty]
notEmptyLogger.info(refineMV[NonEmpty]("this is a statement"))
// will not compile
//notEmptyLogger.info(refineMV(""))
val urlLogger: SemanticLogger[String Refined Url] = logger.semantic[String Refined Url]
urlLogger.info(refineMV[Url]("http://google.com"))
// will not compile
//urlLogger.info(refineMV("this is a statement"))
}
}
The source code for this page can be found here.