Git Product home page Git Product logo

woof's People

Contributors

aeons avatar armanbilge avatar bageren avatar colin-tue avatar hejfelix avatar imkarrer avatar kheino avatar kubukoz avatar lmarkowski avatar marcodaniels avatar rgueldem avatar scala-steward avatar unicojoyhug avatar xuwei-k 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

woof's Issues

Document `Filter` enum

Since we moved to the "applicative" style Filter DSL, it's no longer easy to inspect the functionality of the different filter nodes. We should add scaladocs to explain each node in detail.

Example: What exactly does ClassRegex(regex: Regex) do?

Add slf4j-2 module for v 2.x.x support

Going forward, it would be nice to have a slf4j-2 module to support the new binding mechanism. After a while, we can drop the current slf4j module completely.

Support SLF4J 2.x.x

Now that 2.0.x is out, we need to either fully adopt it or split the slf4j module in 2 versions.

image

As I read the table above, users DO need to update their SLF4J API-version if we fully adopt version 2.0.x. This might not be straightforward to do for the users if the API is provided by their framework, or if the API is consumed by downstream dependencies (e.g. http4s).
Ultimately, this means splitting is the only sensible option if we mean to support the new version, as users will be forced to override dependencies of downstream libraries otherwise.

org.legogroup.woof.slf4j.WoofLogger has a hard dependency on slf4j-api, however, since 2.0.x is backwards compatible, we should be able to re-use that code for both 1.x.x and 2.x.x lines leaving only a re-implementation of org.slf4j.impl.StaticLoggerBinder which is small.

If we choose to go ahead with this, the 2.x.x line needs to implement a ServiceLoader instead of a static binder while keeping WoofLogger completely as-is.

Add file output

This is a bit trickier because we support scala.js. We could support this for node environments, but not really in the browser. There is also the option to skip scala.js completely for this feature, but I think having node support would be quite nice.

File rotation could also be nice.

Custom enclosingClass when calling makeIOLogger?

Hi,

I was wondering if you would consider a custom (probably optional) argument for makeIOLogger so that the value of enclosingClass is not computed via macro?

My use case is that I already have a Logger interface all my services use, so I want to abstract over any implementation detail (woof could be just one of many interpreters). At the moment, the "enclosing class" is the following one, which is not pretty:

trading.lib.Logger$._$$anon

Ideally, though, this should represent the package of each service instead of this common package name.

For reference, here's how I instantiate it in my Logger:

val make: IO[Logger[IO]] =
  given Filter  = Filter.everything
  given Printer = ColorPrinter()
  WoofLogger.makeIoLogger(Output.fromConsole).map(from[IO])

Or maybe there's a way to make my logger constructor aware of the right enclosing class via macro?

Thanks!

Separate `Logger` and IO-local context to allow `Logger` to be a `ProFunctor`

Currently, creating a logger is effectful because we create an IOLocal context.

object DefaultLogger:
  def makeIo(output: Output[IO], outputs: Output[IO]*)(using Clock[IO], Printer, Filter): IO[DefaultLogger[IO]] =
    for given StringLocal[IO] <- ioStringLocal
    yield new DefaultLogger[IO](output, outputs*)
end DefaultLogger

The actual interface requires a local instance to allow adding context to effects:

trait Logger[F[_]]:

  val stringLocal: StringLocal[F]

  def doLog(level: LogLevel, message: String)(using LogInfo): F[Unit]
...
end Logger

This in turn allows us to scope context to an effect value whenever we have a logger in scope:

def program(using Logger[IO]): IO[Unit] = 
  for
    _ <- Logger[IO].debug("This is some debug")
    _ <- Logger[IO].info("HEY!")
    _ <- Logger[IO].warn("I'm warning you")
    _ <- Logger[IO].error("I give up")
  yield ()

