Skip to content

Commit c231233

Browse files
authored
Merge pull request #60 from ovotech/add-fallthrough-to
Add fallthroughTo so that HttpRoutes and HttpApp can be composed
2 parents bfd216b + 68cd69d commit c231233

File tree

6 files changed

+105
-4
lines changed

6 files changed

+105
-4
lines changed

docs/docs/docs/natchez-fs2.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ i.e. Kafka consumers using FS2.
1616
val natchezExtrasVersion = "@VERSION@"
1717

1818
libraryDependencies ++= Seq(
19-
"com.ovoenergy" %% "natchez-fs2" % natchezExtrasVersion
19+
"com.ovoenergy" %% "natchez-extras-fs2" % natchezExtrasVersion
2020
)
2121
```
2222

docs/docs/docs/natchez-http4s.md

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ When it is merged this module will continue to exist but as a wrapper that adds
1515
val natchezExtrasVersion = "@VERSION@"
1616

1717
libraryDependencies ++= Seq(
18-
"com.ovoenergy" %% "natchez-fs2" % natchezExtrasVersion
18+
"com.ovoenergy" %% "natchez-extras-http4s" % natchezExtrasVersion
1919
)
2020
```
2121

@@ -108,6 +108,64 @@ Running the above app and hitting the `POST` endpoint should generate a trace li
108108

109109
![datadog trace]({{site.baseurl}}/img/example-http-trace.png)
110110

111+
112+
## Tracing only some routes
113+
114+
Often you don't want to trace all of your routes, for example if you have a healthcheck route
115+
that is polled by a load balancer every few seconds you may wish to exclude it from your traces.
116+
117+
You can do this using `.fallthroughTo` provided in the `syntax` package which allows the combination
118+
of un-traced `HttpRoutes[F]` and the `HttpApp[F]` that the tracing middleware returns:
119+
120+
```scala mdoc
121+
import cats.data.Kleisli
122+
import cats.effect.{ExitCode, IO, IOApp, Resource}
123+
import com.ovoenergy.natchez.extras.datadog.Datadog
124+
import com.ovoenergy.natchez.extras.http4s.Configuration
125+
import com.ovoenergy.natchez.extras.http4s.server.TraceMiddleware
126+
import com.ovoenergy.natchez.extras.http4s.server.syntax.KleisliSyntax
127+
import natchez.{EntryPoint, Span}
128+
import org.http4s._
129+
import org.http4s.client.blaze.BlazeClientBuilder
130+
import org.http4s.dsl.io._
131+
import org.http4s.server.blaze.BlazeServerBuilder
132+
import org.http4s.syntax.kleisli._
133+
134+
import scala.concurrent.ExecutionContext.global
135+
136+
object Main extends IOApp {
137+
138+
type TraceIO[A] = Kleisli[IO, Span[IO], A]
139+
val conf: Configuration[IO] = Configuration.default()
140+
141+
val datadog: Resource[IO, EntryPoint[IO]] =
142+
for {
143+
httpClient <- BlazeClientBuilder[IO](global).withDefaultSslContext.resource
144+
entryPoint <- Datadog.entryPoint(httpClient, "example-http-api", "default-resource")
145+
} yield entryPoint
146+
147+
val healthcheck: HttpRoutes[IO] =
148+
HttpRoutes.of { case GET -> Root / "health" => Ok("healthy") }
149+
150+
val application: HttpRoutes[TraceIO] =
151+
HttpRoutes.pure(Response(status = Status.InternalServerError))
152+
153+
def run(args: List[String]): IO[ExitCode] =
154+
datadog.use { entryPoint =>
155+
156+
val combinedRoutes: HttpApp[IO] =
157+
healthcheck.fallthroughTo(TraceMiddleware(entryPoint, conf)(application.orNotFound))
158+
159+
BlazeServerBuilder[IO](global)
160+
.withHttpApp(combinedRoutes)
161+
.bindHttp(port = 8080)
162+
.serve
163+
.compile
164+
.lastOrError
165+
}
166+
}
167+
```
168+
111169
## Configuration
112170

113171
Given that every HTTP API is likely to have different tracing requirements `natchez-http4s` attempts to be as configurable as possible.

docs/docs/docs/natchez-log4cats.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ the [agentless logging](https://docs.datadoghq.com/logs/log_collection/java/?tab
1919
val natchezExtrasVersion = "@VERSION@"
2020

2121
libraryDependencies ++= Seq(
22-
"com.ovoenergy" %% "natchez-fs2" % natchezExtrasVersion
22+
"com.ovoenergy" %% "natchez-extras-log4cats" % natchezExtrasVersion
2323
)
2424
```
2525

docs/docs/docs/natchez-testkit.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ that check your application is sending the right information to Natchez.
1414
val natchezExtrasVersion = "@VERSION@"
1515

1616
libraryDependencies ++= Seq(
17-
"com.ovoenergy" %% "natchez-testkit" % natchezExtrasVersion % Test
17+
"com.ovoenergy" %% "natchez-extras-testkit" % natchezExtrasVersion % Test
1818
)
1919
```
2020

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.ovoenergy.natchez.extras.http4s.server
2+
3+
import cats.Monad
4+
import cats.data.{Kleisli, OptionT}
5+
6+
object syntax {
7+
/**
8+
* Given an `A => F[Option[B]]` and an `A => F[B]` run the first function
9+
* and if it returns F[None] then fall through to the second function.
10+
* This is useful for composing HttpRoutes[F] with HttpApp[F]
11+
*/
12+
implicit class KleisliSyntax[F[_]: Monad, A, B](a: Kleisli[OptionT[F, *], A, B]) {
13+
def fallthroughTo(b: Kleisli[F, A, B]): Kleisli[F, A, B] = Kleisli(r => a.run(r).getOrElseF(b.run(r)))
14+
}
15+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.ovoenergy.natchez.extras.http4s.server
2+
3+
import cats.effect.IO
4+
import com.ovoenergy.natchez.extras.http4s.server.syntax._
5+
import org.http4s.Status.{InsufficientStorage, Ok}
6+
import org.http4s.{HttpApp, HttpRoutes, Request, Response}
7+
import org.scalatest.matchers.should.Matchers
8+
import org.scalatest.wordspec.AnyWordSpec
9+
10+
class SyntaxTest extends AnyWordSpec with Matchers {
11+
12+
"fallThroughTo" should {
13+
14+
"Call the second Kleisli if the first returns None" in {
15+
val routes: HttpRoutes[IO] = HttpRoutes.empty
16+
val app: HttpApp[IO] = HttpApp.pure(Response(status = Ok))
17+
val result = routes.fallthroughTo(app).run(Request()).unsafeRunSync()
18+
result.status shouldBe Ok
19+
}
20+
21+
"Not call the second Kleisli if the first returns a result" in {
22+
val routes: HttpRoutes[IO] = HttpRoutes.pure(Response(status = Ok))
23+
val app: HttpApp[IO] = HttpApp.pure(Response(status = InsufficientStorage))
24+
val result = routes.fallthroughTo(app).run(Request()).unsafeRunSync()
25+
result.status shouldBe Ok
26+
}
27+
}
28+
}

0 commit comments

Comments
 (0)