Getting Started
| Project | Maven Central | 
|---|---|
| logger-f-cats-effect3 | |
| logger-f-cats-effect | |
| logger-f-monix | |
| logger-f-scalaz-effect | |
| logger-f-slf4j | |
| logger-f-log4j | |
| logger-f-log4s | |
| logger-f-sbt-logging | 
- Supported Scala Versions: 3,2.13and2.12
 LoggerF - Logger for
 LoggerF - Logger for F[_]
LoggerF is a tool for logging tagless final with an effect library. LoggerF
requires Effectie to construct F[_].
All the example code in this doc site uses Effectie so if you're not familiar
with it, please check out Effectie
website.
Why LoggerF? Why not just log with map or flatMap? Please
read "Why?" section.
Getting Started
Get LoggerF For Cats Effect
With SLF4J
In build.sbt,
- Cats Effect 3
- Cats Effect 2
libraryDependencies ++=
  Seq(
    "io.kevinlee" %% "logger-f-cats-effect3" % "1.20.0",
    "io.kevinlee" %% "logger-f-slf4j" % "1.20.0"
  )
libraryDependencies ++=
  Seq(
    "io.kevinlee" %% "logger-f-cats-effect" % "1.20.0",
    "io.kevinlee" %% "logger-f-slf4j" % "1.20.0"
  )
With Log4j
- Cats Effect 3
- Cats Effect 2
libraryDependencies ++=
  Seq(
    "io.kevinlee" %% "logger-f-cats-effect3" % "1.20.0",
    "io.kevinlee" %% "logger-f-log4j" % "1.20.0"
  )
libraryDependencies ++=
  Seq(
    "io.kevinlee" %% "logger-f-cats-effect" % "1.20.0",
    "io.kevinlee" %% "logger-f-log4j" % "1.20.0"
  )
With Log4s
- Cats Effect 3
- Cats Effect 2
libraryDependencies ++=
  Seq(
    "io.kevinlee" %% "logger-f-cats-effect3" % "1.20.0",
    "io.kevinlee" %% "logger-f-log4s" % "1.20.0"
  )
libraryDependencies ++=
  Seq(
    "io.kevinlee" %% "logger-f-cats-effect" % "1.20.0",
    "io.kevinlee" %% "logger-f-log4s" % "1.20.0"
  )
With sbt Logging Util
You probably need logger-f for sbt plugin development.
- Cats Effect 3
- Cats Effect 2
libraryDependencies ++=
  Seq(
    "io.kevinlee" %% "logger-f-cats-effect3" % "1.20.0",
    "io.kevinlee" %% "logger-f-sbt-logging" % "1.20.0"
  )
libraryDependencies ++=
  Seq(
    "io.kevinlee" %% "logger-f-cats-effect" % "1.20.0",
    "io.kevinlee" %% "logger-f-sbt-logging" % "1.20.0"
  )
Get LoggerF For Monix
With SLF4J
In build.sbt,
libraryDependencies ++=
  Seq(
    "io.kevinlee" %% "logger-f-monix" % "1.20.0",
    "io.kevinlee" %% "logger-f-slf4j" % "1.20.0"
  )
With Log4j
libraryDependencies ++=
  Seq(
    "io.kevinlee" %% "logger-f-monix" % "1.20.0",
    "io.kevinlee" %% "logger-f-log4j" % "1.20.0"
  )
With Log4s
libraryDependencies ++=
  Seq(
    "io.kevinlee" %% "logger-f-monix" % "1.20.0",
    "io.kevinlee" %% "logger-f-log4s" % "1.20.0"
  )
With sbt Logging Util
You probably need logger-f for sbt plugin development.
libraryDependencies ++=
  Seq(
    "io.kevinlee" %% "logger-f-monix" % "1.20.0",
    "io.kevinlee" %% "logger-f-sbt-logging" % "1.20.0"
  )
Get LoggerF For Scalaz Effect
With SLF4J
In build.sbt,
libraryDependencies ++=
  Seq(
    "io.kevinlee" %% "logger-f-scalaz-effect" % "1.20.0",
    "io.kevinlee" %% "logger-f-slf4j" % "1.20.0"
  )
With Log4j
In build.sbt,
libraryDependencies ++=
  Seq(
    "io.kevinlee" %% "logger-f-scalaz-effect" % "1.20.0",
    "io.kevinlee" %% "logger-f-log4j" % "1.20.0"
  )
With Log4s
In build.sbt,
libraryDependencies ++=
  Seq(
    "io.kevinlee" %% "logger-f-scalaz-effect" % "1.20.0",
    "io.kevinlee" %% "logger-f-log4s" % "1.20.0"
  )
