census-ecosystem / opencensus-scala

A scala wrapper for the opencensus-java library
Apache License 2.0
52 stars 19 forks source link

Module for tagless-final & cats-effect #31

Open fiadliel opened 6 years ago

fiadliel commented 6 years ago

Hi, would you be interested in code to make it easier to work with tagless-final style FP programming, and with cats-effect (FP effect monad)?

The current tracing API is designed for Future users, and isn't very idiomatic for FP.

I would be thinking of something like:

An API for Span

e.g.

trait Span[F[_]] {
  def spanContext: SpanContext
  def end(implicit F: Sync[F]): F[Unit]
  def addAnnotation(description: String)(implicit F: Sync[F]): F[Unit]
  def openCensusSpan: io.opencensus.trace.Span
}

An API for creating spans

e.g.

trait SpanBuilder[F[_]] {
  def rootSpan(spanName: String): F[Span[F]]
  def spanWithRemoteParent(spanName: String,
                           parentSpanContext: Option[SpanContext]): F[Span[F]]
  def spanWithLocalParent(spanName: String, parentSpan: Span[F]): F[Span[F]]
  def threadLocalSpan: F[Span[F]]
}

An API for trace propagation which is similar to Kleisli, i.e. a wrapper around Span[F] => F[A]

e.g.

class Trace[F[_], A](val underlying: Span[F] => F[A]) extends AnyVal {
  def withChildSpan(spanName: String)(
      implicit F: Sync[F],
      SpanBuilder: SpanBuilder[F]): Trace[F, A] =
    Trace(
      parentSpan =>
        F.bracket(SpanBuilder.spanWithLocalParent(spanName, parentSpan))(
          underlying)(_.end))
}

object Trace {
  def apply[F[_], A](fa: Span[F] => F[A]): Trace[F, A] = new Trace(fa)
  def apply[F[_], A](fa: Kleisli[F, Span[F], A]): Trace[F, A] = Trace(fa.run)
  def liftF[F[_], A](fa: F[A]): Trace[F, A] = Trace(_ => fa)
}

An implementation of Trace for the Sync typeclass (this provides map, flatMap, pure, delay, etc.)

e.g.

implicit def traceSyncInstance[F[_]](implicit F: Sync[F]): Sync[Trace[F, ?]] =
  new Sync[Trace[F, ?]] {
     ...
  }
fiadliel commented 6 years ago

(I was working on this idea independently, before I found your project. It can still be done as a separate project if you don't see it fitting into your one.)

Sebruck commented 6 years ago

I love the idea and I think this is related to https://github.com/Sebruck/opencensus-scala/issues/23

Would you like to contribute somethings? Otherwise @yannick-cw and me will for sure pick this up and build something as soon as we have time.

fiadliel commented 6 years ago

I have some local code, I'll try and submit a PR in the next few days.

I'd been thinking about this for a while, coming from the position of how gRPC manages context: I'm pretty sure that we need to use lexical scope when dealing with async APIs (grpc-java uses ThreadLocal values by default, which don't interoperate well in async world, considering the various thread pools callbacks can be executed on). Monix is better than most in this area (with TaskLocal), but I don't want a solution which is restricted to that library.

I've also used New Relic quite a bit, and seen how even the best efforts to automatically instrument code by bytecode injection doesn't really work out that reliably.

yannick-cw commented 6 years ago

Awesome, I was thinking in a similar direction for the api, but you seem to put in already way more thought. I am very interested in what you come up with and willing to review / support any time :)