@vkostyukov vkostyukov released this Jul 12, 2018 · 119 commits to master since this release

Assets 2

This release bumps Finagle to 18.7 and introduces "tracing" allowing users to identify what endpoint (distinguished by path) was matched.

Tracing for Endpoints

It's been a known problem for quite a while: there is no way to tell what endpoint (out of the coproduct of endpoints) was matched. This has been making it non-trivial to add telemetry and instrumentation into Finch applications.

In #957, we introduced a new data type, io.finch.Trace, that models a path over which an endpoint is matched and is returned as part of EndpointResult. For example:

scala> import io.finch._, io.finch.syntax._
import io.finch._
import io.finch.syntax._

scala> val foo = get("foo" :: "bar" :: path[String]) { s: String => Ok(s) }
foo: io.finch.Endpoint[String] = GET /foo :: bar :: :string

scala> val bar = get("bar" :: "foo" :: path[Int]) { i: Int => Ok(i) }
bar: io.finch.Endpoint[Int] = GET /bar :: foo :: :int

scala> val fooBar = foo :+: bar
fooBar: io.finch.Endpoint[String :+: Int :+: shapeless.CNil] = (GET /foo :: bar :: :string :+: GET /bar :: foo :: :int)

scala> fooBar(Input.get("/foo/bar/baz")).trace
res0: Option[io.finch.Trace] = Some(/foo/bar/:string)

scala> fooBar(Input.get("/bar/foo/10")).trace
res1: Option[io.finch.Trace] = Some(/bar/foo/:int)

In #960, we allowed users to capture such Traces on their side (presumably in Finagle filters) using Twitter Future Locals. The usage API looks as follows.

scala> val foo = get("foo" :: path[String]) { s: String => Ok(s) }
foo: io.finch.Endpoint[String] = GET /foo :: :string

scala> import com.twitter.finagle.http.Request
import com.twitter.finagle.http.Request

scala> val s = foo.toServiceAs[Text.Plain]
s: com.twitter.finagle.Service[com.twitter.finagle.http.Request,com.twitter.finagle.http.Response] = io.finch.ToService$$anon$4$$anon$2

scala> Trace.capture { s(Request("/foo/bar")).map(_ => Trace.captured) }
res0: com.twitter.util.Future[io.finch.Trace] = Promise@406161512(state=Done(Return(/foo/:string)))

A couple of things worth noting with regards to the new machinery:

  • There is no need to explicitly enable it in Bootstrap options, a Trace will always be captured within a Trace.capture context.
  • There is no need to wait for a service's future to resolve before retrieving a captured Trace as it's immediately available once endpoint is matched (i.e., after the service(req) call).
  • Obviously materializing a new structure on each request comes at the cost of allocations/running time. We, however, haven't observed any significant overhead in Finch's benchmarks.