Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tracing a Resource While Staying in Resource #194

Open
ChristopherDavenport opened this issue May 3, 2023 · 4 comments
Open

Tracing a Resource While Staying in Resource #194

ChristopherDavenport opened this issue May 3, 2023 · 4 comments
Labels
tracing Improvements to tracing module

Comments

@ChristopherDavenport
Copy link
Member

So here is an approach I found that was able to get correct resource timing.

https://github.com/ChristopherDavenport/natchez-http4s-otel/blob/otel2/core/src/main/scala/io/chrisdavenport/natchezhttp4sotel/ClientMiddleware.scala#L76-L87

I was wondering how I was supposed to trace for the lifetime of a resource while staying in Resource?

Client are Request[F] => Resource[F, Response[F]] so we dont want to directly use the resource in line.

I assumed I would use wrapResource on spanBuilder, but the use seems to still collapse to F rather than maintaining the resource that's necessary to preserve the live tcp connection.

Hoping that someone might have an approach to how to best preserve the resource while tracing a resource?

@iRevive
Copy link
Contributor

iRevive commented May 3, 2023

This behavior was supported in the very first draft. Turned out it does not work the way we want with Local[F, Vault].

There are a few discussions regarding this topic:

  1. Tracing with local semantics #105
  2. Does tracing work well without IOLocal? Should it? #88
  3. Local-compatible tracing semantics #107 (comment)

@ChristopherDavenport
Copy link
Member Author

So we can't trace the lifetime of an http4s client resource? I'm not sure I'm following.

@iRevive
Copy link
Contributor

iRevive commented Jul 17, 2023

We will eliminate wrapResource in the upcoming release. A follow-up to #273.

To summarize: it's not possible to trace the resource now (e.g. different stages: acquire, use, release; and stay within the resource), and will not be possible in the future because the API is bound to the Local[F, Vault] semantics :(

@iRevive
Copy link
Contributor

iRevive commented Jul 30, 2024

Local semantics doesn't go along with the Resource.

Warning

The code below provides a leaky abstraction, please don't use it

However, there is a workaround to make it work by manually manipulating the context:

class IOLocalTracer[F[_]: Monad: LiftIO, Ctx](underlying: Tracer[F], ioLocal: IOLocal[Ctx]) extends Tracer[F] {
  def meta: Tracer.Meta[F] = underlying.meta
  ... // forward all other methods
  def spanBuilder(name: String): SpanBuilder[F] = 
    DelegateSpanBuilder(underlying.spanBuilder(name), ioLocal)
}

case class DelegateSpanBuilder[F[_]: Monad: LiftIO, Ctx](
   builder: SpanBuilder[F],
   ioLocal: IOLocal[Ctx]
) extends SpanBuilder[F] {
  def addAttribute[A](attribute: Attribute[A]): SpanBuilder[F] = copy(builder.addAttribute(attribute))
  ... // forward all other methods
  def build: SpanOps[F] = {
    val b = builder.build
    new SpanOps[F] {
      def resource: Resource[F, SpanOps.Res[F]] =
        for {
          res <- b.resource
          // manually set the context
          _ <- Resource.make( 
            ioLocal.get.to[F] <* res.trace(ioLocal.get.to[F]).flatMap(ioLocal.set(_).to[F])
          )(ioLocal.set(_).to[F])
        } yield res
      def use[A](f: Span[F] => F[A]): F[A] = b.use(f)
      def use_ : F[Unit] = b.use_
    }
  }
}

And wire everything together:

IOLocal(Context.root).flatMap { implicit ioLocal =>
  OtelJava.autoConfigured[IO]().use { otel4s =>
    otel4s.tracerProvider.get("tracer").flatMap { t =>
      implicit val tracer: Tracer[IO] = new IOLocalTracer(t, ioLocal)
      Tracer[IO].span("test").resource.use { _ =>
        Tracer[IO].currentSpanContext.debug() // prints the current span
      }
    }
  }
}

Technically, we can do something similar under the hood. For example, if we detect that Local is backed by the IOLocal, we can apply custom propagation logic.

Downsides:

  1. An unpredictable behavior in some cases: IO.race doesn't propagate winner's IOLocal context cats-effect#3100, IOLocal - generalize scope function cats-effect#3360
  2. Different behavior for different effects: i.e. Kleisli[IO, Context, *] has a built-in Local[Kleisli[IO, Context, *], Context] instance that doesn't require IOLocal
  3. Resource may start on one fiber and finalize on another one. Hence, the abstraction is leaking and may create more problems than solve

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
tracing Improvements to tracing module
Projects
None yet
Development

No branches or pull requests

2 participants