Code Monkey home page Code Monkey logo

log4cats's Introduction

log4cats Build Status Maven Central

Project Goals

log4cats attempts to make referentially transparent logging a reality. These F algebras allow you to write code for logging knowing you won't be doing side-effects as it offers no way to do so. We provide our own slf4j layer, or you can use any of the supported backends, or create your own.

Quick Start

To use log4cats in an existing SBT project with Scala 2.12 or a later version, add the following dependency to your build.sbt:

libraryDependencies ++= Seq(
  "org.typelevel" %% "log4cats-core"    % "<version>",  // Only if you want to Support Any Backend
  "org.typelevel" %% "log4cats-slf4j"   % "<version>",  // Direct Slf4j Support - Recommended
)

Why log4cats?

Well, to answer that, let's take a look at how you might combine cats-effect with vanilla logging...

object MyVanillaLoggingThing {
  val logger: Logger = Logger(LoggerFactory.getLogger(getClass.getName))
  
  def doSomething(): IO[Unit] =
    IO(logger.info("Doing something!")) *> IO.println("Hello, World!")

}

But what if you don't want to wrap your logger in an IO like this?
Good news, you don't have to! Enter log4cats! Read on!

Examples

import org.typelevel.log4cats.Logger
import org.typelevel.log4cats.slf4j.Slf4jLogger
import cats.effect.Sync
import cats.implicits._

object MyThing {
  // Impure But What 90% of Folks I know do with log4s
  implicit def unsafeLogger[F[_]: Sync] = Slf4jLogger.getLogger[F]

  // Arbitrary Local Function Declaration
  def doSomething[F[_]: Sync]: F[Unit] =
    Logger[F].info("Logging Start Something") *>
    Sync[F].delay(println("I could be doing anything"))
      .attempt.flatMap{
        case Left(e) => Logger[F].error(e)("Something Went Wrong")
        case Right(_) => Sync[F].pure(())
      }

  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

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

Wait...why log4cats again?

If you compare the vanilla logger + cats-effect example with the log4cats examples above, you might be asking yourself "why do I have to do any of this?" or "why can't I just add a log statement like normal?"

Well there are several reasons. Logging is often times an overlooked side-effect, which under the hood can range from writing to a mutable queue, writing to disk, outputting to the console, or sometimes even doing network I/O! To correctly deal with these kinds of side-effects we have to ensure they are properly wrapped in IO, see the cats-effect docs for more details.

Basically, we are using cats-effect. We like things like "referential transparency" and "programs-as-values". Wrapping our log statement in an IO helps with that.

Laconic syntax

It's possible to use interpolated syntax for logging. Currently, supported ops are: trace, debug, info, warn, error. You can use it for your custom Logger as well as for Slf4j Logger.

import cats.Applicative
import cats.effect.Sync
import org.typelevel.log4cats.Logger
import org.typelevel.log4cats.syntax._

def successComputation[F[_]: Applicative]: F[Int] = Applicative[F].pure(1)
def errorComputation[F[_]: Sync]: F[Unit] = Sync[F].raiseError[Unit](new Throwable("Sorry!"))

def log[F[_]: Sync: Logger] = 
  for {
    result1 <- successComputation[F]
    _ <- info"First result is $result1"
    _ <- errorComputation[F].onError(_ => error"We got an error!")
  } yield ()

Logging using capabilities

You can work with logging using capabilities. It's implemented via the LoggerFactory trait. You instantiate it once (dependent on the specific logging backend you use) and pass this around in your application.

This brings several advantages:

