Logging of errors described in errorOut

Hi there!
I wanted to add business logic error logging to my service.
I was thinking of implementing logging at the interceptor level, but I ran into the problem that it is impossible to get error information at the interceptor level.
I have something like this endpoint:

  val myEndpoint =
        query[String]("appVersion").description("example - 5.18") and
        query[String]("platform").description("ios or android") and
        query[String]("deviceId").description("unique id of device")
          oneOfVariant(jsonBody[InternalServerErrorResponse] and statusCode(StatusCode.InternalServerError)),
          oneOfVariant(jsonBody[BadRequestResponse] and statusCode(StatusCode.BadRequest))
      .in("api" / "v1" / "mb")
      .description("List of user's phones")
      .zServerLogic {
        case (_, siebelId, _, _, _) => handler.getConnectedPhones(MbChatId(siebelId.value))

And here is such a handler:

def getConnectedPhones(siebelId: MbChatId): IO[InternalServerErrorResponse, PhonesResponseRaw] =
    for {
      basicAuth <- authService.getAuth
      phones <- secretariesService.getPhones(basicAuth, siebelId).mapError(_ => InternalServerErrorResponse("some additional info"))
    } yield PhonesResponseRaw(phones)

If ZIO fails with an error that the endpoint did not expect, then ServerLog.exception will be called for it, where you can log the error. But if ZIO fails with an error that is defined in .errorOut, then the request is considered successfully processed and error information cannot be received.
It would be possible to remove .errorOut and force the handler to return Task[A], but I don’t want to lose typing.
Is there a way at the interceptor level to understand that the effect ended with an Error exception? Or maybe there is some other approach to error logging?

There is no dedicated mechanism for handling error responses received as ZIO errors, only for “failed effect exceptions” as you mentioned.
Assuming you’re using zio-http, if you’re OK with handling the response as a byte array, you might write an interceptor like:

  def logBodyAsError(bytes: Chunk[Byte], contentLength: Option[Long]): Eff[Unit] =
    ZIO.logError(new String(bytes.toArray, StandardCharsets.UTF_8))

  val logZioErrorInterceptor = RequestInterceptor
    .transformResultEffect(new RequestResultEffectTransform[Eff] {
      override def apply[B](request: ServerRequest, result: Eff[RequestResult[B]]): Eff[RequestResult[B]] = {
        result.flatMap {
          case RequestResult.Response(ServerResponse(code, _, Some(Right(ZioRawHttpResponseBody(bytes, contentLength))), _))
              if !code.isSuccess =>            
            logBodyAsError(bytes, contentLength).flatMap(_ => result)
          case _ => result

(where Eff is a RIO[Session, A] in my showcase app where I tested it, but I guess it could be just a Task if needed).