import Logger.*
val mainWithContext: IO[Unit] = 
  for
    given Logger[IO]  <- DefaultLogger.makeIo(consoleOutput)
    _                 <- program.withLogContext("trace-id", "4d334544-6462-43fa-b0b1-12846f871573")
    _                 <- Logger[IO].info("Now the context is gone")
  yield ()

This is very convenient and easy to use. There are 2 main drawbacks:

  1. The constructor of the logger is now effectful
  2. It's difficult to construct a correct instance of Profunctor for this interface

Challenges

If the Logger no longer has a reference to the local context, we place the burden on the user to pass around this context. It's not clear if there's a good solution to avoid this (maybe Odin has solved this?).

Proposal

Simplify the interface to something akin to:

case class LogLine(level: LogLevel, message: String, context: Map[String,String], info: LogInfo)

trait Logger[F[_]]:
  def doLog(line: LogLine): F[Unit]
end Logger

Consider publishing for scala-native

There has been progress in scala-native world recently. It got runtime for cats-effect. As this library is built on cats-effect, would it be possible to add support for scala-native?

Consider applicative DSL for filters

Currently, log filters are defined as:

type Filter = LogLine => Boolean

which is the simplest and most flexible way to filter logs. It works quite well and supports any non-effectful filter you could come up with.
In the end, we often use a composition of the pre-defined filters with atLeastLevel, regexFilter, or everything.
These filters can all be defined in a "applicative style", allowing for easier testing and introspection at the cost of some minor flexibility.

Having an applicative filter would allow performance optimizations not currently possible, e.g. using the nothing filter could eliminate the overhead of materializing the log line in the first place.

Add more printers

In the end, printers are just styling. It would be nice to have a few more options (different colors, different timestamp layout, reordering of information etc).

Add thread ID to context

This is more for discussion. Sometimes, it may be interesting to understand which fiber/thread you are logging from. I believe this may incur some runtime cost.

Allow shortening of class path

Having a class with a deep nesting e.g. com.mydomain.services.implementations.MyClassImpl adds a lot of noise to logs. It could be shortened to c.m.s.i.MyClassImpl or similar.
However, for shorter paths it might make sense to keep everything, e.g. services.MigrationService.

It would be nice to have a Printer that, depending on the length of the string, cuts down the class path based on some heuristic.
Another consideration would be to expand the LogInfo class https://github.com/LEGO/woof/blob/main/modules/core/src/main/scala/org/legogroup/woof/LogInfo.scala such that enclosingClass gets a more precise type than the current String.
This would make the Printer implementation more straight forward.

Create "examples" module to serve as documentation

Currently, there's not much documentation regarding e.g. configuration of log levels, printers, outputs etc. It would make sense to create and maintain a module with a bunch of self-contained main functions, each showing some aspect of the library.

Support structured logging

Currently, the org.legogroup.Output expects String as input which would stop the application from efficiently doing any kind of structured logging.
Combining the Printer and Output concept would easily solve this with a method signature similar to:

  def output(
      epochMillis: EpochMillis,
      level: LogLevel,
      info: LogInfo,
      message: String,
      context: List[(String, String)],
  ): F[Unit]

however, this will lead to a less composable architecture. We could change the arguments to a case class:

case class LogLine(      
      epochMillis: EpochMillis,
      level: LogLevel,
      info: LogInfo,
      message: String,
      context: List[(String, String)],
      )

which would make it much easier to pass around. I still think there's some merit to separating the stateless transformations from the effectful outputs, but it's not easy to pick a suitable input type for the Output class methods.

Consider the following:

  • The user wants to print human readable lines in color to the std output
  • The user also wants to send structured output as json to a structured log server via the network

Here, it would be nice to allow pairing up printers and outputs which would basically solve this and many more cases.

On a final note, I do realize that we could simply change it to be 1 concept of LogLine => F[Unit] and let the library users do their own function composition, but I feel like that may be a bit too unclear and not very intention-revealing. This solution will not force people to use String as an intermediate format, though!

Thoughts are very welcome!

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.