  • it's no more needed to pass the very powerful F[_]: Sync constraint everywhere that can do almost anything when you only need logging.
  • you have control of loggers creation, and you can even add in whatever custom functionality you need for your applications here. E.g. create loggers that also push logs to some external providers by giving a custom implementation of this trait.

If you are unsure how to create a new LoggerFactory[F] instance, then you can look at the log4cats-slf4j, or log4cats-noop modules for concrete implementations.

The quickest fix might be to import needed implicits:

// assumes dependency on log4cats-slf4j module
import org.typelevel.log4cats._
import org.typelevel.log4cats.slf4j._

val logger: SelfAwareStructuredLogger[IO] = LoggerFactory[IO].getLogger

// or
def anyFSyncLogger[F[_]: Sync]: SelfAwareStructuredLogger[F] = Slf4jFactory[F].getLogger

Alternatively, a mutually exclusive solution is to explicitly create your LoggerFactory[F] instance and pass them around implicitly:

import cats.effect.IO
import cats.Monad
import cats.syntax.all._
import org.typelevel.log4cats._
import org.typelevel.log4cats.slf4j.Slf4jFactory

// create our LoggerFactory
implicit val logging: LoggerFactory[IO] = Slf4jFactory[IO]

// we summon LoggerFactory instance, and create logger
val logger: SelfAwareStructuredLogger[IO] = LoggerFactory[IO].getLogger
logger.info("logging in IO!"): IO[Unit]

// basic example of a service using LoggerFactory
class LoggerUsingService[F[_]: LoggerFactory: Monad] {
  val logger = LoggerFactory[F].getLogger
  def use(args: String): F[Unit] =
    for {
      _ <- logger.info("yay! effect polymorphic code")
      _ <- logger.debug(s"and $args")
    } yield ()
}
new LoggerUsingService[IO].use("foo")

Using log4cats in tests

See here for details

CVE-2021-44228 ("log4shell")

log4cats is not directly susceptible to CVS-2021-44228. The log4cats-slf4j implementation delegates all logging operations to slf4j. if you use log4cats-slf4j, your configured slf4j provider may put you at risk. See slf4j's comments on CVE-2021-44228 for more.

log4cats's People

Contributors

alexcardell avatar ant8e avatar armanbilge avatar bpholt avatar catostrophe avatar christopherdavenport avatar danicheg avatar diogocanut avatar dj707chen avatar gatorcse avatar hamnis avatar ivan-klass avatar kubukoz avatar larsrh avatar lavrov avatar lorandszakacs avatar mergify[bot] avatar morgen-peschke avatar ollyw avatar qi77qi avatar rossabaker avatar rpiaggio avatar scala-steward avatar seigert avatar tbrown1979 avatar timbess avatar travisbrown avatar typelevel-steward[bot] avatar vlachjosef avatar weipingc avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

log4cats's Issues

Mdc context map cleared before logging

io.chrisdavenport.log4cats.slf4j.internal.Slf4jLoggerInternal#contextLog

  private[this] def contextLog[F[_]](
      isEnabled: F[Boolean],
      ctx: Map[String, String],
      logging: F[Unit]
  )(implicit F: Sync[F]): F[Unit] =
    isEnabled.ifM(
      F.suspend {
        val backup = MDC.getCopyOfContextMap
        try {
          for {
            (k, v) <- ctx
          } MDC.put(k, v) 
          logging
        } finally {
          if (backup eq null) MDC.clear() // this will be executed before the logging effect
          else MDC.setContextMap(backup)
        }
      },
      F.unit
    )

Code not wrapped in F will be executed "ahead of time", most probably bracket would fit better

def bracket[A, B](acquire: F[A])(use: A => F[B])
    (release: A => F[Unit]): F[B] =
      bracketCase(acquire)(use)((a, _) => release(a))

When using cats and following code:

val logging = IO.delay(println("logging"))

      val ioa = IO.suspend {
        val backup = MDC.getCopyOfContextMap
        try {
          println("before")
          logging
        } finally {
          println("finally")
        }
      }
      ioa.unsafeRunSync()

prints

before
finally
logging

version used "io.chrisdavenport" %% "log4cats-slf4j" % "0.4.0-M1",

Support LoggerFactory

Abstract LoggerFactory[F[_]] which would provide implementation agnostic factory methods. Having such factory would allow one to choose implementation at one place once.

Better documentation

It would be very nice if there was some more documentation all of the logger apis that are available for use as well as some beginner friendly examples with explanation.

Add means to test whether debug logging is enabled

When attempting to port our application from Journal to log4cats 0.0.1 the first issue I encountered was there appears to be no way to test whether or not debug logging is enabled. We achieved this in Journal by using Logger.backend.isDebugEnabled.

No-op Logger

Do you think it'd be useful to include a Logger that just acts as a no-op for any logging operations?

Mostly inspired by monad-logger's NoLoggingT, which I've used in testing scenarios when I just want to disable logging entirely for any functions using MonadLogger.

A working example in README.md

The example I copied from README.md cannot be compiled correctly without modifications. I also suggest making it executable. I find it helpful to someone like me who is unfamiliar with this style of logging.

While we are at it, is the unsafeLogger defined at (A) considered impure? Thanks

import io.chrisdavenport.log4cats.Logger
import io.chrisdavenport.log4cats.slf4j.Slf4jLogger
import cats.effect.{ExitCode, IO, IOApp, Sync}
import cats.implicits._

object MyThing extends IOApp {

