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

Every exceptions logged as FiberFailure #891

Open
leejaeseung opened this issue Sep 29, 2024 · 2 comments
Open

Every exceptions logged as FiberFailure #891

leejaeseung opened this issue Sep 29, 2024 · 2 comments

Comments

@leejaeseung
Copy link

leejaeseung commented Sep 29, 2024

related with #811

Hi, I recently upgraded this library version from 2.1.12 to 2.3.1.
After upgrade, every error log’s root exception was changed to FiberFailure.
(And so our every error alert turn to FiberFailure - using sentry)

It’s because this code (v2.12.2)

override def appendCause(cause: Cause[Any]): Unit = {
  if (!cause.isEmpty) {
    val maybeThrowable = (cause.failures.collect { case t: Throwable => t } ++ cause.defects).headOption
    maybeThrowable match {
      case Some(t) => throwable = t
      case None    => throwable = FiberFailure(cause)
    }
  }
  ()
}

changed to (v2.3.1)

val causeToThrowableDefault: Cause[Any] => Option[Throwable] = cause =>
  if (cause.isEmpty) {
    None
  } else {
    Some(FiberFailure(cause)) // here
  }
    
override def appendCause(cause: Cause[Any]): Unit = {
  throwable = causeToThrowable(cause).orNull
  ()
}

FiberFailure’s desciption :

This could be caused by some non-recoverable error, such as a defect or system error, by some typed error, or by interruption

I think some failures in cause are not case of FiberFailure. (it’s recoverable)

So i think the extracting cause’s failures code should put into where cause is not empty.
Like this :

val causeToThrowableDefault: Cause[Any] => Option[Throwable] = cause =>
  if (cause.isEmpty) {
    None
  } else {
    val maybeThrowable = (cause.failures.collect { case t: Throwable => t } ++ cause.defects).headOption
    maybeThrowable.orElse(Some(FiberFailure(cause)))
  }
    
override def appendCause(cause: Cause[Any]): Unit = {
  throwable = causeToThrowable(cause).orNull
  ()
}

If I missed something, please correct me. thanks :)

@justcoon
Copy link
Contributor

justcoon commented Sep 30, 2024

Hello @leejaeseung,

It is like you mentioning, every exception is wrapped with zio.FiberFailure (#811)

This was made for one reason, to have proper stack traces ZIO together with java code

package zio.logging.example

object Test {

  def foo(): Unit =
    bar()

  private def bar(): Unit =
    doe()

  private def doe(): Unit =
    throw new RuntimeException("test doe")
}
package zio.logging.example

import zio._
import zio.logging.backend.SLF4J

object Example2 extends ZIOAppDefault {

  override val bootstrap: ZLayer[ZIOAppArgs, Any, Environment] =
    Runtime.removeDefaultLoggers >>> SLF4J.slf4j

  def foo: ZIO[Any, Throwable, Unit] =
    for {
      _ <- bar
    } yield ()

  def bar: ZIO[Any, Throwable, Unit] =
    for {
      _ <- baz
    } yield ()

  def baz: ZIO[Any, Throwable, Unit] =
    ZIO.attempt(Test.foo())

  val run: ZIO[Any, Throwable, Unit] =
    foo.tapErrorCause(cause => ZIO.logErrorCause("ERROR", cause))
}

where log output (with current zio-logging version) will be

08:43:43.853 [ZScheduler-Worker-3] [] ERROR zio-slf4j-logger 
zio.FiberFailure: test doe
	at zio.logging.example.Test$.doe(Test.scala:12)
	at zio.logging.example.Test$.bar(Test.scala:9)
	at zio.logging.example.Test$.foo(Test.scala:6)
	at zio.logging.example.Example2$.$anonfun$baz$1(Example2.scala:22)
	at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18)
	at zio.ZIOCompanionVersionSpecific.$anonfun$attempt$1(ZIOCompanionVersionSpecific.scala:100)
	at zio.logging.example.Example2.baz(Example2.scala:22)
	at zio.logging.example.Example2.run(Example2.scala:24)
Caused by: java.lang.RuntimeException: test doe
	at zio.logging.example.Test$.doe(Test.scala:12)
	at zio.logging.example.Test$.bar(Test.scala:9)
	at zio.logging.example.Test$.foo(Test.scala:6)
	at zio.logging.example.Example2$.$anonfun$baz$1(Example2.scala:22)
	at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18)
	at zio.ZIOCompanionVersionSpecific.$anonfun$attempt$1(ZIOCompanionVersionSpecific.scala:100)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:999)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1067)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1097)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1162)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1097)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1162)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1162)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1097)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1162)
	at zio.internal.FiberRuntime.evaluateEffect(FiberRuntime.scala:413)
	at zio.internal.FiberRuntime.evaluateMessageWhileSuspended(FiberRuntime.scala:489)
	at zio.internal.FiberRuntime.drainQueueOnCurrentThread(FiberRuntime.scala:250)
	at zio.internal.FiberRuntime.run(FiberRuntime.scala:138)
	at zio.internal.ZScheduler$$anon$3.run(ZScheduler.scala:380)

