Skip to main content
Version: 2.0.0

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.

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]

Example

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.instances.cats._
import loggerf.logger._

implicit val canLog: CanLog = Slf4JLogger.slf4JCanLog("test-logger")
// canLog: CanLog = loggerf.logger.Slf4JLogger@31ed24f7

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.instances.cats._
import loggerf.logger._

implicit val canLog: CanLog = Slf4JLogger.slf4JCanLog("test-logger")
// canLog: CanLog = loggerf.logger.Slf4JLogger@47b9adf3

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

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.

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]

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._
import loggerf.instances.cats._

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._
import loggerf.instances.cats._

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@59f5ad13

import effectie.instances.ce2.fx._
import loggerf.instances.cats._

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@53a945eb

import effectie.instances.ce2.fx._
import loggerf.instances.cats._

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@14c682d

import effectie.instances.ce2.fx._
import loggerf.instances.cats._

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@3ba93b95

import effectie.instances.ce2.fx._
import loggerf.instances.cats._

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