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

Reduce exposure of end methods for spans #350

Closed
wants to merge 1 commit into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ package trace
import scala.concurrent.duration.FiniteDuration

private[otel4s] trait SpanMacro[F[_]] {
self: Span[F] =>
self: SpanAPI[F] =>

/** Adds an attribute to the span. If the span previously contained a mapping
* for the key, the old value is replaced by the specified value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import scala.concurrent.duration.FiniteDuration
import scala.quoted.*

private[otel4s] trait SpanMacro[F[_]] {
self: Span[F] =>
self: SpanAPI[F] =>

/** Adds an attribute to the span. If the span previously contained a mapping
* for the key, the old value is replaced by the specified value.
Expand Down Expand Up @@ -124,7 +124,7 @@ private[otel4s] trait SpanMacro[F[_]] {
object SpanMacro {

def addAttribute[F[_], A](
span: Expr[Span[F]],
span: Expr[SpanAPI[F]],
attribute: Expr[Attribute[A]]
)(using Quotes, Type[F], Type[A]) =
'{
Expand All @@ -134,7 +134,7 @@ object SpanMacro {
}

def addAttributes[F[_]](
span: Expr[Span[F]],
span: Expr[SpanAPI[F]],
attributes: Expr[Seq[Attribute[_]]]
)(using Quotes, Type[F]) =
'{
Expand All @@ -144,7 +144,7 @@ object SpanMacro {
}

def addEvent[F[_]](
span: Expr[Span[F]],
span: Expr[SpanAPI[F]],
name: Expr[String],
attributes: Expr[Seq[Attribute[_]]]
)(using Quotes, Type[F]) =
Expand All @@ -155,7 +155,7 @@ object SpanMacro {
}

def addEvent[F[_]](
span: Expr[Span[F]],
span: Expr[SpanAPI[F]],
name: Expr[String],
timestamp: Expr[FiniteDuration],
attributes: Expr[Seq[Attribute[_]]]
Expand All @@ -167,7 +167,7 @@ object SpanMacro {
}

def recordException[F[_]](
span: Expr[Span[F]],
span: Expr[SpanAPI[F]],
exception: Expr[Throwable],
attributes: Expr[Seq[Attribute[_]]]
)(using Quotes, Type[F]) =
Expand All @@ -178,7 +178,7 @@ object SpanMacro {
}

def setStatus[F[_]](
span: Expr[Span[F]],
span: Expr[SpanAPI[F]],
status: Expr[Status]
)(using Quotes, Type[F]) =
'{
Expand All @@ -188,7 +188,7 @@ object SpanMacro {
}

def setStatus[F[_]](
span: Expr[Span[F]],
span: Expr[SpanAPI[F]],
status: Expr[Status],
description: Expr[String]
)(using Quotes, Type[F]) =
Expand Down
139 changes: 82 additions & 57 deletions core/trace/src/main/scala/org/typelevel/otel4s/trace/Span.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,30 @@ import org.typelevel.otel4s.meta.InstrumentMeta

import scala.concurrent.duration.FiniteDuration

/** An implementation detail of [[Span]]. Users SHOULD NOT refer to this type.
*/
sealed abstract class SpanAPI[F[_]] extends SpanMacro[F] {
def backend: Span.Backend[F]

/** Returns the [[SpanContext]] associated with this span. */
final def context: SpanContext =
backend.context

/** Updates the name of the [[Span]].
*
* '''Note''': if used, this will override the name provided via the
* [[SpanBuilder]].
*
* '''Caution''': upon this update, any sampling behavior based on span's
* name will depend on the implementation.
*
* @param name
* the new name of the span
*/
final def updateName(name: String): F[Unit] =
backend.updateName(name)
}

/** The API to trace an operation.
*
* There are two types of span lifecycle managements: manual and auto.
Expand Down Expand Up @@ -64,60 +88,16 @@ import scala.concurrent.duration.FiniteDuration
* }
* }}}
*/
trait Span[F[_]] extends SpanMacro[F] {
def backend: Span.Backend[F]

/** Returns the [[SpanContext]] associated with this span.
*/
final def context: SpanContext =
backend.context

/** Updates the name of the [[Span]].
*
* '''Note''': if used, this will override the name provided via the
* [[SpanBuilder]].
*
* '''Caution''': upon this update, any sampling behavior based on span's
* name will depend on the implementation.
*
* @param name
* the new name of the span
*/
final def updateName(name: String): F[Unit] =
backend.updateName(name)

/** Marks the end of [[Span]] execution.
*
* Only the timing of the first end call for a given span will be recorded,
* the subsequent calls will be ignored.
*
* The end timestamp is based on the `Clock[F].realTime`.
*/
final def end: F[Unit] =
backend.end

/** Marks the end of [[Span]] execution with the specified timestamp.
*
* Only the timing of the first end call for a given span will be recorded,
* the subsequent calls will be ignored.
*
* '''Note''': the timestamp should be based on `Clock[F].realTime`. Using
* `Clock[F].monotonic` may lead to a missing span.
*
* @param timestamp
* the explicit timestamp from the epoch
*/
final def end(timestamp: FiniteDuration): F[Unit] =
backend.end(timestamp)
final class Span[F[_]] private (val backend: Span.Backend[F])
extends SpanAPI[F] {

/** Modify the context `F` using the transformation `f`. */
def mapK[G[_]](f: F ~> G): Span[G] = Span.fromBackend(backend.mapK(f))

/** Modify the context `F` using an implicit [[KindTransformer]] from `F` to
* `G`.
*/
final def mapK[G[_]](implicit kt: KindTransformer[F, G]): Span[G] =
mapK(kt.liftK)
def mapK[G[_]](implicit kt: KindTransformer[F, G]): Span[G] = mapK(kt.liftK)
}

object Span {
Expand Down Expand Up @@ -150,12 +130,6 @@ object Span {

/** Modify the context `F` using the transformation `f`. */
def mapK[G[_]](f: F ~> G): Backend[G] = new Backend.MappedK(this)(f)

/** Modify the context `F` using an implicit [[KindTransformer]] from `F` to
* `G`.
*/
final def mapK[G[_]](implicit kt: KindTransformer[F, G]): Backend[G] =
mapK(kt.liftK)
}

object Backend {
Expand Down Expand Up @@ -222,8 +196,59 @@ object Span {
}
}

private[otel4s] def fromBackend[F[_]](back: Backend[F]): Span[F] =
new Span[F] {
def backend: Backend[F] = back
}
/** The API for a manually managed trace operation, including the ability to
* end the operation. A `Span.Manual` can be converted into a [[Span]] by
* calling [[unmanageable]], and the `Span` type should be used for the
* majority of operations on spans.
*
* @see
* [[Span]]
*/
final class Manual[F[_]] private (val backend: Backend[F])
extends SpanAPI[F] {

/** This span, without the ability to end it. */
lazy val unmanageable: Span[F] = Span.fromBackend(backend)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this name feels kind of silly, but I can't think of anything better


/** Marks the end of span execution.
*
* Only the timing of the first end call for a given span will be recorded,
* the subsequent calls will be ignored.
*
* The end timestamp is based on the `Clock[F].realTime`.
*/
def end: F[Unit] =
backend.end

/** Marks the end of span execution with the specified timestamp.
*
* Only the timing of the first end call for a given span will be recorded,
* the subsequent calls will be ignored.
*
* '''Note''': the timestamp should be based on `Clock[F].realTime`. Using
* `Clock[F].monotonic` may lead to a missing span.
*
* @param timestamp
* the explicit timestamp from the epoch
*/
def end(timestamp: FiniteDuration): F[Unit] =
backend.end(timestamp)

/** Modify the context `F` using the transformation `f`. */
def mapK[G[_]](f: F ~> G): Manual[G] = Manual.fromBackend(backend.mapK(f))

/** Modify the context `F` using an implicit [[KindTransformer]] from `F` to
* `G`.
*/
def mapK[G[_]](implicit kt: KindTransformer[F, G]): Manual[G] =
mapK(kt.liftK)
}

private[otel4s] object Manual {
private[otel4s] def fromBackend[F[_]](backend: Backend[F]): Manual[F] =
new Manual(backend)
}

private[otel4s] def fromBackend[F[_]](backend: Backend[F]): Span[F] =
new Span(backend)
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ object SpanBuilder {

def noop[F[_]: Applicative](back: Span.Backend[F]): SpanBuilder[F] =
new SpanBuilder[F] {
private val span: Span[F] = Span.fromBackend(back)
private val span: Span.Manual[F] = Span.Manual.fromBackend(back)

def addAttribute[A](attribute: Attribute[A]): SpanBuilder[F] = this

Expand All @@ -150,13 +150,13 @@ object SpanBuilder {
def withStartTimestamp(timestamp: FiniteDuration): SpanBuilder[F] = this

def build: SpanOps[F] = new SpanOps[F] {
def startUnmanaged: F[Span[F]] =
def startUnmanaged: F[Span.Manual[F]] =
Applicative[F].pure(span)

def resource: Resource[F, SpanOps.Res[F]] =
Resource.pure(SpanOps.Res(span, FunctionK.id))
Resource.pure(SpanOps.Res(span.unmanageable, FunctionK.id))

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

override def use_ : F[Unit] = Applicative[F].unit
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import cats.~>

trait SpanOps[F[_]] {

/** Creates a [[Span]]. The span requires to be ended '''explicitly''' by
* invoking `end`.
/** Creates a [[Span.Manual]]. The span requires to be ended '''explicitly'''
* by invoking `end`.
*
* This strategy can be used when it's necessary to end a span outside of the
* scope (e.g. async callback). Make sure the span is ended properly.
Expand Down Expand Up @@ -54,7 +54,7 @@ trait SpanOps[F[_]] {
* @see
* [[use]], [[use_]], [[surround]], or [[resource]] for a managed lifecycle
*/
def startUnmanaged: F[Span[F]]
def startUnmanaged: F[Span.Manual[F]]

/** Creates a [[Span]] and a [[cats.effect.kernel.Resource Resource]] for
* using it. Unlike [[startUnmanaged]], the lifecycle of the span is fully
Expand Down Expand Up @@ -193,7 +193,7 @@ object SpanOps {
ops: SpanOps[F]
)(implicit kt: KindTransformer[F, G])
extends SpanOps[G] {
def startUnmanaged: G[Span[G]] =
def startUnmanaged: G[Span.Manual[G]] =
kt.liftK(ops.startUnmanaged).map(_.mapK[G])

def resource: Resource[G, Res[G]] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ private[java] final case class SpanBuilderImpl[F[_]: Sync](
copy(finalizationStrategy = strategy)

def build: SpanOps[F] = new SpanOps[F] {
def startUnmanaged: F[Span[F]] =
def startUnmanaged: F[Span.Manual[F]] =
runnerContext.flatMap(ctx => SpanRunner.startUnmanaged(ctx))

def resource: Resource[F, SpanOps.Res[F]] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,17 @@ private[java] object SpanRunner {
}
}

def startUnmanaged[F[_]: Sync](context: Option[RunnerContext]): F[Span[F]] =
def startUnmanaged[F[_]: Sync](
context: Option[RunnerContext]
): F[Span.Manual[F]] =
context match {
case Some(RunnerContext(builder, _, ts, _)) =>
for {
back <- SpanRunner.startSpan(builder, ts)
} yield Span.fromBackend(back)
} yield Span.Manual.fromBackend(back)

case None =>
Sync[F].pure(Span.fromBackend(Span.Backend.noop))
Sync[F].pure(Span.Manual.fromBackend(Span.Backend.noop))
}

private def startSpan[F[_]: Sync](
Expand Down