-
Notifications
You must be signed in to change notification settings - Fork 3
Improve ZIO interop #68
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
Conversation
… along with allowing for direct usage with ZIO without using Tagless Final
@calvinlfer thanks, will check and approve soon. Could you also look at the examples with ZIO at the t4c-docs repo? They may need some similar changes. |
Hey @catostrophe I was testing my changes against the docs by publishing locally so everything works nicely 😸 |
… final/no zlayer functionality
...t-zio/src/test/scala/io/janstenpickle/trace4cats/inject/zio/ZIOTraceInstanceSummonTest.scala
Show resolved
Hide resolved
I have noticed that when using ZIO directly, I have found the following functionality to be nicer to use: import cats.data.NonEmptyList
import io.janstenpickle.trace4cats.inject.EntryPoint
import io.janstenpickle.trace4cats.{ErrorHandler, Span, ToHeaders}
import io.janstenpickle.trace4cats.model.{AttributeValue, Link, SpanKind, TraceHeaders, TraceId}
import zio.*
import zio.blocking.Blocking
import zio.clock.Clock
import zio.interop.catz.*
class ZTracer(
private val clock: Clock.Service,
private val blocking: Blocking.Service,
private val currentSpan: FiberRef[Span[RIO[Clock & Blocking, *]]]
) {
def put(key: String, value: AttributeValue): UIO[Unit] =
currentSpan.get
.flatMap(_.put(key, value))
.ignore
.provide(Has(clock) ++ Has(blocking))
def putAll(fields: (String, AttributeValue)*): UIO[Unit] =
currentSpan.get
.flatMap(_.putAll(fields*))
.ignore
.provide(Has(clock) ++ Has(blocking))
def span[R <: Has[?], E, A](name: String, kind: SpanKind = SpanKind.Internal)(zio: ZIO[R, E, A]): ZIO[R, E, A] =
currentSpan.get.flatMap { cur =>
cur
.child(name, kind)
.toManagedZIO
.orDie
.provide(Has(clock) ++ Has(blocking))
.use(childSpan => currentSpan.locally(childSpan)(zio))
}
def headers: UIO[TraceHeaders] =
currentSpan.get
.map(span => ToHeaders.all.fromContext(span.context))
def traceId: UIO[TraceId] =
currentSpan.get.map(_.context.traceId)
def addLinks(link: Link, links: Link*): UIO[Unit] =
currentSpan.get
.flatMap(_.addLinks(NonEmptyList.of(link, links*)))
.ignore
.provide(Has(clock) ++ Has(blocking))
}
object ZTracer {
def root(
name: String,
kind: SpanKind = SpanKind.Internal
): ZManaged[Clock & Blocking & Has[EntryPoint[RIO[Clock & Blocking, *]]], Nothing, ZTracer] =
for {
entryPoint <- ZManaged.service[EntryPoint[RIO[Clock & Blocking, *]]]
clock <- ZManaged.service[Clock.Service]
blocking <- ZManaged.service[Blocking.Service]
rootSpan <- entryPoint
.root(name, kind)
.toManagedZIO
.orElse(Span.noop[RIO[Clock & Blocking, *]].toManagedZIO)
.orDie
fiberRef <- FiberRef.make(rootSpan).toManaged_
} yield new ZTracer(clock, blocking, fiberRef)
def fromHeaders(
headers: TraceHeaders,
name: String = "root",
kind: SpanKind = SpanKind.Internal
): ZManaged[Clock & Blocking & Has[EntryPoint[RIO[Clock & Blocking, *]]], Nothing, ZTracer] =
for {
span <- ZManaged
.service[EntryPoint[RIO[Clock & Blocking, *]]]
.flatMap(ep => ep.continueOrElseRoot(name, kind, headers).toManagedZIO)
.orElse(Span.noop[RIO[Clock & Blocking, *]].toManagedZIO)
.orDie
clock <- ZManaged.service[Clock.Service]
blocking <- ZManaged.service[Blocking.Service]
fiberRef <- FiberRef.make(span).toManaged_
} yield new ZTracer(clock, blocking, fiberRef)
def environmentSpan: URIO[Clock & Blocking & Has[Span[RIO[Clock & Blocking, *]]], ZTracer] =
for {
clock <- ZIO.service[Clock.Service]
blocking <- ZIO.service[Blocking.Service]
span <- ZIO.service[Span[RIO[Clock & Blocking, *]]]
fiberRef <- FiberRef.make(span)
} yield new ZTracer(clock, blocking, fiberRef)
def environmentSpan[A, R <: Has[?], E, B](
f: ZTracer => ZIO[R, E, B]
): ZIO[R & Clock & Blocking & Has[Span[RIO[Clock & Blocking, *]]], E, B] =
environmentSpan.flatMap(f)
} It's quite similar to the typeclass implementation but just more forgiving because I eliminate the failure from using Spans so that the user can provide any |
@calvinlfer it should be released soon as a snapshot version. Could you improve the docs repo if you think there's something useful to be added? |
thanks a lot @catostrophe, i'll add some documentation to the other repo tomorrow 😸 |
Hey all,
Thank you very much for this useful library! We have a client that does not make use of Tagless Final and commits to using ZIO throughout the application along with ZLayers for dependency management which results in the use of a
Has
that interferes with the current zio interop. The new implementation allows you to freely use parts of the ZIO environment along with the ZIO interop Trace4Cats in a more ergonomic way.The previous implementation would result in the following errors if I tried to use ZIO directly with Http4S due to the lack of variance + not using Has[Span[...]] but rather using Span[...] directly when it came to the environment:

After the changes, this compiles perfectly and infers all the dependencies automatically (including widening to account for more) which is amazing for the user:
This also preserves the existing examples making use of the interop with no changes 😺
I would love to hear your feedback and I hope you would consider this change as it makes life so much easier thanks to the many integrations you have kindly provided. Please let me know if you want me to make any changes as I would love to work with you to get this merged 🙏🏽
Thank you very much
Cal
P.S. the old implementation uses
Task
but I have widened this toRIO[Clock & Blocking, *]
because thezio.interop.catz.*
imports provideAsync
instances for this right out of the box. I noticed that interop with FS2 Kafka and HTTP4S usually end up using the same effect (RIO clock, blocking, etc.) rather than Task which requires you to use ZIO.runtime[Clock & Blocking] to summon the necessary machinery via the ZIO runtime to summon typeclass instances forAsync[Task]
.