Getting Started

Build Status Release Status Latest version

ProjectBintrayMaven Central
logger-f-cats-effectDownloadMaven Central
logger-f-monixDownloadMaven Central
logger-f-scalaz-effectDownloadMaven Central
logger-f-slf4jDownloadMaven Central
logger-f-log4jDownloadMaven Central
logger-f-log4sDownloadMaven Central
logger-f-sbt-loggingDownloadMaven Central
  • Supported Scala Versions: 2.11.12, 2.12.12, 2.13.3 and 3.0.0-M2

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,

libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-cats-effect" % "1.7.0",
"io.kevinlee" %% "logger-f-slf4j" % "1.7.0"
)

With Log4j

libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-cats-effect" % "1.7.0",
"io.kevinlee" %% "logger-f-log4j" % "1.7.0"
)

With Log4s

libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-cats-effect" % "1.7.0",
"io.kevinlee" %% "logger-f-log4s" % "1.7.0"
)

With sbt Logging Util

You probably need logger-f for sbt plugin development.

libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-cats-effect" % "1.7.0",
"io.kevinlee" %% "logger-f-sbt-logging" % "1.7.0"
)

Get LoggerF For Monix

With SLF4J

In build.sbt,

libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-monix" % "1.7.0",
"io.kevinlee" %% "logger-f-slf4j" % "1.7.0"
)

With Log4j

libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-monix" % "1.7.0",
"io.kevinlee" %% "logger-f-log4j" % "1.7.0"
)

With Log4s

libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-monix" % "1.7.0",
"io.kevinlee" %% "logger-f-log4s" % "1.7.0"
)

With sbt Logging Util

You probably need logger-f for sbt plugin development.

libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-monix" % "1.7.0",
"io.kevinlee" %% "logger-f-sbt-logging" % "1.7.0"
)

Get LoggerF For Scalaz Effect

With SLF4J

In build.sbt,

libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-scalaz-effect" % "1.7.0",
"io.kevinlee" %% "logger-f-slf4j" % "1.7.0"
)

With Log4j

In build.sbt,

libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-scalaz-effect" % "1.7.0",
"io.kevinlee" %% "logger-f-log4j" % "1.7.0"
)

With Log4s

In build.sbt,

libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-scalaz-effect" % "1.7.0",
"io.kevinlee" %% "logger-f-log4s" % "1.7.0"
)

With sbt Logging Util

In build.sbt,

libraryDependencies ++=
Seq(
"io.kevinlee" %% "logger-f-scalaz-effect" % "1.7.0",
"io.kevinlee" %% "logger-f-sbt-logging" % "1.7.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 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[_] : 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.

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.

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@5fe013b0
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

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 logger: CanLog = Slf4JLogger.slf4JCanLog("MyLogger")
// logger: CanLog = loggerf.logger.Slf4JLogger@1a92c0a6
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

00:40:48.663 [main] DEBUG MyLogger - a is 1
00:40:48.667 [main] ERROR MyLogger - Error: Some Error

Usage

Pleae check out