-
Notifications
You must be signed in to change notification settings - Fork 38
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
Conversation
@@ -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, |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
// 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 | ||
} | ||
} |
There was a problem hiding this comment.
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.
import io.opentelemetry.context.{Context => JContext} | ||
import org.typelevel.otel4s.java.trace.TraceScope.Scope |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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]]( |
There was a problem hiding this comment.
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.
// There's no getAndSet. This is inherently stateful. | ||
/* | ||
Resource | ||
.make(local.getAndSet(scope).to[F])(p => local.set(p).to[F]) | ||
.void | ||
*/ |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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:
- We can span whatever calls
Client#run
, butClient#run
can't span it locally because it can't see it. - 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 ofClient#run
and more confusing. - We could create a new resource-like algebra with an
Intercept
, constructable fromResource
. But, gosh, that's disruptive. - 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.
There are good ideas here, but it now needs to be retrotfit onto #107. |
See #88, #101, and, more generally, typelevel/cats-effect#3385. What does tracing look like with
cats.mtl.Local
instead ofcats.effect.IOLocal
.