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 Trace
s 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, aTrace
will always be captured within aTrace.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 theservice(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.