Giter VIP home page Giter VIP logo

named-log4cats's Introduction

Named-log4Cats

A tiny wrapper around log4cats that binds a logger to specific type, allowing for named loggers to be easily passed around.

tl;dr

Use an implicit LoggerForType in your code. Use the second type parameter to bind the logger to a specific class/object in your code

class ServiceA[F[_]: Monad: MonadUsers](implicit logger: LoggerForType[F, ServiceA[F]]) {

  def run: F[Unit] = for {
    users <- MonadUsers[F].fetchUsers
    _ <- logger.info(s"Retrieved ${users.size} users")
  } yield ()
}

Instantiate the logger in your main

object Main extends IOApp {

  implicit def logger[Class: ClassTag]: NamedLogger[IO, Class] = LoggerForType.unsafeSlf4j

  def run(args: List[String]): IO[ExitCode] =
    new ServiceA[IO].run >> ServiceB.run[IO] >> ExitCode.Success.pure[IO]
}

And you will see properly named loggers in your traces

11:23:55.508 [io-compute-4] INFO dev.guillaumebogard.ServiceA - Hello from ServiceA!
11:23:55.511 [io-compute-4] WARN dev.guillaumebogard.ServiceA - Warning from ServiceA!
11:23:55.512 [io-compute-4] INFO dev.guillaumebogard.ServiceB$ - Hello from ServiceB!

See examples/ for more examples.

What problem does it solve?

Expressing logging as a capability of the program, using type classes, is neat. It allows us to model programs using the weakest constraints possible, rather than requiring powerful abilities like cats.effect.Sync everywhere. These programs are in turn easier to test, because their external dependencies are easily mocked, and easier to reason about, because weaker constraints tell us everything we need to know about a program's behaviour.

Log4Cats lets us abstract away the effect of logging using a type class:

def myPorgram[F[_]: MonadUser: MonadThrow: Logger]: F[Unit]

Here, my program can do exactly three things: manage users in some persistence layer, throw technical exceptions, and log things. The signature is self-documenting, neat!

However, passing a single Logger around in your program means all your traces will share a single name. If you instantiate your logger in the Main of your application, your traces will look something like this:

DEBUG f.c.r.c.Main - Creating new user with id: e180ea47-f90f-4fee-8c1e-ffa21197b895 
DEBUG f.c.r.c.Main - Sending welcome e-mail for user with id: e180ea47-f90f-4fee-8c1e-ffa21197b895

All our traces are sent from the Main logger!

To remedy this, the log4cats readme suggests creating a logger when you need, as part of the function:

def safelyDoThings[F[_]: Sync]: F[Unit] = for {
  logger <- Slf4jLogger.create[F]
  _ <- logger.info("Logging at start of safelyDoThings")
  something <- Sync[F].delay(println("I could do anything"))
    .onError{case e => logger.error(e)("Something Went Wrong in safelyDoThings")}
  _ <- logger.info("Logging at end of safelyDoThings")
} yield something

This defeats the purpose of the libray IMHO, as powerful constraints such as Sync cannot be realistically mocked, and do not convey meaningful information about the program's behaviour, as Sync allows any arbitrary side effect.

Ideally we want the name of the logger to reflect the class where this code belongs, so the traces provide more useful information, and we can tune our logging backend depending on the specific logger. And we don't want to use very powerful type classes such as Sync as part of our business logic.

We could achieve this by instantiating several loggers, and passing them to the services that need them, but then it wouldn't be possible to use implicits (including context bounds), because nothing could tell two Loggers apart.

This is what this tiny library tries to solve.

How does it work ?

We define a NamedLogger[F[_], T], where the second type parameter T is used to disambiguate multiple loggers in the implicit scope. This T is bound to a single, named logger, whose name provides more information about the origin of the traces.

We provide ways, both pure and impure, to create NamedLoggers using the Slf4j backend, in which case a ClassTag is used to name the logger auto-magically.

Alternatively, a NamedLogger can be created from any Logger, as it is just a case class, in which case it is your responsibility to make sure the underlying logger is properly named.

named-log4cats's People

Contributors

gbogard avatar

Watchers

 avatar  avatar

Forkers

aleczorab

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.