Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use IOLocal instead of user-defined reader monal for contextual effects #462

Open
Swoorup opened this issue Jun 25, 2022 · 1 comment
Open

Comments

@Swoorup
Copy link

Swoorup commented Jun 25, 2022

I am wondering if something like IOLocal be used to provide contextual effects logging by default similar to woof? https://github.com/LEGO/woof/blob/main/modules/core/shared/src/main/scala/org/legogroup/woof/local/Local.scala

@Swoorup
Copy link
Author

Swoorup commented Jun 25, 2022

I managed to have something worker, but would be nice if Odin provides this out of the box.

Local.scala

import cats.effect.{IO, IOLocal, MonadCancel}
import cats.kernel.Monoid
import cats.syntax.all.*

trait Local[F[_]: ([G[_]] =>> MonadCancel[G, Throwable]), T]:
  def ask: F[T]
  def local[A](fa: F[A])(f: T => T): F[A]
  def scope[A](fa: F[A])(e: T): F[A] = local(fa)(_ => e)

object Local:

  def makeIoLocal[T: Monoid]: IO[Local[IO, T]] = IOLocal(Monoid[T].empty).map(fromIoLocal)

  def fromIoLocal[T](ioLocal: IOLocal[T]): Local[IO, T] = new Local[IO, T]:
    def ask: IO[T] = ioLocal.get
    def local[A](fa: IO[A])(f: T => T): IO[A] =
      for
        before <- ioLocal.get
        updated = f(before)
        a <- ioLocal.set(updated).as(updated).bracket(_ => fa)(_ => ioLocal.set(before))
      yield a

extension [T: Monoid, U, F[_]: ([G[_]] =>> Local[G, T])](fu: F[U])
  def withAddedContext(t: T) = summon[Local[F, T]].local(fu)(tt => Monoid[T].combine(tt, t))

WrappedLogger.scala

import cats.Monad
import cats.effect.{IO, Sync, Clock}
import cats.syntax.all.*
import io.odin.formatter.Formatter
import io.odin.loggers.*
import io.odin.syntax.*
import io.odin.{consoleLogger, Logger as OdinLogger}

trait Logger[F[_]] extends OdinLogger[F]:
  val stringLocal: Logger.StringLocal[F]

object Logger:
  extension [F[_]: Logger, A](fa: F[A])
    def withLogContext(key: String, value: String): F[A] =
      Logger[F].stringLocal.local(fa)(ctx => ctx.appended((key, value)))

  type StringLocal[F[_]] = Local[F, List[(String, String)]]

  def apply[F[_]](using l: Logger[F]): Logger[F] = l
  val ioStringLocal                              = Local.makeIoLocal[List[(String, String)]]

object DefaultLogger:
  def makeIoFromOdin(odinlogger: OdinLogger[IO]): IO[Logger[IO]] =
    for
      local <- Logger.ioStringLocal
      test: WithContext[IO] = new WithContext[IO]{
        def context: IO[Map[String, String]] = local.ask.map(_.toMap)
      }

      odinCtx = odinlogger.withContext(using Clock[IO], Monad[IO], test)
      logger = new Logger[IO]:
                 val stringLocal = local
                 export odinCtx.*
    yield logger
    

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant