Skip to content

Commit 2e0ff10

Browse files
authored
Merge pull request #1160 from iRevive/core-trace/with-current-span
core-trace: add `withCurrentSpanOrNoop` utility to `Tracer`
2 parents 856d695 + 039a5b1 commit 2e0ff10

4 files changed

Lines changed: 73 additions & 2 deletions

File tree

core/trace/src/main/scala/org/typelevel/otel4s/trace/Tracer.scala

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package trace
2020
import cats.Applicative
2121
import cats.ApplicativeThrow
2222
import cats.effect.kernel.MonadCancelThrow
23+
import cats.syntax.flatMap._
2324
import cats.syntax.functor._
2425
import cats.~>
2526
import org.typelevel.otel4s.context.propagation.TextMapGetter
@@ -64,6 +65,15 @@ sealed trait Tracer[F[_]] extends TracerMacro[F] {
6465
*/
6566
def currentSpanOrThrow: F[Span[F]]
6667

68+
/** Applies `f` to the current span if one exists in the local scope, or a no-op span otherwise.
69+
*
70+
* This is a convenience for `currentSpanOrNoop.flatMap(f)`.
71+
*
72+
* @see
73+
* [[currentSpanOrNoop]]
74+
*/
75+
def withCurrentSpanOrNoop[A](f: Span[F] => F[A]): F[A]
76+
6777
/** Creates a new [[SpanBuilder]]. The builder can be used to make a fully customized [[Span]].
6878
*
6979
* @param name
@@ -232,12 +242,13 @@ object Tracer {
232242
def noop[F[_]: Applicative]: Tracer[F] =
233243
new Tracer[F] {
234244
private val noopBackend = Span.Backend.noop
245+
private val noopSpan = Span.fromBackend(noopBackend)
235246
private val builder = SpanBuilder.noop(noopBackend)
236247
val meta: InstrumentMeta[F] = InstrumentMeta.disabled
237248
val currentSpanContext: F[Option[SpanContext]] = Applicative[F].pure(None)
238-
val currentSpanOrNoop: F[Span[F]] =
239-
Applicative[F].pure(Span.fromBackend(noopBackend))
249+
val currentSpanOrNoop: F[Span[F]] = Applicative[F].pure(noopSpan)
240250
def currentSpanOrThrow: F[Span[F]] = currentSpanOrNoop
251+
def withCurrentSpanOrNoop[A](f: Span[F] => F[A]): F[A] = f(noopSpan)
241252
def rootScope[A](fa: F[A]): F[A] = fa
242253
def noopScope[A](fa: F[A]): F[A] = fa
243254
def childScope[A](parent: SpanContext)(fa: F[A]): F[A] = fa
@@ -259,6 +270,8 @@ object Tracer {
259270
kt.liftK(tracer.currentSpanOrNoop.map(_.liftTo[G]))
260271
def currentSpanOrThrow: G[Span[G]] =
261272
kt.liftK(tracer.currentSpanOrThrow.map(_.liftTo[G]))
273+
def withCurrentSpanOrNoop[A](f: Span[G] => G[A]): G[A] =
274+
currentSpanOrNoop.flatMap(f)
262275
def spanBuilder(name: String): SpanBuilder[G] =
263276
tracer.spanBuilder(name).liftTo[G]
264277
def childScope[A](parent: SpanContext)(ga: G[A]): G[A] =

core/trace/src/test/scala/org/typelevel/otel4s/trace/BaseTracerSuite.scala

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,55 @@ abstract class BaseTracerSuite[Ctx, K[X] <: Key[X]](implicit
311311
}
312312
}
313313

314+
sdkTest("`withCurrentSpan` outside of a span (root scope)") { sdk =>
315+
for {
316+
tracer <- sdk.provider.get("tracer")
317+
isValid <- tracer.withCurrentSpanOrNoop(span => IO.pure(span.context.isValid))
318+
spans <- sdk.finishedSpans
319+
} yield {
320+
assert(!isValid)
321+
assertEquals(spans.length, 0)
322+
}
323+
}
324+
325+
sdkTest("`withCurrentSpan` in noop scope") { sdk =>
326+
for {
327+
tracer <- sdk.provider.get("tracer")
328+
isValid <- tracer.noopScope {
329+
tracer.withCurrentSpanOrNoop(span => IO.pure(span.context.isValid))
330+
}
331+
spans <- sdk.finishedSpans
332+
} yield {
333+
assert(!isValid)
334+
assertEquals(spans.length, 0)
335+
}
336+
}
337+
338+
sdkTest("`withCurrentSpan` inside a span") { sdk =>
339+
def expected(now: FiniteDuration) =
340+
List(SpanTree(SpanInfo("span", now, now)))
341+
342+
val attribute =
343+
Attribute("string-attribute", "value")
344+
345+
TestControl.executeEmbed {
346+
for {
347+
now <- IO.monotonic.delayBy(1.second) // otherwise returns 0
348+
tracer <- sdk.provider.get("tracer")
349+
_ <- tracer.span("span").surround {
350+
tracer.withCurrentSpanOrNoop { span =>
351+
span.addAttribute(attribute) >> IO(assert(span.context.isValid))
352+
}
353+
}
354+
spans <- sdk.finishedSpans
355+
tree <- IO.pure(treeOf(spans))
356+
} yield {
357+
assertEquals(tree, expected(now))
358+
assertEquals(spans.map(_.attributes), List(Attributes(attribute)))
359+
}
360+
}
361+
}
362+
314363
sdkTest("`currentSpanOrThrow` outside of a span (root scope)") { sdk =>
315364
{
316365
for {

core/trace/src/test/scala/org/typelevel/otel4s/trace/TracerSuite.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,11 @@ class TracerSuite extends CatsEffectSuite {
132132
yield assert(!span.context.isValid)
133133
}
134134

135+
test("`withCurrentSpan` uses a no-op span when instrument is noop") {
136+
val tracer = Tracer.noop[IO]
137+
tracer.withCurrentSpanOrNoop(span => IO(assert(!span.context.isValid)))
138+
}
139+
135140
// utility
136141

137142
private sealed trait BuilderOp
@@ -173,6 +178,7 @@ class TracerSuite extends CatsEffectSuite {
173178
def currentSpanContext: F[Option[SpanContext]] = underlying.currentSpanContext
174179
def currentSpanOrNoop: F[Span[F]] = underlying.currentSpanOrNoop
175180
def currentSpanOrThrow: F[Span[F]] = underlying.currentSpanOrThrow
181+
def withCurrentSpanOrNoop[A](f: Span[F] => F[A]): F[A] = underlying.withCurrentSpanOrNoop(f)
176182
def childScope[A](parent: SpanContext)(fa: F[A]): F[A] = underlying.childScope(parent)(fa)
177183
def joinOrRoot[A, C: TextMapGetter](carrier: C)(fa: F[A]): F[A] = underlying.joinOrRoot(carrier)(fa)
178184
def rootScope[A](fa: F[A]): F[A] = underlying.rootScope(fa)

oteljava/trace/src/main/scala/org/typelevel/otel4s/oteljava/trace/TracerImpl.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ private[oteljava] class TracerImpl[F[_]](
6565
.getOrElse(Tracer.raiseNoCurrentSpan)
6666
}.flatten
6767

68+
def withCurrentSpanOrNoop[A](f: Span[F] => F[A]): F[A] =
69+
currentSpanOrNoop.flatMap(f)
70+
6871
def spanBuilder(name: String): SpanBuilder[F] =
6972
SpanBuilderImpl[F](jTracer, name, meta, runner, traceScope)
7073

0 commit comments

Comments
 (0)