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

How to catch exceptions? #543

Open
omidb opened this issue Aug 15, 2023 · 6 comments
Open

How to catch exceptions? #543

omidb opened this issue Aug 15, 2023 · 6 comments

Comments

@omidb
Copy link

omidb commented Aug 15, 2023

I have a ZIO grpc server running and somewhere in my code there is an ??? unimplemented exception. I get now cause or anything in my call and I cannot log the error or cause. Using the debugger I see that it ends up in here:

).onExit(ex => call.close(ListenerDriver.exitToStatus(ex), requestContext.responseMetadata.metadata).ignore)

of course, wrapping everything in ZIO.attempt bubbles up the error but that is not a good pattern to follow.

What am I missing?

@thesamet
Copy link
Contributor

If I understand correctly, you are looking for a way to handle exceptions systematically throughout your app. Have you looked into accomplishing this with a ZTransform similar to the one here: https://scalapb.github.io/zio-grpc/docs/next/decorating

@omidb
Copy link
Author

omidb commented Aug 16, 2023

here is a better version of my sample using the samples in the repo:

package zio_grpc.examples.helloworld

import io.grpc.StatusException
import scalapb.zio_grpc.ServerMain
import scalapb.zio_grpc.ServiceList
import zio._
import zio.Console._

import io.grpc.examples.helloworld.helloworld.ZioHelloworld.Greeter
import io.grpc.examples.helloworld.helloworld.{HelloReply, HelloRequest}

import io.grpc.StatusException
import scalapb.zio_grpc.{RequestContext, ZTransform}
import zio._
import zio.stream.ZStream

class LoggingTransform extends ZTransform[Any, RequestContext] {

  def logCause(rc: RequestContext, cause: Cause[StatusException]): UIO[Unit] =
    printLine(rc.toString()).orDie

  def accessLog(rc: RequestContext): UIO[Unit] = {
    printLine(rc.toString()).orDie
  }

  override def effect[A](
      io: Any => ZIO[Any, StatusException, A]
  ): RequestContext => ZIO[Any, StatusException, A] = { rc =>
    io(rc).zipLeft(accessLog(rc)).tapErrorCause(logCause(rc, _))
  }

  override def stream[A](
      io: Any => ZStream[Any, StatusException, A]
  ): RequestContext => ZStream[Any, StatusException, A] = { rc =>
    (io(rc) ++ ZStream.fromZIO(accessLog(rc)).drain).onError(logCause(rc, _))
  }
}

object GreeterImpl extends Greeter {

  def parse(value: String) = {
    value match {
      case "test" => ???
      case "fail" =>
        ZIO.fail(new StatusException(io.grpc.Status.INVALID_ARGUMENT))
      case _ =>
        ZIO.succeed(HelloReply(s"Parsed, ${value}"))
    }
  }
  def sayHello(
      request: HelloRequest
  ): ZIO[Any, StatusException, HelloReply] = {

    parse(request.name).logError("errr") *> printLine(
      s"Got request: $request"
    ).orDie zipRight
      ZIO.succeed(HelloReply(s"Hello, ${request.name}"))
  }
}

object HelloWorldServer extends ServerMain {

  val decoratedService =
    GreeterImpl.transform(new LoggingTransform)
  def services: ServiceList[Any] = ServiceList.add(decoratedService)
}

@thesamet I tested with and without transform.

basically there is no way to log non-zio exceptions. In a simple Zio code you get an exception in the console. In Akka or other places, we get the exception in the console or logs and server continue to work. Is my question more clear now?

@thesamet
Copy link
Contributor

thesamet commented Aug 17, 2023

I'd like to investigate more on what would be a consistent behavior with other ZIO libraries, however in the meantime, if you expect your effects might throw exception maybe the transformer can catch, handle and convert them to values:

  override def effect[A](
     io: Any => ZIO[Any, StatusException, A]
  ): RequestContext => ZIO[Any, StatusException, A] = { rc =>
    try { io(rc) }
    catch {
      case t: Throwable =>
        printLine(t.toString) *> ZIO.fail(t)
    }
  }

@omidb
Copy link
Author

omidb commented Aug 17, 2023

yep, this does the job:

override def effect[A](
      io: Any => ZIO[Any, StatusException, A]
  ): RequestContext => ZIO[Any, StatusException, A] = { rc =>
    try { io(rc) }
    catch {
      case t: Throwable =>
        (ZIO.logError(t.getMessage()) *> ZIO.fail(t)).mapError { t =>
          new StatusException(Status.INTERNAL)
        }

    }
  }

@thesamet
Copy link
Contributor

thesamet commented Aug 17, 2023 via email

@omidb
Copy link
Author

omidb commented Aug 17, 2023

@thesamet it would be great to have a catch all to at least be able to log everything

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