Skip to content

Commit

Permalink
Change return type of SpanOps#resource
Browse files Browse the repository at this point in the history
Add `SpanOps.Res` type for the return type of `SpanOps#resource`,
for clearer, more evolvable and more documentable API.
  • Loading branch information
NthPortal committed Jul 24, 2023
1 parent b5ca781 commit caeea8d
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import cats.Applicative
import cats.arrow.FunctionK
import cats.effect.MonadCancelThrow
import cats.effect.Resource
import cats.~>

import scala.concurrent.duration.FiniteDuration

Expand Down Expand Up @@ -145,8 +144,8 @@ object SpanBuilder {
def startUnmanaged: F[Span[F]] =
Applicative[F].pure(span)

def resource: Resource[F, (Span[F], F ~> F)] =
Resource.pure(span -> FunctionK.id)
def resource: Resource[F, SpanOps.Res[F]] =
Resource.pure(SpanOps.Res(span, FunctionK.id))

def use[A](f: Span[F] => F[A]): F[A] = f(span)

Expand Down
43 changes: 38 additions & 5 deletions core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,24 @@ trait SpanOps[F[_]] {
*
* @see
* default finalization strategy [[SpanFinalizer.Strategy.reportAbnormal]]
* @see
* [[SpanOps.Res]] for the semantics and usage of the resource's value
* @example
* {{{
* val tracer: Tracer[F] = ???
* val ok: F[Unit] =
* tracer.spanBuilder("resource-span")
* .build
* .resource
* .use { case (span, wrap) =>
* // `wrap` encloses its contents within the "resource-span" span;
* // anything not applied to `wrap` will not end up in the span
* wrap(span.setStatus(Status.Ok, "all good"))
* .use { res =>
* // `res.include` encloses its contents within the "resource-span"
* // span; anything not applied to `res.include` will not end up in
* // the span
* res.include(res.span.setStatus(Status.Ok, "all good"))
* }
* }}}
*/
def resource: Resource[F, (Span[F], F ~> F)]
def resource: Resource[F, SpanOps.Res[F]]

/** Creates and uses a [[Span]]. Unlike [[startUnmanaged]], the lifecycle of
* the span is fully managed. The span is started and passed to `f` to
Expand Down Expand Up @@ -136,3 +139,33 @@ trait SpanOps[F[_]] {
*/
final def surround[A](fa: F[A]): F[A] = use(_ => fa)
}

object SpanOps {

/** The span and associated natural transformation [[`trace`]] provided and
* managed by a call to [[SpanOps.resource]]. In order to trace something in
* the span, it must be applied to [[`trace`]].
*/
sealed trait Res[F[_]] {

/** The managed span. */
def span: Span[F]

/** A natural transformation that traces everything applied to it in the
* span. Note: anything not applied to this
* [[cats.arrow.FunctionK FunctionK]] will not be traced.
*/
def trace: F ~> F
}

object Res {
private[this] final case class Impl[F[_]](span: Span[F], trace: F ~> F)
extends Res[F]

/** Creates a [[Res]] from a managed span and a natural transformation for
* tracing operations in the span.
*/
def apply[F[_]](span: Span[F], trace: F ~> F): Res[F] =
Impl(span, trace)
}
}
8 changes: 3 additions & 5 deletions examples/src/main/scala/TracingExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,8 @@ object TracingExample extends IOApp.Simple {
tracer
.span("resource")
.resource
.use { case (span, wrap) =>
// `wrap` encloses its contents within the "resource" span;
// anything not applied to `wrap` will not end up in the span
wrap {
.use { res =>
res.trace {
for {
_ <- tracer.span("acquire").surround(IO.sleep(50.millis))
_ <- tracer.span("use").surround {
Expand All @@ -80,7 +78,7 @@ object TracingExample extends IOApp.Simple {
)
)
}
_ <- span.addEvent("event")
_ <- res.span.addEvent("event")
_ <- tracer.span("release").surround(IO.sleep(100.millis))
} yield ()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import cats.effect.Resource
import cats.effect.Sync
import cats.syntax.flatMap._
import cats.syntax.functor._
import cats.~>
import io.opentelemetry.api.trace.{Span => JSpan}
import io.opentelemetry.api.trace.{SpanBuilder => JSpanBuilder}
import io.opentelemetry.api.trace.{SpanKind => JSpanKind}
Expand Down Expand Up @@ -85,11 +84,11 @@ private[java] final case class SpanBuilderImpl[F[_]: Sync](
def startUnmanaged: F[Span[F]] =
runnerContext.flatMap(ctx => SpanRunner.startUnmanaged(ctx))

def resource: Resource[F, (Span[F], F ~> F)] =
def resource: Resource[F, SpanOps.Res[F]] =
Resource.eval(runnerContext).flatMap(ctx => runner.start(ctx))

override def use[A](f: Span[F] => F[A]): F[A] =
resource.use { case (span, nt) => nt(f(span)) }
resource.use { res => res.trace(f(res.span)) }

override def use_ : F[Unit] = use(_ => Sync[F].unit)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,10 @@ import io.opentelemetry.api.trace.{SpanBuilder => JSpanBuilder}
import io.opentelemetry.context.{Context => JContext}
import org.typelevel.otel4s.trace.Span
import org.typelevel.otel4s.trace.SpanFinalizer
import org.typelevel.otel4s.trace.SpanOps

private[java] sealed trait SpanRunner[F[_]] {
def start(
ctx: Option[SpanRunner.RunnerContext]
): Resource[F, (Span[F], F ~> F)]
def start(ctx: Option[SpanRunner.RunnerContext]): Resource[F, SpanOps.Res[F]]
}

private[java] object SpanRunner {
Expand All @@ -46,18 +45,20 @@ private[java] object SpanRunner {

def span[F[_]: Sync](scope: TraceScope[F]): SpanRunner[F] =
new SpanRunner[F] {
def start(ctx: Option[RunnerContext]): Resource[F, (Span[F], F ~> F)] = {
def start(ctx: Option[RunnerContext]): Resource[F, SpanOps.Res[F]] = {
ctx match {
case Some(RunnerContext(builder, _, hasStartTs, finalization)) =>
startManaged(
builder = builder,
hasStartTimestamp = hasStartTs,
finalizationStrategy = finalization,
scope = scope
).map { case (back, nt) => (Span.fromBackend(back), nt) }
).map { case (back, nt) => SpanOps.Res(Span.fromBackend(back), nt) }

case None =>
Resource.pure((Span.fromBackend(Span.Backend.noop), FunctionK.id))
Resource.pure(
SpanOps.Res(Span.fromBackend(Span.Backend.noop), FunctionK.id)
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -408,18 +408,20 @@ class TracerSuite extends CatsEffectSuite {
tracer
.span("resource-span", attributes: _*)
.resource
.flatMap { case (_, nt) =>
.flatMap { res =>
Resource[F, A] {
nt {
res.trace {
tracer
.span("acquire")
.surround {
resource.allocated.map { case (acquired, release) =>
acquired -> nt(tracer.span("release").use(_ => release))
acquired -> res.trace(
tracer.span("release").surround(release)
)
}
}
}
}.map(_ => nt)
}.as(res.trace)
}
}

Expand Down

0 comments on commit caeea8d

Please sign in to comment.