Skip to content

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

@hejfelix

Description

@hejfelix

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions