Tracing doodles#50
Conversation
|
|
||
| object TracedLambda { | ||
| def apply[F[_]: MonadCancelThrow, G[_], Event: HasKernel, Result](entryPoint: EntryPoint[F])( | ||
| lambda: Trace[G] => Lambda[G, Event, Result])( |
There was a problem hiding this comment.
I'm not sure yet how I feel about injecting Trace[G] like this. It's a bit awkward, but I think it's a cleaner way to support IOLocal-based tracing, or generally tracing in a generic G.
| object HasKernel extends HasKernelLowPriority { | ||
| @inline def apply[E](implicit hk: HasKernel[E]): hk.type = hk | ||
|
|
||
| implicit val apiGateProxyEventV2Kernel: HasKernel[ApiGatewayProxyEventV2] = | ||
| e => Kernel(e.headers) | ||
| } |
There was a problem hiding this comment.
One of the nice things about having all the events together in the same module is it makes it easy to provide these implicits. Although I'm doubtful if any will have a special implementation except this one.
| def viaSpan[F[_]: MonadCancelThrow, Event: HasKernel, Result](entryPoint: EntryPoint[F])( | ||
| lambda: Span[F] => Lambda[F, Event, Result]): Lambda[F, Event, Result] = { |
There was a problem hiding this comment.
@bpholt Here's an alternative TracedLambda middleware, which only requires a single effect F and gives you a Span[F] instead of a Trace[G].
Does this mean less boilerplate? It depends. If all you wanted all along was a Span[F], then this is perfect.
But, if you actually want a Trace instance (e.g., to use natchez http4s middleware), then you have a couple options.
- Hardcode the traced effect (either
KleisliorIO) and run it with the given span. This is straightforward enough, but you lose some abstraction power. - Re-invent
LiftTrace[F, G]😉
There was a problem hiding this comment.
This works pretty well! Let's run with it.
There was a problem hiding this comment.
Cool! So you prefer this one to the other in terms of Trace[G]? I think I'd like to keep both forms, but maybe this should be the primary :)
There was a problem hiding this comment.
I'm using it wrapped by XRayTracedLambda:
package feral.lambda.natchez.xray
import _root_.natchez._
import _root_.natchez.noop.NoopSpan
import _root_.natchez.xray.{XRay, XRayEnvironment}
import cats.data._
import cats.effect.std.Random
import cats.effect.{Trace => _, _}
import feral.lambda._
import feral.lambda.natchez._
object XRayTracedLambda {
def apply[F[_]: Async, Event: HasKernel, Result](installer: Resource[Kleisli[F, Span[F], *], Lambda[Kleisli[F, Span[F], *], Event, Result]])
(implicit LT: LiftTrace[F, Kleisli[F, Span[F], *]]): Resource[F, Lambda[F, Event, Result]] =
installer.mapK(Kleisli.applyK(new NoopSpan[F]())).flatMap(XRayTracedLambda(_))
def apply[F[_]: Async, Event: HasKernel, Result](lambda: Span[F] => Lambda[F, Event, Result]): Resource[F, Lambda[F, Event, Result]] =
Resource.eval(Random.scalaUtilRandom[F]).flatMap { implicit random =>
Resource
.eval(XRayEnvironment[F].daemonAddress)
.flatMap {
case Some(addr) => XRay.entryPoint[F](addr)
case None => XRay.entryPoint[F]()
}
.map(TracedLambda.viaSpan(_)(lambda))
}
def apply[F[_]: Async, G[_], Event: HasKernel, Result](lambda: Lambda[G, Event, Result])
(implicit LT: LiftTrace[F, G]): Resource[F, Lambda[F, Event, Result]] =
XRayTracedLambda { span => (event, context) =>
LT.run(span) { _ => lambda(event, context.mapK(LT.liftK)) }
}
}Here's an example of a Lambda using that middleware: https://github.com/Dwolla/rabbitmq-topology-backup/blob/xray/src/main/scala/com/dwolla/rabbitmq/topology/LambdaHandler.scala#L24-L42
Basically:
def handler[F[_] : Async : Trace]: Resource[F, lambda.Lambda[F, Event, INothing]] = …
override def run: Resource[IO, lambda.Lambda[IO, Event, INothing]] =
XRayTracedLambda(handler[Kleisli[IO, Span[IO], *]])There was a problem hiding this comment.
Also, here's a custom CFN resource using it: https://github.com/Dwolla/postgresql-init-custom-resource/blob/feral/db/src/main/scala/com/dwolla/postgres/init/Handler.scala#L33-L52
There was a problem hiding this comment.
@bpholt what would you think about adding this stuff directly to the XRay integration in Natchez? Seems like common boilerplate?
Resource.eval(Random.scalaUtilRandom[F]).flatMap { implicit random =>
Resource
.eval(XRayEnvironment[F].daemonAddress)
.flatMap {
case Some(addr) => XRay.entryPoint[F](addr)
case None => XRay.entryPoint[F]()
}Also, what's the thread-safety story with the Random :)
|
I think this is in a pretty good state, pending what happens with typelevel/natchez#448. Is there anything we want to try or do before merging (and maybe publishing snapshots)? |
|
Also, I have the XRay middleware updated to use this branch, but it doesn't build since the XRay support in Natchez hasn't been published yet. I was thinking if we plan to merge this branch soon, I can push it up as a followup PR (in its own module? I'm not sure how we want to organize things yet) |
|
Awesome! Actually, what I'd like to do is split the PRs again which got mixed up while we were experimenting :)
All of this should be publishable right now and I'm happy to do so while Daniel looks at (1).
Ooh, I'll take a look at this shortly! I thought we could get to a place where we don't need a specific integration, but maybe this was too ambitious 😆
I'm like wayyy into over-modularization, so please push back on this. But yes that's my first instinct! |
|
@bpholt I just published everything in this branch, propagating now. |
|
Everything here now split into: |
Fork of #45 for me to doodle with how to integrate natchez with feral.