  // Arbitrary Local Function Declaration
  def doSomething[F[_]: Sync]: F[Unit] = {
    // Impure But What 90% of Folks I know do with log4s
    implicit def unsafeLogger[F[_]: Sync] = Slf4jLogger.getLogger[F]

    Logger[F].info("Logging Start Something") *>
      Sync[F].delay(println("I could be doing anything")).attempt.flatMap {
        case Left(e)  => Logger[F].error(e)("Something Went Wrong")
        case Right(_) => Sync[F].pure(())
      }
  }

  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

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

  override def run(args: List[String]): IO[ExitCode] =
    doSomething[IO] *>
      MyThing.safelyDoThings[IO] *> {

      // Impure But What 90% of Folks I know do with log4s
      implicit def unsafeLogger[F[_]: Sync] = Slf4jLogger.getLogger[F]    // (A)

      MyThing.passForEasierUse[IO]
    } *>
      IO(ExitCode.Success)
}

MDC is not cleared on task cancellation

In the case where the logging task (with a context for MDC) gets cancelled, the MDC context is not cleared/restored to it's previous state.

Here is an example code that quickly produces the issue. A log setting the MDC context gets cancelled. Other log tasks then randomly display keys from the "leaked" MDC context.

I've had a look at the MDC related code. I was about to suggest using a Resource or a Bracket to clear/restore the MDC to it's previous state but that's already the case.
I'm not sure why this is not working as expected 🤔, I'll have another look this weekend.

Direct SLF4J backend

For projects which might use log4cats as their only logging interface, the additional layer of abstraction that is log4s seems unnecessary, would it be possible to build a direct SLF4J backend to log4cats?

Add means to easily add context information to messages from a given logger

We want to have certain values logged every time a log message is emitted from a given logger. In Journal we achieved this using its support for a handler function (handler: LogMessage => Unit) thus:

    new Logger(
      backend, {
        case journal.Error(message, None) =>
          backend.error(prependContext(message))
        case journal.Error(message, Some(e)) =>
          backend.error(prependContext(message), e)
        case journal.Info(message, None) =>
          backend.info(prependContext(message))
        case journal.Info(message, Some(e)) =>
          backend.info(prependContext(message), e)
        case journal.Warn(message, None) =>
          backend.warn(prependContext(message))
        case journal.Warn(message, Some(e)) =>
          backend.warn(prependContext(message), e)
        case journal.Debug(message, None) =>
          backend.debug(prependContext(message))
        case journal.Debug(message, Some(e)) =>
          backend.debug(prependContext(message), e)
      }
    )

Obiously this is a lot of code duplication.

So far as I can see to achieve a similar result with log4cats in its current form one would need to create a new log4s logger with a new implementation of the org.slf4j.Logger interface that would do the above prepending. This seems overly cumbersome, it would be great to have some sort of easier way to do this in log4cats.

Publish versionScheme

With SBT 1.5.0 now in place, versionScheme gained importance as it's the source of truth when identifying eviction warnings. Can we please add it to build.sbt?
What would be the best value for that? semver-spec looks reasonable IMO

Could use `implicit F: Defer: Applicative` instead of `F: Sync` here?

The use of Sync here propagates out to any code that wants to use the logger. If instead, it used F: Defer: Applicative and all the instances of F.delay(x) were changed to F.defer(x.pure[F]) then this would relax the requirements and only reference type classes from cats-core instead of cats-effect.

This is quite a big change, however, as the typeclass constraints will propagate out through all the other layers of log4cats.

https://github.com/ChristopherDavenport/log4cats/blob/e84f1bd6e0990a1cb7b8006ba869ed068a354303/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/Slf4jLoggerInternal.scala#L44

History logger for testing convenience

In some sections of our codebase we've found ourselves wanting to check that code is infact logging the way we expect. To do this with Journal we built a HistoryLogger along these lines (apologies for the lack of purity ;-) ):

class HistoryLogger(private val enableDebug: Boolean = true) {
  val messages = mutable.MutableList[String]()

