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

Add Tracer#currentSpanOrThrow #555

Merged
merged 1 commit into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions core/trace/src/main/scala/org/typelevel/otel4s/trace/Tracer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package org.typelevel.otel4s
package trace

import cats.Applicative
import cats.ApplicativeThrow
import cats.effect.kernel.MonadCancelThrow
import cats.syntax.functor._
import cats.~>
Expand Down Expand Up @@ -51,11 +52,21 @@ trait Tracer[F[_]] extends TracerMacro[F] {
*/
def currentSpanContext: F[Option[SpanContext]]

/** Returns the current span if one exists in the local scope, or a no-op span
* otherwise.
/** @return
* the current span if one exists in the local scope, or a no-op span
* otherwise
*/
def currentSpanOrNoop: F[Span[F]]

/** @return
* the current span if one exists in the local scope (even if it's a no-op
* span), or raises an error in `F` otherwise
*
* @throws java.lang.IllegalStateException
* when called while not inside a span, indicating programmer error
*/
def currentSpanOrThrow: F[Span[F]]

/** Creates a new [[SpanBuilder]]. The builder can be used to make a fully
* customized [[Span]].
*
Expand Down Expand Up @@ -206,6 +217,10 @@ trait Tracer[F[_]] extends TracerMacro[F] {
}

object Tracer {
private[otel4s] def raiseNoCurrentSpan[F[_]](implicit
F: ApplicativeThrow[F]
): F[Span[F]] =
F.raiseError(new IllegalStateException("not inside a span"))
iRevive marked this conversation as resolved.
Show resolved Hide resolved

def apply[F[_]](implicit ev: Tracer[F]): Tracer[F] = ev

Expand Down Expand Up @@ -264,6 +279,7 @@ object Tracer {
val currentSpanContext: F[Option[SpanContext]] = Applicative[F].pure(None)
val currentSpanOrNoop: F[Span[F]] =
Applicative[F].pure(Span.fromBackend(noopBackend))
def currentSpanOrThrow: F[Span[F]] = currentSpanOrNoop
def rootScope[A](fa: F[A]): F[A] = fa
def noopScope[A](fa: F[A]): F[A] = fa
def childScope[A](parent: SpanContext)(fa: F[A]): F[A] = fa
Expand All @@ -283,6 +299,8 @@ object Tracer {
kt.liftK(tracer.currentSpanContext)
def currentSpanOrNoop: G[Span[G]] =
kt.liftK(tracer.currentSpanOrNoop.map(_.mapK[G]))
def currentSpanOrThrow: G[Span[G]] =
kt.liftK(tracer.currentSpanOrThrow.map(_.mapK[G]))
def spanBuilder(name: String): SpanBuilder[G] =
tracer.spanBuilder(name).mapK[G]
def childScope[A](parent: SpanContext)(ga: G[A]): G[A] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,58 @@ abstract class BaseTracerSuite[Ctx, K[X] <: Key[X]](implicit
}
}

sdkTest("`currentSpanOrThrow` outside of a span (root scope)") { sdk =>
{
for {
tracer <- sdk.provider.get("tracer")
_ <- tracer.currentSpanOrThrow
_ <- IO.raiseError[Unit](
new AssertionError("did not throw for `currentSpanOrThrow`")
)
} yield ()
}.recover { case _: IllegalStateException => () }
}

sdkTest("`currentSpanOrThrow` in noop scope") { sdk =>
for {
tracer <- sdk.provider.get("tracer")
_ <- tracer.noopScope {
for {
currentSpan <- tracer.currentSpanOrThrow
_ <- currentSpan.addAttribute(Attribute("string-attribute", "value"))
} yield assert(!currentSpan.context.isValid)
}
spans <- sdk.finishedSpans
} yield assertEquals(spans.length, 0)
}

sdkTest("`currentSpanOrThrow` inside a span") { sdk =>
def expected(now: FiniteDuration) =
List(SpanTree(SpanInfo("span", now, now)))

val attribute =
Attribute("string-attribute", "value")

TestControl.executeEmbed {
for {
now <- IO.monotonic.delayBy(1.second) // otherwise returns 0
tracer <- sdk.provider.get("tracer")
_ <- tracer.span("span").surround {
for {
currentSpan <- tracer.currentSpanOrThrow
_ <- currentSpan.addAttribute(attribute)
} yield assert(currentSpan.context.isValid)
}
spans <- sdk.finishedSpans
tree <- IO.pure(treeOf(spans))
// _ <- IO.println(tree.map(renderTree).mkString("\n"))
} yield {
assertEquals(tree, expected(now))
assertEquals(spans.map(_.attributes), List(Attributes(attribute)))
}
}
}

sdkTest("create a new scope with a custom parent") { sdk =>
def expected(now: FiniteDuration) =
SpanTree(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ import org.typelevel.otel4s.trace.SpanContext
import org.typelevel.otel4s.trace.TraceScope
import org.typelevel.otel4s.trace.Tracer

private[oteljava] class TracerImpl[F[_]: Sync](
private[oteljava] class TracerImpl[F[_]](
jTracer: JTracer,
propagators: ContextPropagators[Context],
traceScope: TraceScope[F, Context],
) extends Tracer[F] {
)(implicit F: Sync[F])
extends Tracer[F] {

private val runner: SpanRunner[F] = SpanRunner.fromTraceScope(traceScope)

Expand All @@ -54,6 +55,15 @@ private[oteljava] class TracerImpl[F[_]: Sync](
)
}

def currentSpanOrThrow: F[Span[F]] =
traceScope.contextReader { ctx =>
Option(JSpan.fromContextOrNull(ctx.underlying))
.map { jSpan =>
F.pure(Span.fromBackend(SpanBackendImpl.fromJSpan(jSpan)))
}
.getOrElse(Tracer.raiseNoCurrentSpan)
}.flatten

def spanBuilder(name: String): SpanBuilder[F] =
new SpanBuilderImpl[F](jTracer, name, runner, traceScope)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,21 @@ private final class SdkTracer[F[_]: Temporal: Console] private[trace] (
def currentSpanContext: F[Option[SpanContext]] =
traceScope.current.map(current => current.filter(_.isValid))

def currentSpanOrNoop: F[Span[F]] =
private[this] def currentBackend: OptionT[F, Span.Backend[F]] =
OptionT(traceScope.current)
.semiflatMap { ctx =>
OptionT(storage.get(ctx)).getOrElse(Span.Backend.propagating(ctx))
}

def currentSpanOrNoop: F[Span[F]] =
currentBackend
.getOrElse(Span.Backend.noop)
.map(backend => Span.fromBackend(backend))
.map(Span.fromBackend)

def currentSpanOrThrow: F[Span[F]] =
currentBackend
.map(Span.fromBackend)
.getOrElseF(Tracer.raiseNoCurrentSpan)

def spanBuilder(name: String): SpanBuilder[F] =
new SdkSpanBuilder[F](name, scopeInfo, sharedState, traceScope)
Expand Down