with

val causeToThrowableDefault: Cause[Any] => Option[Throwable] = cause =>
  if (cause.isEmpty) {
    None
  } else {
    val maybeThrowable = (cause.failures.collect { case t: Throwable => t } ++ cause.defects).headOption
    maybeThrowable.orElse(Some(FiberFailure(cause)))
  }

will be

08:35:43.303 [ZScheduler-Worker-5] [] ERROR zio-slf4j-logger 
java.lang.RuntimeException: test doe
	at zio.logging.example.Test$.doe(Test.scala:12)
	at zio.logging.example.Test$.bar(Test.scala:9)
	at zio.logging.example.Test$.foo(Test.scala:6)
	at zio.logging.example.Example2$.$anonfun$baz$1(Example2.scala:23)
	at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18)
	at zio.ZIOCompanionVersionSpecific.$anonfun$attempt$1(ZIOCompanionVersionSpecific.scala:100)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:999)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1067)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1097)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1162)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1097)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1162)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1162)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1097)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1162)
	at zio.internal.FiberRuntime.evaluateEffect(FiberRuntime.scala:413)
	at zio.internal.FiberRuntime.evaluateMessageWhileSuspended(FiberRuntime.scala:489)
	at zio.internal.FiberRuntime.drainQueueOnCurrentThread(FiberRuntime.scala:250)
	at zio.internal.FiberRuntime.run(FiberRuntime.scala:138)
	at zio.internal.ZScheduler$$anon$3.run(ZScheduler.scala:380)

I agree that it is not best to have everything wrapped like zio.FiberFailure, it seemed the most reasonable solution for problem at that time.

Ideas for improvements are welcome. Thank you very much.

@leejaeseung
Copy link
Author

@justcoon Thank you for answer :)

I have searched codes and understood that ZIO Cause’s stack traces are missed if just extracting throwable.

I agree with showing Cause’s traces is important, too.

But I still worried about the fixed root exception. (it changes my alert message)

So, how about using suppressed exception?

Like this :

// A custom Exception To save Cause's stack traces. (copy of FiberFailure)
final case class SuppressedCauseException(cause: Cause[Any]) extends Throwable(null, null, true, false) {
  override def getMessage: String = cause.unified.headOption.fold("<unknown>")(_.message)

  override def getStackTrace(): Array[StackTraceElement] = {
    cause.unified.headOption.fold[Chunk[StackTraceElement]](Chunk.empty)(_.trace).toArray
  }
}

// just simple implements. (please ignore details - like unsafe .get)
val causeToThrowableDefault: Cause[Any] => Option[Throwable] = cause =>
    if (cause.isEmpty) {
      None
    } else {

      val maybeThrowable = (cause.failures.collect { case t: Throwable => t } ++ cause.defects).headOption

      val failure = SuppressedCauseException(cause)

      if(!maybeThrowable.get.getSuppressed.contains(failure)) {
        maybeThrowable.get.addSuppressed(failure)
      }

      Some(maybeThrowable.get)
    }

And it’s traces look like this

java.lang.RuntimeException: test doe
	at Test$.doe(Test.scala:11)
	at Test$.bar(Test.scala:7)
	at Test$.foo(Test.scala:4)
	at Example2$.$anonfun$baz$1(Main.scala:33)
	at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18)
	at zio.ZIOCompanionVersionSpecific.$anonfun$attempt$1(ZIOCompanionVersionSpecific.scala:100)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:999)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1067)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1097)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1162)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1097)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1162)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1162)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1097)
	at zio.internal.FiberRuntime.runLoop(FiberRuntime.scala:1162)
	at zio.internal.FiberRuntime.evaluateEffect(FiberRuntime.scala:413)
	at zio.internal.FiberRuntime.evaluateMessageWhileSuspended(FiberRuntime.scala:489)
	at zio.internal.FiberRuntime.drainQueueOnCurrentThread(FiberRuntime.scala:250)
	at zio.internal.FiberRuntime.run(FiberRuntime.scala:138)
	at zio.internal.ZScheduler$$anon$3.run(ZScheduler.scala:380)
	Suppressed: zio.logging.backend.SuppressedCauseException: test doe
		at Test$.doe(Test.scala:11)
		at Test$.bar(Test.scala:7)
		at Test$.foo(Test.scala:4)
		at Example2$.$anonfun$baz$1(Main.scala:33)
		at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18)
		at zio.ZIOCompanionVersionSpecific.$anonfun$attempt$1(ZIOCompanionVersionSpecific.scala:100)
		at <empty>.Example2.baz(Main.scala:33)
		at <empty>.Example2.run(Main.scala:39)

it may seems a little weird.
(wrap cause to exception, and add it into throwable extracted from cause)

But i think it may be one of the possible options to maintain the root exception.

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

2 participants