  private val slf4jBackend =
    new SubstituteLogger("test", new java.util.LinkedList(), false) {
      override def isInfoEnabled = true

      override def isWarnEnabled = true

      override def isErrorEnabled = true

      override def isDebugEnabled = enableDebug

      override def info(msg: String): Unit = messages += msg

      override def warn(msg: String): Unit = messages += msg

      override def error(msg: String): Unit = messages += msg

      override def debug(msg: String): Unit = messages += msg
    }
  slf4jBackend.setDelegate(NOPLogger.NOP_LOGGER)

}

This allows us to pass in this logger and then assert that the expected message has been logged. Perhaps it could be useful to have a testing logger such as this built in to log4cats if this is a sufficiently wide use-case?

Drop 2.11

I think its time.

I dislike 2.11 and I can add new stuff to traits without it.

Way to change logger name

It would be nice for me to change the name of a passed Logger instance so I can differentiate across classes.

I think a method like
def withName(s: String): Logger[F]
would do

WDYT?

MDC Domain Type Inquiry

It has been raised to my attention that (String, String)* is not a very desirable type to work with for MDC, and that perhaps a Map[String, String] would be better.

What was originally selected was done so in parity with the underlying SLF4J.

https://github.com/ChristopherDavenport/log4cats/blob/master/cats/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LoggerMacros.scala#L166

Which applied each into the mapped diagnostic context. However I can see that generally you likely do not want more than 1 entry for a single key, and as a result.

If we switch to map - What behavior do we want to see if that Map already contains a key from a higher level logger, should that be overridden in the lower logger if it is applied in the map?

Comments very appreciated.

Class in log output shown as the class where log was instantiated

Given the following code:

import io.chrisdavenport.log4cats.Logger
import io.chrisdavenport.log4cats.slf4j.Slf4jLogger
import cats.effect.{ExitCode, IO, IOApp, Sync}
import cats.implicits._

object MyOtherThing {
  def doSomething[F[_]: Sync: Logger]: F[Unit] =
    Logger[F].info("Other stuff to do ")
}

object MyThing extends IOApp {

  // Arbitrary Local Function Declaration
  def doSomething[F[_]: Sync]: F[Unit] = {
    // Impure But What 90% of Folks I know do with log4s
    implicit def unsafeLogger[F[_]: Sync] = Slf4jLogger.getLogger[F]

    Logger[F].info("Logging Start Something") *>
      Sync[F].delay(println("I could be doing anything")).attempt.flatMap {
        case Left(e)  => Logger[F].error(e)("Something Went Wrong")
        case Right(_) => Sync[F].pure(())
      }
  }

  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

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

  override def run(args: List[String]): IO[ExitCode] =
    doSomething[IO] *>
      MyThing.safelyDoThings[IO] *> {

      // Impure But What 90% of Folks I know do with log4s
      implicit def unsafeLogger[F[_]: Sync] = Slf4jLogger.getLogger[F]    // (A)

      MyThing.passForEasierUse[IO] *> MyOtherThing.doSomething[IO]
    } *>
      IO(ExitCode.Success)
}

and following logback.xml:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%logger{35} - [%class] - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="stdout"/>
    </root>
</configuration>

this produces following output:

.util.MyThing - [io.chrisdavenport.log4cats.slf4j.internal.Slf4jLoggerInternal$Slf4jLogger] - Logging Start Something
I could be doing anything
.util.MyThing - [io.chrisdavenport.log4cats.slf4j.internal.Slf4jLoggerInternal$Slf4jLogger] - Logging at start of safelyDoThings
I could do anything
.util.MyThing - [io.chrisdavenport.log4cats.slf4j.internal.Slf4jLoggerInternal$Slf4jLogger] - Logging at end of safelyDoThings
.util.MyThing - [io.chrisdavenport.log4cats.slf4j.internal.Slf4jLoggerInternal$Slf4jLogger] - Logging at start of passForEasierUse
I could do anything
.util.MyThing - [io.chrisdavenport.log4cats.slf4j.internal.Slf4jLoggerInternal$Slf4jLogger] - Logging at end of passForEasierUse
.util.MyThing - [io.chrisdavenport.log4cats.slf4j.internal.Slf4jLoggerInternal$Slf4jLogger] - Other stuff to do 

As you can see the last log line shows that logging is coming from Mything(whereas in reality it is MyOtherThing) Wondering if there is any trickery to make this show MyOtherThing as a source?

IOLog4s Backend

Need to implement a iolog4s backend as well because its good to be able to be the bridge between all useable backends in my opinion.

If this is the interface of the logging libraries, this library deserves a spot. Should be almost seamless.

Consider using scribe backend

Hey @ChristopherDavenport, this is a really nice project! Looks great!

I was wondering whether you'd consider a PR to use outr/scribe rather than log4s or making the logging backend configurable? scribe has support for scala-native / scala.js and great performance, and it can still be used as an slf4j backend.

Slf4jLogger.create[F] doesn't reflect the enclosing class name

import cats.effect.{ExitCode, IO, IOApp, Sync}
import io.chrisdavenport.log4cats.slf4j.Slf4jLogger

object MyClass {
  def mkLogger[F[_]: Sync] = Slf4jLogger.create[F]
  def mkLoggerUnsafe[F[_]: Sync] = Slf4jLogger.unsafeCreate[F]
}

object LoggerApp extends IOApp {
  override def run(args: List[String]): IO[ExitCode] = for {
    safeLogger <- MyClass.mkLogger[IO]
    unsafeLogger = MyClass.mkLoggerUnsafe[IO]
    _ <- safeLogger.info("safe")
    _ <- unsafeLogger.info("unsafe")
  } yield ExitCode.Success
}

Result:

INFO  i.c.l.s.Slf4jLogger - safe 
INFO  MyClass - unsafe 

Expected:

INFO  MyClass - safe  
INFO  MyClass - unsafe 

Usability issue with `Sync` constraint requirement.

The fact that all the constructor methods in Slf4jLogger require a Sync instance is very detrimental to the usage of this library -- especially when considering that the macro call needs to live inside every class to produce the desired class name in the log lines. This effectively means all classes that log need the Sync constraint.
I'm not sure there is an easy solution here, though :/

Improvement proposal: "LoggerForType", use phantom types to disambiguate loggers

Hello :)

