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

An exploration of cats.mtl.Local tracing #102

Closed
wants to merge 1 commit into from
Closed

Conversation

rossabaker
Copy link
Member

See #88, #101, and, more generally, typelevel/cats-effect#3385. What does tracing look like with cats.mtl.Local instead of cats.effect.IOLocal.

@@ -175,7 +177,7 @@ lazy val `java-trace` = project
.settings(
name := "otel4s-java-trace",
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-effect" % CatsEffectVersion,
"org.typelevel" %%% "cats-mtl" % CatsMtlVersion,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bonus: the hard IO dependency is gone.

@@ -96,6 +97,7 @@ lazy val `core-trace` = crossProject(JVMPlatform, JSPlatform, NativePlatform)
name := "otel4s-core-trace",
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-effect-kernel" % CatsEffectVersion,
"org.typelevel" %%% "cats-mtl" % CatsMtlVersion,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Negative: another core dependency in a library we hope to integrate with the flagships.

Comment on lines +51 to +66
// This would be a library function
def local[E](a: E): IO[Local[IO, E]] =
IOLocal(a).map { ioLocal =>
new Local[IO, E] {
override def local[A](fa: IO[A])(f: E => E): IO[A] =
ioLocal.get.flatMap(prev =>
ioLocal
.set(f(prev))
.bracket(Function.const(fa))(Function.const(ioLocal.set(prev)))
)
override val applicative: Applicative[IO] =
Applicative[IO]
override def ask[E2 >: E]: IO[E2] =
ioLocal.get
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is my interpretation of typelevel/cats-effect#3385, but I haven't tested it.

Comment on lines +69 to +70
import io.opentelemetry.context.{Context => JContext}
import org.typelevel.otel4s.java.trace.TraceScope.Scope
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neither of these imports are particularly desirable in userspace.

def tracerResource: Resource[IO, Tracer[IO]] = {
import io.opentelemetry.context.{Context => JContext}
import org.typelevel.otel4s.java.trace.TraceScope.Scope
Resource.eval(local(Scope.Root(JContext.root()): Scope)).flatMap {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this is worse. If we're going to expose Scope, we should have a clean way for users to construct this one.

@@ -46,10 +46,12 @@ object OtelJava {
* @return
* An effect of an [[org.typelevel.otel4s.Otel4s]] resource.
*/
def forSync[F[_]: LiftIO: Sync](jOtel: JOpenTelemetry): F[Otel4s[F]] = {
def forSync[F[_]: Sync: Local[*[_], TraceScope.Scope]](
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where Scope starts to leak. I think we can fix this with some Greek letters.

[edit] Well, probably not: if F is Kleisli, we start to publicly return a bunch of functions of Scope.

Comment on lines +83 to +88
// There's no getAndSet. This is inherently stateful.
/*
Resource
.make(local.getAndSet(scope).to[F])(p => local.set(p).to[F])
.void
*/
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this here is where the wheels fall off. Do we want tracing of more than IOLocal, or do we want acquire, use, and release to be siblings? This was a matter of long debate in Natchez.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a case for both. If the three phases are part of the same trace, then you'd want them to be siblings, but if they should be part of different traces, then not.

Motivation for all three in the same trace: the Resource[F, Response[F]] from http4s's Client.run, where the whole thing should be encapsulated in the trace that was responsible for starting the request.

Motivation for separate traces: a Resource[F, A] where A is a long-lived server process, where IMHO ideally you'd have an initialization trace that completes when the acquisition phase is complete and a finalization trace that starts and finishes when the resource is finalized. (In my head, the use is not really traced itself, but each request handled by the server would start a new trace, possibly linked to the acquisition trace if that would be helpful.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exposing the Resource as Client#run really shot us in the foot for this use case. We used to accept the callback directly -- I think it was called fetch. There's not a whole lot we can do with local semantics:

  1. We can span whatever calls Client#run, but Client#run can't span it locally because it can't see it.
  2. We could have a Resource[F, A] => Resource[F, (F ~> F, A)], and the user of the resource would be responsible for calling the natural transformation for scoping. But that's roughly as intrusive as tracing the caller of Client#run and more confusing.
  3. We could create a new resource-like algebra with an Intercept, constructable from Resource. But, gosh, that's disruptive.
  4. Someone smarter than me could figure out how to shoehorn it into the existing constructors. I more readily believe someone is smarter than me than that this is possible.

@rossabaker
Copy link
Member Author

There are good ideas here, but it now needs to be retrotfit onto #107.

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

Successfully merging this pull request may close these issues.

2 participants