Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,8 @@ lazy val zioHttpBenchmarks = (project in file("zio-http-benchmarks"))
.settings(
libraryDependencies ++= Seq(
// "com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % "1.1.0",
"com.softwaremill.sttp.tapir" %% "tapir-http4s-server" % "1.13.10",
"com.softwaremill.sttp.tapir" %% "tapir-json-circe" % "1.13.10",
"com.softwaremill.sttp.tapir" %% "tapir-http4s-server" % "1.13.13",
"com.softwaremill.sttp.tapir" %% "tapir-json-circe" % "1.13.13",
"com.softwaremill.sttp.client3" %% "core" % "3.11.0",
// "dev.zio" %% "zio-interop-cats" % "3.3.0",
"org.slf4j" % "slf4j-api" % "2.0.17",
Expand Down
23 changes: 23 additions & 0 deletions zio-http/jvm/src/test/scala/zio/http/RouteSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,29 @@ object RouteSpec extends ZIOHttpSpec {
),
)
},
test("sandbox does not log requests with failures of type Response") {
val route =
Method.GET / "foo" -> Handler.fail(Response.badRequest)

for {
_ <- route.sandbox.toRoutes.runZIO(Request.get(url"/foo"))
entries <- ZTestLogger.logOutput
} yield assertTrue(
!entries.exists(e => e.message().contains("Unhandled exception in request handler")),
)
},
test("sandbox does not log requests with middleware failures of type Response") {
val route =
Method.GET / "foo" -> handler(Response.ok)

val routes = route.sandbox.toRoutes @@ Middleware.fail(Response.badRequest)
for {
_ <- routes.runZIO(Request.get(url"/foo"))
entries <- ZTestLogger.logOutput
} yield assertTrue(
!entries.exists(e => e.message().contains("Unhandled exception in request handler")),
)
},
test("sandbox does not log for successful requests") {
val route =
Method.GET / "foo" -> handler(Response.ok)
Expand Down
30 changes: 30 additions & 0 deletions zio-http/jvm/src/test/scala/zio/http/ServerErrorLoggingSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ object ServerErrorLoggingSpec extends ZIOSpecDefault {
Method.GET / "fail" -> Handler.fail(new RuntimeException("typed failure")),
).sandbox

// Routes that fail with a response, should not produce error logs
val routesFailResponse = Routes(
Method.GET / "fail-response" -> Handler.fail(Response.fromThrowable(new RuntimeException("typed failure"))),
).sandbox

// Routes that fail with a response because of Middleware, should not produce error logs
val routesFailMiddlewareResponse = (Routes(
Method.GET / "fail-middleware-response" -> Handler.ok,
) @@ Middleware.fail(Response.fromThrowable(new RuntimeException("typed failure")))).sandbox

// Routes that succeed - should not produce error logs
val routesOk = Routes(
Method.GET / "ok" -> Handler.ok,
Expand Down Expand Up @@ -72,6 +82,26 @@ object ServerErrorLoggingSpec extends ZIOSpecDefault {
errorLog.get.logLevel == LogLevel.Error,
)
},
test("sandbox does not log requests with failures of type Response") {
for {
port <- Server.installRoutes(routesFailResponse)
_ <- ZIO.scoped {
Client.streaming(Request.get(s"http://localhost:$port/fail-response")).flatMap(_.ignoreBody)
}
entries <- ZTestLogger.logOutput
errorLog = entries.find(_.message() == "Unhandled exception in request handler")
} yield assertTrue(errorLog.isEmpty)
},
test("sandbox does not log requests with middleware failures of type Response") {
for {
port <- Server.installRoutes(routesFailMiddlewareResponse)
_ <- ZIO.scoped {
Client.streaming(Request.get(s"http://localhost:$port/fail-middleware-response")).flatMap(_.ignoreBody)
}
entries <- ZTestLogger.logOutput
errorLog = entries.find(_.message() == "Unhandled exception in request handler")
} yield assertTrue(errorLog.isEmpty)
},
test("sandbox does not log for successful requests") {
for {
port <- Server.installRoutes(routesOk)
Expand Down
11 changes: 10 additions & 1 deletion zio-http/shared/src/main/scala/zio/http/Handler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -550,10 +550,19 @@ sealed trait Handler[-R, +Err, -In, +Out] { self =>

final def sandbox(implicit trace: Trace): Handler[R, Response, In, Out] =
self.mapErrorCauseZIO { cause =>
ZIO.logErrorCause("Unhandled exception in request handler", cause) *>
logUnhandledError(cause) *>
ErrorResponseConfig.configRef.getWith(cfg => Exit.fail(Response.fromCause(cause, cfg)))
}

/**
* Log an unhandled error unless the cause is already a response
*/
private def logUnhandledError(cause: Cause[Any]): UIO[Unit] =
cause.failureOrCause match {
case Left(_: Response) => ZIO.unit
case _ => ZIO.logErrorCause("Unhandled exception in request handler", cause)
}

final def status(implicit ev: Out <:< Response, trace: Trace): Handler[R, Err, In, Status] =
self.map(_.status)

Expand Down
11 changes: 10 additions & 1 deletion zio-http/shared/src/main/scala/zio/http/Route.scala
Original file line number Diff line number Diff line change
Expand Up @@ -410,10 +410,19 @@ sealed trait Route[-Env, +Err] { self =>
*/
final def sandbox(implicit trace: Trace): Route[Env, Nothing] =
handleErrorCauseZIO { cause =>
ZIO.logErrorCause("Unhandled exception in request handler", cause) *>
logUnhandledError(cause) *>
ErrorResponseConfig.configRef.getWith(cfg => Exit.succeed(Response.fromCause(cause, cfg)))
}

/**
* Log an unhandled error unless the cause is already a response
*/
private def logUnhandledError(cause: Cause[Any]): UIO[Unit] =
cause.failureOrCause match {
case Left(_: Response) => ZIO.unit
case _ => ZIO.logErrorCause("Unhandled exception in request handler", cause)
}

def toHandler(implicit ev: Err <:< Response, trace: Trace): Handler[Env, Response, Request, Response]

/**
Expand Down