As someone else pointed out in this issue #397, the class that shows up in traces reflects where the logger was instantiated, usually the main class of the application. In many applications, it is however desirable to have several loggers, each with a name reflecting a particular portion of code. This makes it easier to spot where an issue comes from, and allows logging backends to be configured on a per-class basis.

However, creating multiple loggers and passing them around can be a rather frustrating experience. Having multiple loggers means you have to give up using implicits to pass them around. I came up with a solution to address this issue, and believe this could be a nice improvement for this library.

The idea is to use a phantom type to bind a logger instance to a single class, and optionally use a ClassTag to conveniently instantiate named loggers. There are code examples in the repository :)

final case class LoggerForType[F[_], T](logger: Logger[T]) extends Logger[F]

I haven't yet published the work as a library, and I am willing to open a PR to include it directly in log4cats, if the maintainers find it desirable :)

Cheers, and thank you for your work

Support for Scala 2.13.x

I'm trying to support Scala 2.13.0-M5 in some libraries that depend on log4cats. I can give it a try and make a PR if you're okay with it.

cats-mtl module with FunctorTellLogger and automatic MDC injection

A cats-mtl module could provide a couple of useful things:

  • A logger based on StructuredLogger that automatically adds MDC context from an ApplicativeAsk instance.
  • A logger similar to WriterTLogger but based on FunctorTell for extra flexibility.

I'd be happy to put in a PR if this would be wanted.

MDC regression between 0.3.0 and 0.4.0-M1

For a simple setup like this:
build.sbt

libraryDependencies += "io.chrisdavenport" %% "log4cats-slf4j" % "0.4.0-M1" withSources()
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3" withSources()

Log4CatsMDC.scala:

package bug

import cats._
import cats.implicits._
import cats.effect._
import io.chrisdavenport.log4cats.slf4j._

object Log4CatsMDC extends IOApp {
  private val logger = Slf4jLogger.getLogger[IO]
  override def run(args: List[String]): IO[ExitCode] = {
    for {
      _ <- logger.info(Map("p1" -> "value"))("w/ MDC")
      _ <- logger.info("w/o MDC")
    } yield ExitCode.Success
  }
}

logback.xml:

<configuration>

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%thread] %highlight(%-5level) %cyan(%logger) - %mdc — %msg %n
            </pattern>
        </encoder>
    </appender>

    <root level="DEBUG">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

With no other changes except log4cats version, we get:

0.3.0

[scala-execution-context-global-479] INFO  bug.Log4CatsMDC - p1=value — w/ MDC 
[scala-execution-context-global-479] INFO  bug.Log4CatsMDC -  — w/o MDC 

0.4.0-M1

[ioapp-compute-0] INFO  bug.Log4CatsMDC -  — w/ MDC 
[ioapp-compute-0] INFO  bug.Log4CatsMDC -  — w/o MDC

As you can see, MDC key/value pairs no longer show up in logs 😢

Streamline adding context to structured loggers

At the moment, to add some context to the logger we have to do this:

