Log - Cats
Log - Cats (WIP)
Log is an algebra to log F[A], F[Option[A]], F[Either[A, B]], OptionT[F, A] and EitherT[F, A, B]. So Log provides abstraction of logging operations, and there can multiple interpretations if required.
More precisely, it requires Fx from Effectie and Monad from Cats. So it can be used for whatever effect you want as long as there's an interpreter for Fx of the effect.
Log String
LoggerF is mainly for F[_] but let's start with more simple logging case that is logging String.
- With syntax
- Without syntax
logS(String)(debug) // F[String]
logS(String)(info) // F[String]
logS(String)(warn) // F[String]
logS(String)(error) // F[String]
// or
String.logS(debug) // F[String]
String.logS(info) // F[String]
String.logS(warn) // F[String]
String.logS(error) // F[String]
If you don't need to re-use the String value,
import loggerf.syntax.all._
logS_(String)(debug) // F[Unit]
logS_(String)(info) // F[Unit]
logS_(String)(warn) // F[Unit]
logS_(String)(error) // F[Unit]
// or
String.logS_(debug) // F[Unit]
String.logS_(info) // F[Unit]
String.logS_(warn) // F[Unit]
String.logS_(error) // F[Unit]
import loggerf.core._
Log[F].logS(String)(debug) // F[String]
Log[F].logS(String)(info) // F[String]
Log[F].logS(String)(warn) // F[String]
Log[F].logS(String)(error) // F[String]
If you don't need to re-use the String value,
Log[F].logS_(String)(debug) // F[Unit]
Log[F].logS_(String)(info) // F[Unit]
Log[F].logS_(String)(warn) // F[Unit]
Log[F].logS_(String)(error) // F[Unit]
Example
- With syntax
- Without syntax
import cats._
import cats.syntax.all._
import effectie.core._
import effectie.syntax.all._
import loggerf.core._
import loggerf.syntax.all._
def hello[F[_]: Fx: Log: Monad](name: String): F[Unit] = for {
greeting <- logS(s"[1] Hello $name")(info) // F[String]
_ <- effectOf(println(greeting)) // F[Unit]
// Or
greeting2 <- s"[2] Hello $name".logS(info) // F[String]
_ <- effectOf(println(greeting2)) // F[Unit]
} yield ()
import cats.effect._
import effectie.instances.ce2.fx._
import loggerf.logger._
implicit val canLog: CanLog = Slf4JLogger.slf4JCanLog("test-logger")
// canLog: CanLog = loggerf.logger.Slf4JLogger@27dfdc78
hello[IO]("Kevin").unsafeRunSync()
// [1] Hello Kevin
// [2] Hello Kevin
22:52:36.970 [Thread-64] INFO test-logger - [1] Hello Kevin
22:52:36.977 [Thread-64] INFO test-logger - [2] Hello Kevin
If you don't need to re-use the String value,
import cats._
import cats.syntax.all._
import effectie.core._
import effectie.syntax.all._
import loggerf.core._
import loggerf.syntax.all._
def hello[F[_]: Fx: Log: Monad](name: String): F[Unit] = for {
_ <- logS_(s"[1] The name is $name")(info) // F[Unit]
// Or
_ <- s"[2] The name is $name".logS_(info) // F[Unit]
_ <- effectOf(println(s"Hello $name")) // F[Unit]
} yield ()
import cats.effect._
import effectie.instances.ce2.fx._
import loggerf.logger._
implicit val canLog: CanLog = Slf4JLogger.slf4JCanLog("test-logger")
// canLog: CanLog = loggerf.logger.Slf4JLogger@515b2a5d
hello[IO]("Kevin").unsafeRunSync()
// Hello Kevin
22:53:09.165 [Thread-66] INFO test-logger - [1] The name is Kevin
22:53:09.166 [Thread-66] INFO test-logger - [2] The name is Kevin
import cats._
import cats.syntax.all._
import effectie.core._
import effectie.syntax.all._
import loggerf.core._
import loggerf.syntax.all._
def hello[F[_]: Fx: Log: Monad](name: String): F[Unit] = for {
greeting <- Log[F].logS(s"Hello $name")(info) // F[String]
_ <- effectOf(println(greeting)) // F[Unit]
} yield ()
import cats.effect._
import effectie.instances.ce2.fx._
import loggerf.logger._
implicit val canLog: CanLog = Slf4JLogger.slf4JCanLog("test-logger")
// canLog: CanLog = loggerf.logger.Slf4JLogger@50126e24
hello[IO]("Kevin").unsafeRunSync()
// Hello Kevin
02:07:09.298 [Thread-67] INFO test-logger - Hello Kevin
If you don't need to re-use the String value,
import cats._
import cats.syntax.all._
import effectie.core._
import effectie.syntax.all._
import loggerf.core._
import loggerf.syntax.all._
def hello[F[_]: Fx: Log: Monad](name: String): F[Unit] = for {
_ <- logS_(s"The name is $name")(info) // F[Unit]
_ <- effectOf(println(s"Hello $name")) // F[Unit]
} yield ()
import cats.effect._
import effectie.instances.ce2.fx._
import loggerf.logger._
implicit val canLog: CanLog = Slf4JLogger.slf4JCanLog("test-logger")
// canLog: CanLog = loggerf.logger.Slf4JLogger@d2f6ccb
hello[IO]("Kevin").unsafeRunSync()
// Hello Kevin
02:15:41.367 [Thread-74] INFO test-logger - The name is Kevin
Log F[A]
Log[F].log(F[A])(A => LogMessage) // F[A]
or with loggerf.syntax
F[A].log(A => LogMessage) // F[A]
A given F[A], you can simply log A with log.
- With Syntax
- Without Syntax
import loggerf.syntax.all._
val fa: F[A] = ...
fa.log(a => debug(s"Some meesage: $a")) // F[A]
fa.log(a => info(s"Some meesage: $a")) // F[A]
fa.log(a => warn(s"Some meesage: $a")) // F[A]
fa.log(a => error(s"Some meesage: $a")) // F[A]
// OR
log(fa)(a => debug(s"Some meesage: $a")) // F[A]
log(fa)(a => info(s"Some meesage: $a")) // F[A]
log(fa)(a => warn(s"Some meesage: $a")) // F[A]
log(fa)(a => error(s"Some meesage: $a")) // F[A]
import effectie.core._
import loggerf.core._
import loggerf.syntax.all._
def count[F[_]: Fx: Log](): F[Count] =
counter.currentCount() // F[Count]
.log(count => info(s"Current count: $count")) // F[Count]
val fa: F[A] = ...
Log[F].log(fa)(a => debug(s"Some meesage: $a")) // F[A]
Log[F].log(fa)(a => info(s"Some meesage: $a")) // F[A]
Log[F].log(fa)(a => warn(s"Some meesage: $a")) // F[A]
Log[F].log(fa)(a => error(s"Some meesage: $a")) // F[A]
import effectie.core._
import loggerf.core._
import loggerf.syntax.all._
def count[F[_]: Fx: Log](): F[Count] =
Log[F].log(counter.currentCount())(count => info(s"Current count: $count"))
Example
import cats._
import cats.syntax.all._
import cats.effect._
import effectie.core._
import effectie.syntax.all._
import loggerf.core._
import loggerf.syntax.all._
import loggerf.logger._
def hello[F[_]: Functor: Fx: Log](name: String): F[Unit] =
s"Hello $name".logS(debug).map(println(_))
object MyApp extends IOApp {
implicit val canLog: CanLog = Slf4JLogger.slf4JCanLog("MyApp")
import effectie.instances.ce2.fx._
def run(args: List[String]): IO[ExitCode] = for {
_ <- hello[IO]("World")
_ <- hello[IO]("Kevin")
} yield ExitCode.Success
}
23:34:25.021 [ioapp-compute-1] DEBUG MyApp - Hello World
Hello World
23:34:25.022 [ioapp-compute-1] DEBUG MyApp - Hello Kevin
Hello Kevin
trait Named[A] {
def name(a: A): String
}
object Named {
def apply[A: Named]: Named[A] = implicitly[Named[A]]
}
final case class GivenName(givenName: String) extends AnyVal
final case class Surname(surname: String) extends AnyVal
final case class Person(givenName: GivenName, surname: Surname)
object Person {
implicit val namedPerson: Named[Person] =
person => s"${person.givenName.givenName} ${person.surname.surname}"
}
import cats._
import cats.syntax.all._
import cats.effect._
import effectie.core._
import effectie.syntax.all._
import loggerf.core._
import loggerf.syntax.all._
import loggerf.logger._
trait Greeting[F[_]] {
def greet[A: Named](a: A): F[String]
}
object Greeting {
def apply[F[_] : Greeting]: Greeting[F] = implicitly[Greeting[F]]
implicit def hello[F[_]: Fx: Monad: Log]: Greeting[F] =
new Greeting[F] {
def greet[A: Named](a: A): F[String] = for {
name <- effectOf(Named[A].name(a)).log(x => info(s"The name is $x"))
greeting <- pureOf(s"Hello $name").log(greet => debug(s"Greeting: $greet"))
} yield greeting
}
}
object MyApp extends IOApp {
implicit val canLog: CanLog = Slf4JLogger.slf4JCanLog("MyApp")
import effectie.instances.ce2.fx._
def run(args: List[String]): IO[ExitCode] = for {
greetingMessage <- Greeting[IO].greet(Person(GivenName("Kevin"), Surname("Lee")))
_ <- ConsoleFx[IO].putStrLn(greetingMessage)
} yield ExitCode.Success
}
23:04:56.272 [ioapp-compute-0] INFO MyApp - The name is Kevin Lee
23:04:56.273 [ioapp-compute-0] DEBUG MyApp - Greeting: Hello Kevin Lee
Hello Kevin Lee
Log F[Option[A]]
Log[F].log(
F[Option[A]]
)(
ifEmpty: => LogMessage with MaybeIgnorable,
toLeveledMessage: A => LogMessage with MaybeIgnorable
)
A given F[Option[A]], you can simply log Some(A) or None with log.
Example
import cats._
import cats.syntax.all._
import cats.effect._
import effectie.core._
import effectie.syntax.all._
import loggerf.core._
import loggerf.syntax.all._
import loggerf.logger._
def greeting[F[_]: Fx](name: String): F[String] =
pureOf(s"Hello $name")
def hello[F[_]: Monad: Fx: Log](maybeName: Option[String]): F[Unit] =
for {
name <- pureOf(maybeName).log(
warn("No name given"),
name => info(s"Name: $name")
)
message <- name.traverse(greeting[F]).log(ignore, msg => info(s"Message: $msg"))
_ <- effectOf(message.foreach(msg => println(msg)))
} yield ()
implicit val canLog: CanLog = Slf4JLogger.slf4JCanLog("MyApp- F[Option[A]]")
// canLog: CanLog = loggerf.logger.Slf4JLogger@106f4b01
import effectie.instances.ce2.fx._
def run(): IO[Unit] = for {
_ <- hello[IO](none)
_ <- hello[IO]("Kevin".some)
} yield ()
run().unsafeRunSync()
// Hello Kevin
20:09:43.117 [Thread-31] WARN MyApp- F[Option[A]] - No name given
20:09:43.133 [Thread-31] INFO MyApp- F[Option[A]] - Name: Kevin
20:09:43.133 [Thread-31] INFO MyApp- F[Option[A]] - Message: Hello Kevin
Log F[Either[A, B]]
Log[F].log(
F[Either[A, B]]
)(
leftToMessage: A => LeveledMessage with MaybeIgnorable,
rightToMessage: B => LeveledMessage with MaybeIgnorable
)
A given F[Either[A, B]], you can simply log Left(A) or Right(B) with log.
Example
import cats._
import cats.syntax.all._
import cats.effect._
import effectie.core._
import effectie.syntax.all._
import loggerf.core._
import loggerf.syntax.all._
import loggerf.logger._
def foo[F[_]: Fx](a: Int): F[Int] =
pureOf(a * 2)
def divide[F[_]: Fx: CanHandleError](a: Int, b: Int): F[Either[String, Int]] =
effectOf((a / b).asRight[String])
.handleNonFatal{ err =>
err.getMessage.asLeft[Int]
}
def calculate[F[_]: Monad: Fx: CanHandleError: Log](n: Int): F[Unit] =
for {
a <- foo(n).log(
n => info(s"n: ${n.toString}")
)
result <- divide(1000, a).log(
err => error(s"Error: $err"),
r => info(s"Result: ${r.toString}")
)
_ <- effectOf(println(result.fold(err => s"Error: $err", r => s"1000 / ${a.toString} = ${r.toString}")))
} yield ()
implicit val canLog: CanLog = Slf4JLogger.slf4JCanLog("MyApp - F[Either[A, B]]")
// canLog: CanLog = loggerf.logger.Slf4JLogger@18ee9293
import effectie.instances.ce2.fx._
def run(): IO[Unit] = for {
_ <- calculate[IO](5)
_ <- calculate[IO](0)
} yield ()
run().unsafeRunSync()
// 1000 / 10 = 100
// Error: / by zero
20:20:05.588 [Thread-47] INFO MyApp - F[Either[A, B]] - n: 10
20:20:05.593 [Thread-47] INFO MyApp - F[Either[A, B]] - Result: 100
20:20:05.595 [Thread-47] INFO MyApp - F[Either[A, B]] - n: 0
20:20:05.605 [Thread-47] ERROR MyApp - F[Either[A, B]] - Error: / by zero
Log OptionT[F, A]
Log[F].log(
OptionT[F, A]
)(
ifEmpty: => LogMessage with MaybeIgnorable,
toLeveledMessage: A => LogMessage with MaybeIgnorable
)
A given OptionT[F, A], you can simply log it with log.
Example
import cats._
import cats.data._
import cats.syntax.all._
import cats.effect._
import effectie.core._
import effectie.syntax.all._
import loggerf.core._
import loggerf.syntax.all._
import loggerf.logger._
def greeting[F[_]: Fx](name: String): F[String] =
pureOf(s"Hello $name")
def hello[F[_]: Monad: Fx: Log](maybeName: Option[String]): F[Unit] =
(for {
name <- OptionT(pureOf(maybeName))
.log(
warn("No name given"),
name => info(s"Name: $name")
)
message <- OptionT.liftF(greeting[F](name))
.log(ignore, msg => info(s"Message: $msg"))
} yield message)
.foreachF(msg => effectOf(println(msg)))
implicit val canLog: CanLog = Slf4JLogger.slf4JCanLog("MyApp- OptionT[F, A]")
// canLog: CanLog = loggerf.logger.Slf4JLogger@5b4ebce4
import effectie.instances.ce2.fx._
def run(): IO[Unit] = for {
_ <- hello[IO](none)
_ <- hello[IO]("Kevin".some)
} yield ()
run().unsafeRunSync()
// Hello Kevin
21:49:30.118 [Thread-58] WARN MyApp- OptionT[F, A] - No name given
21:49:30.119 [Thread-58] INFO MyApp- OptionT[F, A] - Name: Kevin
21:49:30.121 [Thread-58] INFO MyApp- OptionT[F, A] - Message: Hello Kevin
Log EitherT[F, A, B]
Log[F].log(
EitherT[F, A, B]
)(
leftToMessage: A => LeveledMessage with MaybeIgnorable,
rightToMessage: B => LeveledMessage with MaybeIgnorable
)
A given Either[F, A, B], you can simply log it with log.
Example
import cats._
import cats.data._
import cats.syntax.all._
import cats.effect._
import effectie.core._
import effectie.syntax.all._
import loggerf.core._
import loggerf.syntax.all._
import loggerf.logger._
def foo[F[_]: Fx](a: Int): F[Int] =
pureOf(a * 2)
def divide[F[_]: Fx: CanHandleError](a: Int, b: Int): F[Either[String, Int]] =
effectOf((a / b).asRight[String])
.handleNonFatal{ err =>
err.getMessage.asLeft[Int]
}
def calculate[F[_]: Monad: Fx: CanHandleError: Log](n: Int): F[Unit] =
(for {
a <- EitherT.liftF[F, String, Int](foo(n))
.log(
ignoreA,
n => info(s"n: ${n.toString}")
)
result <- EitherT(divide(1000, a))
.log(
err => error(s"Error: $err"),
r => info(s"Result: ${r.toString}")
)
} yield result)
.foldF(
err => effectOf(println(s"Error: $err")),
r => effectOf(println(s"1000 / ${n.toString} = ${r.toString}"))
)
implicit val canLog: CanLog = Slf4JLogger.slf4JCanLog("MyApp - EitherT[F, A, B]")
// canLog: CanLog = loggerf.logger.Slf4JLogger@2d71da72
import effectie.instances.ce2.fx._
def run(): IO[Unit] = for {
_ <- calculate[IO](5)
_ <- calculate[IO](0)
} yield ()
run().unsafeRunSync()
// 1000 / 5 = 100
// Error: / by zero
22:14:24.272 [Thread-66] INFO MyApp - EitherT[F, A, B] - n: 10
22:14:24.275 [Thread-66] INFO MyApp - EitherT[F, A, B] - Result: 100
22:14:24.275 [Thread-66] INFO MyApp - EitherT[F, A, B] - n: 0
22:14:24.279 [Thread-66] ERROR MyApp - EitherT[F, A, B] - Error: / by zero