LEGO / woof

A pure Scala 3 logging library with no reflection
Other
434 stars 16 forks source link

FS2: Logger doesn't print #237

Closed emaysyuk closed 5 months ago

emaysyuk commented 5 months ago

Can anybody tell why logger doesn't print from inside of FS2 Stream (here and here)?

I've prepared a simple project to illustrate the problem.

https://github.com/emaysyuk/scala-woof-logger-issue

Looks like the problem in DefaultLogger#logToOutputs, the logger cannot summon Local[IO, List[(String, String)]] but I don't see why because no errors are thrown.

hejfelix commented 5 months ago

Maybe I'm missing something, but isn't this an unused value in these lines:

https://github.com/emaysyuk/scala-woof-logger-issue/blob/067b522b6c6dae801b47eb950d159756bfffd36e/src/main/scala/com/may/main.scala#L32C1-L35C19

i.e. you're throwing away the value from the for yield:

...
      .flatMap { httpServer =>
        for {
          given Logger[IO] <- DefaultLogger.makeIo(Output.fromConsole)
          _ <- Logger[IO].warn("Before exception") // TODO: doesn't print anything
        } yield ()  // this value is not saved or returned from the function?
        Stream.eval(httpServer.useForever).concurrently {
          throw new RuntimeException("Catch me and log to console")
        }
      }

I'll try to reproduce it

hejfelix commented 5 months ago

This works well on my end:

//> using dep co.fs2::fs2-core::3.10.2
//> using dep org.legogroup::woof-core::0.7.0

import cats.effect.unsafe.implicits.global
import org.legogroup.woof.{given, *}
import cats.effect.IO
import fs2.Stream
import Logger.*
given Filter = Filter.everything
given Printer = NoColorPrinter()

@main
def main(): Unit =
  val program = for
    given Logger[IO] <- DefaultLogger.makeIo(Output.fromConsole[IO])
    _ <- Logger[IO].info("Hello, world!")
    _ <- logInStream.withLogContext("stream" -> "log")
  yield ()

  program.unsafeRunSync()

def logInStream(using Logger[IO]): IO[Unit] =
  Stream
    .emits(1 to 10)
    .evalMap(i => Logger[IO].info(s"Processing $i"))
    .compile
    .drain

prints

2024-05-20 20:43:37 [INFO ] fs2-woof$package$: Hello, world! (fs2-woof.scala:16)
2024-05-20 20:43:37 [INFO ] stream=log fs2-woof$package$: Processing 1 (fs2-woof.scala:25)
2024-05-20 20:43:37 [INFO ] stream=log fs2-woof$package$: Processing 2 (fs2-woof.scala:25)
2024-05-20 20:43:37 [INFO ] stream=log fs2-woof$package$: Processing 3 (fs2-woof.scala:25)
2024-05-20 20:43:37 [INFO ] stream=log fs2-woof$package$: Processing 4 (fs2-woof.scala:25)
2024-05-20 20:43:37 [INFO ] stream=log fs2-woof$package$: Processing 5 (fs2-woof.scala:25)
2024-05-20 20:43:37 [INFO ] stream=log fs2-woof$package$: Processing 6 (fs2-woof.scala:25)
2024-05-20 20:43:37 [INFO ] stream=log fs2-woof$package$: Processing 7 (fs2-woof.scala:25)
2024-05-20 20:43:37 [INFO ] stream=log fs2-woof$package$: Processing 8 (fs2-woof.scala:25)
2024-05-20 20:43:37 [INFO ] stream=log fs2-woof$package$: Processing 9 (fs2-woof.scala:25)
2024-05-20 20:43:37 [INFO ] stream=log fs2-woof$package$: Processing 10 (fs2-woof.scala:25)
hejfelix commented 5 months ago

Oh yeah, on the handleError you're again giving it an unused value. The type of handleError is:

  def handleError[B >: A](f: Throwable => B): IO[B] =
    handleErrorWith[B](t => IO.pure(f(t)))

you want to use handleErrorWith(...) if you want side effects. So looks to me like you have unused effect values in both cases.

hejfelix commented 5 months ago

Made a pull request to demonstrate: https://github.com/emaysyuk/scala-woof-logger-issue/pull/1

emaysyuk commented 5 months ago

@hejfelix Thank you for your prompt response and assistance. The example you provided is very helpful. This is my first experience with Cats Effect and FS2, so I'm finding it a bit challenging to understand everything. The logger works perfectly, so I believe we can consider this issue resolved.