StructuredLogger.withContext(logger)(Map("foo" -> 123.show))
// Or
SelfAwareStructuredLogger.withContext(logger)(Map("foo" -> 123.show))

This is noisy. Also note that the two versions produce subtly different results - the first one "loses" the logger's self-awareness.

Instead, I'd like to be able to write something like

logger.withContext(Map("foo" -> "123"))
// or, better yet
logger.withContext("foo" -> "123"))

and have it return StructuredLogger or SelfAwareStructuredLogger depending on the logger's type.

FiberLocal / IOLocal logger wrapper

hopefully self-explanatory: it would be great to have a wrapper for loggers that would allow one to pass structured logging context in a more implicit fashion.

This would probably involve creating a root logging context (the default value of the IOLocal), then using that in a wrapped logger so it passes the context to the underlying logger (e.g. slf4j).

How to raise test coverage on parameters passed to NoOpLogger

I'm failing to meet 100% coverage requirements in a module because the call by name string inside a log effect never gets executed. Would it be reasonable to make the no-op logger execute the passed in strings or to provide an option that allowed it?

Proposal for `log4cats-sourcecode` module to provide information about the call site

I suggest adding a module for integration with sourcecode to add information about the call site to the context of a structured logger. Rather than introduce a new subtype of Logger, I think the simplest thing is to put the logging methods on an object. It might look something like this:

object SourceCodeLogger {
  def info[F[_]](ctx: Map[String, String])(msg: => String)(implicit logger: StructuredLogger, line: sourcecode.Line, file: sourcecode.File): F[Unit] =
    logger.info(ctx ++ Map("callsite" -> s"${file.value}:${line.value}"))(msg)
  /* ... */
}

Of course there's endless scope for bike shedding about exactly what information should be provided and in what format, so I don't know if this is over-opinionated to go in a library like log4cats?

Namespace Concerns

io.chrisdavenport.log4cats is quite long for a logging library but is appropriately namespaced, alternatively this could transition to package log4cats which would make this far easier to import. Any thoughts?

License Change MIT -> Apache

Since we are looking to integrate some log4s originating code for the slf4j-api backend for better license interoperability I think a shift from MIT to Apache is warranted.

@keirlawson Could I get your approval here as you have contributed.

Add String interpolators

For example:

def doSomething[F[_]: Sync: Logger]: F[Unit] =
	for  {
		_ <- info"Wow, success!"
		_ <- error"Aww, that sucks"
		_ <- warn"Better check yourself"
	} yield ()

Scribe ScalaJS

No urgent need for this, but publishing a ScalaJS module for the Scribe backend would probably be a great addition. I may be able to take a hack at it at some point but this is mostly just to put the prospect on your radar. Keep up the great work!

Message and Error Logger base classes

I've been considerring this and not sure if its a great use of time and resources, as most wouldn't utilize it but a message logger and a error logger split would allow a context logger to become a message logger given a context. So you can set context along the way or other tools to pass a specific context.

Wondering if others have thought as it puts another split in the hierarchy in a far more fundamental position.

Adopt Scala Code of Conduct

I believe that these projects should offer a friendly and safe space for working together. I am in the process of adding this to most of my libraries.

As this library has many hands involved I would like to open the issue up for discussion before doing so.

Blocking Slf4j Logger Backer

Not all Slf4j Backends are async. As a result we should provide a blocking consructor so that users of those backends don't do their blocking logging on their computer pool which is wasteful.

Noted from Discord.

Impl can be the exact same as current with Sync[F].delay -> Sync[F].blocking

Move Non-Stable projects into their own Projects

  • scalaz
  • log4s
  • scribe

These are all great backends that I want to keep available, however their existence precludes the ability to go to a 1.0 release because they are massively changing quickly and will increment the core quickly.

I want to propose a core project that has the data construction, and the default slf4j backend that I believe is mainly stable. Then with these other projects we can keep versions that can increment with their evolving backends.

Release 0.1.0

Release a binary compatibility preserved artifact for logging.

What is the suggested way to use this library?

There are two approaches mentioned in the README.md and I am a bit confused at this point. There reason is that; it looks like that first approach is impure and second approach (if I am not mistaken) creates a logger every time you use it in for comprehension.

Would it make sense to treat logger as Resource?

val logger: Resource[F, SelfAwareStructuredLogger[F]] = Resource.liftF(Slf4jLogger.fromClass[F](this.getClass))

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

I am still new to whole TypeLevel/FP stack so please let me know if I am doing something completely wrong.

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.