Project Bintray Maven Central 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: 2.11.12
, 2.12.12
, 2.13.4
and 3.0.0-RC1
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
,
Copy libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-cats-effect" % "1.9.0",
"io.kevinlee" %% "logger-f-slf4j" % "1.9.0"
)
With Log4j# Copy libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-cats-effect" % "1.9.0",
"io.kevinlee" %% "logger-f-log4j" % "1.9.0"
)
With Log4s# Copy libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-cats-effect" % "1.9.0",
"io.kevinlee" %% "logger-f-log4s" % "1.9.0"
)
With sbt Logging Util# You probably need logger-f
for sbt plugin development.
Copy libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-cats-effect" % "1.9.0",
"io.kevinlee" %% "logger-f-sbt-logging" % "1.9.0"
)
Get LoggerF For Monix# With SLF4J# In build.sbt
,
Copy libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-monix" % "1.9.0",
"io.kevinlee" %% "logger-f-slf4j" % "1.9.0"
)
With Log4j# Copy libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-monix" % "1.9.0",
"io.kevinlee" %% "logger-f-log4j" % "1.9.0"
)
With Log4s# Copy libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-monix" % "1.9.0",
"io.kevinlee" %% "logger-f-log4s" % "1.9.0"
)
With sbt Logging Util# You probably need logger-f
for sbt plugin development.
Copy libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-monix" % "1.9.0",
"io.kevinlee" %% "logger-f-sbt-logging" % "1.9.0"
)
Get LoggerF For Scalaz Effect# With SLF4J# In build.sbt
,
Copy libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-scalaz-effect" % "1.9.0",
"io.kevinlee" %% "logger-f-slf4j" % "1.9.0"
)
With Log4j# In build.sbt
,
Copy libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-scalaz-effect" % "1.9.0",
"io.kevinlee" %% "logger-f-log4j" % "1.9.0"
)
With Log4s# In build.sbt
,
Copy libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-scalaz-effect" % "1.9.0",
"io.kevinlee" %% "logger-f-log4s" % "1.9.0"
)
With sbt Logging Util# In build.sbt
,
Copy libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-scalaz-effect" % "1.9.0",
"io.kevinlee" %% "logger-f-sbt-logging" % "1.9.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.
Copy 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 get the result you want.
e.g.)
Copy import cats._
import cats.syntax.all._
import cats.effect._
import effectie.cats.Effectful._
import effectie.cats._
def foo[F[_] : EffectConstructor : 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 Option[Int]
not 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
.
Copy import cats._
import cats.data._
import cats.syntax.all._
import cats.effect._
import effectie.cats.Effectful._
import effectie.cats._
def foo[F[_] : EffectConstructor : 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.
Copy 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 logger: CanLog = Slf4JLogger.slf4JCanLog("MyLogger")
// logger: CanLog = loggerf.logger.Slf4JLogger@43582b21
def foo[F[_] : EffectConstructor : 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
Copy 00:17:33.983 [main] DEBUG MyLogger - a is 1
00:17:33.995 [main] ERROR MyLogger - b is empty
Another example with EitherT
,
Copy 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 logger: CanLog = Slf4JLogger.slf4JCanLog("MyLogger")
// logger: CanLog = loggerf.logger.Slf4JLogger@5bd970aa
def foo[F[_] : EffectConstructor : 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
Copy 00:40:48.663 [main] DEBUG MyLogger - a is 1
00:40:48.667 [main] ERROR MyLogger - Error: Some Error
Usage# Pleae check out