natchez icon indicating copy to clipboard operation
natchez copied to clipboard

Add Trace.spanR

Open rossabaker opened this issue 3 years ago • 7 comments
trafficstars

Adds a way to span a resource, from acquisition through release.

See #514.

rossabaker avatar Feb 28 '22 15:02 rossabaker

I am in favor of continuing with #527 over this one, particularly if https://github.com/typelevel/fs2/pull/2843 works out.

rossabaker avatar Mar 10 '22 03:03 rossabaker

Is natchez okay to take on an fs2 dependency?

armanbilge avatar Mar 10 '22 03:03 armanbilge

The repo already depends on it for xray. Not sure it's smart for core to take it on, but an fs2 module wouldn't hurt.

(Half-baked idea: as @zmccoy is fond of saying, tracing is most important around network calls. Having a natchez-fs2 that traced some of its io functions would be cool.)

rossabaker avatar Mar 10 '22 03:03 rossabaker

It turns out a spanS is a lot easier than spanR for the Kleisli instance. translate gives us a hook to thread the state (here, just a String) through to the outputs:

    def spanS[F[_], A](name: String)(s: Stream[Kleisli[F, String, *], A]): Stream[Kleisli[F, String, *], A] =
      s.translate(
        new (Kleisli[F, String, *] ~> Kleisli[F, String, *]) {
          def apply[B](k: Kleisli[F, String, B]) = k.local(_ => name)
        }
      )

    val s = Stream.eval(Kleisli.ask[IO, String])
    (s ++ spanS("child")(s) ++ s)
      .compile
      .toList
      .run("root")
      .flatMap(IO.println)

prints List("root", "child", "root").

rossabaker avatar Mar 11 '22 03:03 rossabaker

Tracing can be viewed as an exchange F ~> F of an untraced effect F with a traced effect F. This is witnessed by the polymorphic function that results from partially applying Trace.span.

If we redefine spanR as something that returns a resource of F ~> F, we decouple how we propagate the span from the structure of transformed effects. Thus, spanR supports a Trace[Resource[F, *], *], Trace[Stream[F, *], *], and any other "dynamically scoped" monad, all without introducing a second type class.

Expectation is that:

spanR(name).surround(fa) <-> span(name)(fa)

Caveats:

  • I haven't tested any of these.
  • The transformer instances still all require MonadCancelThrow.
  • Trace[Resource[F, *]] is still only ambient for acquisition and release, not use. That's the price of stateless instances.
  • Any Stream interruption hacks would have to be universal, because we're not defining a specific spanS.

rossabaker avatar Mar 12 '22 18:03 rossabaker

The API for tracing a stream (example from https://github.com/armanbilge/bayou/issues/1) ends up looking like

  def stream(implicit trace: Trace[IO]): Stream[IO, Unit] = {
    Trace[Stream[IO, *]].span("new_root")(
      Stream(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
        .covary[IO]
        .chunkN(3)
        .flatMap { chunk =>
          Trace[Stream[IO, *]].span("child")(
            Stream.eval(trace.put("vals" -> chunk.toList.mkString(","))) >>
            Stream.sleep[IO](1.second)
          )
        }
    )
  }

To weave in and out of Stream, we have to accept the simpler Trace[IO] and then summon a Trace[Stream[IO, *]]. We could just reference trace if we made spanS a derived method on Trace, but then we'd need a MonadCancelThrow member, and fs2 would be forced into natchez-core.

rossabaker avatar Mar 12 '22 23:03 rossabaker

Just figured I'd share that we've been using a custom Trace at work for the last few months that is essentially a combination of Bayou and the F ~> F implementation here and it's been working well for us.

I've open sourced it here. I don't intend to promote yet another tracing library. I'd much rather these concepts make it into Natchez.

ryanmiville avatar Jun 09 '22 21:06 ryanmiville

Any thoughts on this design versus #527 now that some time has passed? @armanbilge @rossabaker

mpilquist avatar Nov 19 '22 14:11 mpilquist

I moved onto otel4s and don't remember all the nuances, but I think the basic tradeoff was that this behaves similarly between stateless and stateful instances, and #527 was able to make the resource span the parent of use at the expense of stateless instances (and an extra type class.)

Thanks for picking it up.

rossabaker avatar Nov 21 '22 20:11 rossabaker