With sbt Logging Util
In build.sbt,
libraryDependencies ++=
  Seq(
    "io.kevinlee" %% "logger-f-scalaz-effect" % "1.20.0",
    "io.kevinlee" %% "logger-f-sbt-logging" % "1.20.0"
  )
Why
If you code tagless final and use some effect library like Cats Effect or Monix or Scalaz Effect, you may have inconvenience in logging.
What inconvenience? I can just log with flatMap like.
for {
  a <- foo(n) // F[A]
  _ <- effectOf(logger.debug(s"blah blah $a"))
  b <- bar(a) // F[A]
} yield b
That's true but what happens if you want to use Option or Either? If you use
them with tagless final, you may not get the result you want.
e.g.)
import cats._
import cats.syntax.all._
import cats.effect._
import effectie.cats.Effectful._
import effectie.cats._
def foo[F[_] : Fx : Monad](n: Int): F[Option[Int]] = for {
  a <- effectOf(n.some)
  b <- effectOf(none[Int])
  c <- effectOf(123.some)
} yield c
foo[IO](1).unsafeRunSync() // You expect None here!!!
// res1: Option[Int] = Some(value = 123)
You expect None for the result due to effectOf(none[Int]) yet you get
Some(123) instead. That's because b is from F[Option[Int]] not from
Option[Int].
The same issue exists for F[Either[A, B]] as well.
So you need to use OptionT for F[Option[A]] and EitherT for
F[Either[A, B]].
Let's write it again with OptionT.
import cats._
import cats.data._
import cats.syntax.all._
import cats.effect._
import effectie.cats.Effectful._
import effectie.cats._
def foo[F[_] : Fx : Monad](n: Int): F[Option[Int]] = (for {
  a <- OptionT(effectOf(n.some))
  b <- OptionT(effectOf(none[Int]))
  c <- OptionT(effectOf(123.some))
} yield c).value
foo[IO](1).unsafeRunSync() // You expect None here.
// res3: Option[Int] = None
The problem's gone! Now each flatMap handles only Some case and that's what
you want. However, because of that, it's hard to log None case.
LoggerF can solve this issue for you.
import cats._
import cats.data._
import cats.syntax.all._
import cats.effect._
import effectie.cats.Effectful._
import effectie.cats._
import loggerf.cats._
import loggerf.logger._
import loggerf.syntax._
// or Slf4JLogger.slf4JLogger[MyClass]
implicit val canLog: CanLog = Slf4JLogger.slf4JCanLog("MyLogger")
// canLog: CanLog = loggerf.logger.Slf4JLogger@6ec252bf
def foo[F[_] : Fx : Monad : Log](n: Int): F[Option[Int]] =
  (for {
    a <- log(OptionT(effectOf(n.some)))(
      ifEmpty = error("a is empty"),
      a => debug(s"a is $a")
    )
    b <- log(OptionT(effectOf(none[Int])))(
      error("b is empty"),
      b => debug(s"b is $b")
    )
    c <- log(OptionT(effectOf(123.some)))(
      warn("c is empty"),
      c => debug(s"c is $c")
    )
  } yield c).value
foo[IO](1).unsafeRunSync() // You expect None here.
// res5: Option[Int] = None
With logs like
00:17:33.983 [main] DEBUG MyLogger - a is 1
00:17:33.995 [main] ERROR MyLogger - b is empty
Another example with EitherT,
import cats._
import cats.data._
import cats.syntax.all._
import cats.effect._
import effectie.cats.Effectful._
import effectie.cats._
import loggerf.cats._
import loggerf.logger._
import loggerf.syntax._
// or Slf4JLogger.slf4JLogger[MyClass]
implicit val canLog: CanLog = Slf4JLogger.slf4JCanLog("MyLogger")
// canLog: CanLog = loggerf.logger.Slf4JLogger@7bac2f86
def foo[F[_] : Fx : Monad : Log](n: Int): F[Either[String, Int]] =
  (for {
    a <- log(EitherT(effectOf(n.asRight[String])))(
      err => error(s"Error: $err"),
      a => debug(s"a is $a")
    )
    b <- log(EitherT(effectOf("Some Error".asLeft[Int])))(
      err => error(s"Error: $err"),
      b => debug(s"b is $b")
    )
    c <- log(EitherT(effectOf(123.asRight[String])))(
      err => warn(s"Error: $err"),
      c => debug(s"c is $c")
    )
  } yield c).value
foo[IO](1).unsafeRunSync() // You expect Left("Some Error") here.
// res7: Either[String, Int] = Left(value = "Some Error")
With logs like
00:40:48.663 [main] DEBUG MyLogger - a is 1
00:40:48.667 [main] ERROR MyLogger - Error: Some Error
Usage
Pleae check out