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

Local-compatible tracing semantics #107

Merged
merged 21 commits into from
Feb 7, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ private[otel4s] trait TracerMacro[F[_]] {
* {{{
* val tracer: Tracer[F] = ???
* val span: Span[F] = ???
* val customParent: Resource[F, Span.Auto[F]] = tracer
* val customParent: SpanOps.Aux[F, Span[F]] = tracer
* .spanBuilder("custom-parent")
* .withParent(span.context)
* .start
* .build
* }}}
*
* @see
Expand All @@ -54,7 +54,7 @@ private[otel4s] trait TracerMacro[F[_]] {
* @param attributes
* the set of attributes to associate with the span
*/
def span(name: String, attributes: Attribute[_]*): Resource[F, Span[F]] =
def span(name: String, attributes: Attribute[_]*): SpanOps.Aux[F, Span[F]] =
macro TracerMacro.span

/** Creates a new root span. Even if a parent span is available in the scope,
Expand All @@ -75,7 +75,7 @@ private[otel4s] trait TracerMacro[F[_]] {
def rootSpan(
name: String,
attributes: Attribute[_]*
): Resource[F, Span[F]] =
): SpanOps.Aux[F, Span[F]] =
macro TracerMacro.rootSpan

/** Creates a new child span. The span is automatically attached to a parent
Expand Down Expand Up @@ -103,7 +103,7 @@ private[otel4s] trait TracerMacro[F[_]] {
*/
def resourceSpan[A](name: String, attributes: Attribute[_]*)(
resource: Resource[F, A]
): Resource[F, Span.Res[F, A]] =
): SpanOps.Aux[F, Span.Res[F, A]] =
macro TracerMacro.resourceSpan[F, A]
}

Expand All @@ -116,8 +116,7 @@ object TracerMacro {
): c.universe.Tree = {
import c.universe._
val meta = q"${c.prefix}.meta"

q"if ($meta.isEnabled) ${c.prefix}.spanBuilder($name).addAttributes(..$attributes).start else $meta.noopSpan"
q"(if ($meta.isEnabled) ${c.prefix}.spanBuilder($name).addAttributes(..$attributes) else $meta.noopSpanBuilder).build"
}

def rootSpan(c: blackbox.Context)(
Expand All @@ -126,8 +125,7 @@ object TracerMacro {
): c.universe.Tree = {
import c.universe._
val meta = q"${c.prefix}.meta"

q"if ($meta.isEnabled) ${c.prefix}.spanBuilder($name).root.addAttributes(..$attributes).start else $meta.noopSpan"
q"(if ($meta.isEnabled) ${c.prefix}.spanBuilder($name).root.addAttributes(..$attributes) else $meta.noopSpanBuilder).build"
}

def resourceSpan[F[_], A](c: blackbox.Context)(
Expand All @@ -137,7 +135,7 @@ object TracerMacro {
import c.universe._
val meta = q"${c.prefix}.meta"

q"if ($meta.isEnabled) ${c.prefix}.spanBuilder($name).addAttributes(..$attributes).wrapResource($resource).start else $meta.noopResSpan($resource)"
q"if ($meta.isEnabled) ${c.prefix}.spanBuilder($name).addAttributes(..$attributes).wrapResource($resource).build else $meta.noopResSpan($resource).build"
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ private[otel4s] trait TracerMacro[F[_]] {
* {{{
* val tracer: Tracer[F] = ???
* val span: Span[F] = ???
* val customParent: Resource[F, Span.Auto[F]] = tracer
* val customParent: SpanOps.Aux[F, Span[F]] = tracer
* .spanBuilder("custom-parent")
* .withParent(span.context)
* .start
* .build
* }}}
*
* @see
Expand All @@ -59,7 +59,7 @@ private[otel4s] trait TracerMacro[F[_]] {
inline def span(
inline name: String,
inline attributes: Attribute[_]*
): Resource[F, Span[F]] =
): SpanOps.Aux[F, Span[F]] =
${ TracerMacro.span('self, 'name, 'attributes) }

/** Creates a new root span. Even if a parent span is available in the scope,
Expand All @@ -80,7 +80,7 @@ private[otel4s] trait TracerMacro[F[_]] {
inline def rootSpan(
inline name: String,
inline attributes: Attribute[_]*
): Resource[F, Span[F]] =
): SpanOps.Aux[F, Span[F]] =
${ TracerMacro.rootSpan('self, 'name, 'attributes) }

/** Creates a new child span. The span is automatically attached to a parent
Expand Down Expand Up @@ -109,7 +109,7 @@ private[otel4s] trait TracerMacro[F[_]] {
inline def resourceSpan[A](
inline name: String,
inline attributes: Attribute[_]*
)(inline resource: Resource[F, A]): Resource[F, Span.Res[F, A]] =
)(inline resource: Resource[F, A]): SpanOps.Aux[F, Span.Res[F, A]] =
${ TracerMacro.resourceSpan('self, 'name, 'attributes, 'resource) }

}
Expand All @@ -123,8 +123,8 @@ object TracerMacro {
)(using Quotes, Type[F]) =
'{
if ($tracer.meta.isEnabled)
$tracer.spanBuilder($name).addAttributes($attributes*).start
else $tracer.meta.noopSpan
$tracer.spanBuilder($name).addAttributes($attributes*).build
else $tracer.meta.noopSpanBuilder.build
}

def rootSpan[F[_]](
Expand All @@ -134,8 +134,8 @@ object TracerMacro {
)(using Quotes, Type[F]) =
'{
if ($tracer.meta.isEnabled)
$tracer.spanBuilder($name).root.addAttributes($attributes*).start
else $tracer.meta.noopSpan
$tracer.spanBuilder($name).root.addAttributes($attributes*).build
else $tracer.meta.noopSpanBuilder.build
}

def resourceSpan[F[_], A](
Expand All @@ -150,8 +150,8 @@ object TracerMacro {
.spanBuilder($name)
.addAttributes($attributes*)
.wrapResource($resource)
.start
else $tracer.meta.noopResSpan($resource)
.build
else $tracer.meta.noopResSpan($resource).build
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import scala.concurrent.duration.FiniteDuration
* {{{
* val tracer: Tracer[F] = ???
* val leaked: F[Unit] =
* tracer.spanBuilder("manual-span").startUnmanaged.flatMap { span =>
* tracer.spanBuilder("manual-span").build.startUnmanaged.flatMap { span =>
* span.setStatus(Status.Ok, "all good")
* }
* }}}
Expand All @@ -44,7 +44,7 @@ import scala.concurrent.duration.FiniteDuration
* {{{
* val tracer: Tracer[F] = ???
* val ok: F[Unit] =
* tracer.spanBuilder("manual-span").startUnmanaged.flatMap { span =>
* tracer.spanBuilder("manual-span").build.startUnmanaged.flatMap { span =>
* span.setStatus(Status.Ok, "all good") >> span.end
* }
* }}}
Expand All @@ -58,7 +58,7 @@ import scala.concurrent.duration.FiniteDuration
* {{{
* val tracer: Tracer[F] = ???
* val ok: F[Unit] =
* tracer.spanBuilder("auto-span").start.use { span =>
* tracer.spanBuilder("auto-span").build.use { span =>
* span.setStatus(Status.Ok, "all good")
* }
* }}}
Expand Down
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.effect.kernel.MonadCancelThrow
import cats.effect.kernel.Resource

import scala.concurrent.duration.FiniteDuration
Expand Down Expand Up @@ -80,9 +81,8 @@ trait SpanBuilder[F[_]] {
/** Sets an explicit start timestamp for the newly created span.
*
* Use this method to specify an explicit start timestamp. If not called, the
* implementation will use the timestamp value at ([[start]],
* [[startUnmanaged]], [[wrapResource]]) time, which should be the default
* case.
* implementation will use the timestamp value from the method called on
* [[build]], which should be the default case.
*
* '''Note''': the timestamp should be based on `Clock[F].realTime`. Using
* `Clock[F].monotonic` may lead to a missing span.
Expand Down Expand Up @@ -134,7 +134,7 @@ trait SpanBuilder[F[_]] {
* val tracer: Tracer[F] = ???
* val resource: Resource[F, String] = Resource.eval(Sync[F].delay("string"))
* val ok: F[Unit] =
* tracer.spanBuilder("wrapped-resource").wrapResource(resource).start.use { case span @ Span.Res(value) =>
* tracer.spanBuilder("wrapped-resource").wrapResource(resource).build.use { case span @ Span.Res(value) =>
* span.setStatus(Status.Ok, s"all good. resource value: $${value}")
* }
* }}}
Expand All @@ -145,55 +145,7 @@ trait SpanBuilder[F[_]] {
resource: Resource[F, A]
)(implicit ev: Result =:= Span[F]): SpanBuilder.Aux[F, Span.Res[F, A]]

/** Creates a [[Span]]. 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.
*
* Leaked span:
* {{{
* val tracer: Tracer[F] = ???
* val leaked: F[Unit] =
* tracer.spanBuilder("manual-span").startUnmanaged.flatMap { span =>
* span.setStatus(Status.Ok, "all good")
* }
* }}}
*
* Properly ended span:
* {{{
* val tracer: Tracer[F] = ???
* val ok: F[Unit] =
* tracer.spanBuilder("manual-span").startUnmanaged.flatMap { span =>
* span.setStatus(Status.Ok, "all good") >> span.end
* }
* }}}
*
* @see
* [[start]] for a managed lifecycle
*/
def startUnmanaged(implicit ev: Result =:= Span[F]): F[Span[F]]

/** Creates a [[Span]]. Unlike [[startUnmanaged]] the lifecycle of the span is
* managed by the [[cats.effect.kernel.Resource Resource]]. That means the
* span is started upon resource allocation and ended upon finalization.
*
* The finalization strategy is determined by [[SpanFinalizer.Strategy]]. By
* default, the abnormal termination (error, cancelation) is recorded.
*
* @see
* default finalization strategy [[SpanFinalizer.Strategy.reportAbnormal]]
*
* @example
* {{{
* val tracer: Tracer[F] = ???
* val ok: F[Unit] =
* tracer.spanBuilder("auto-span").start.use { span =>
* span.setStatus(Status.Ok, "all good")
* }
* }}}
*/
def start: Resource[F, Result]
def build: SpanOps.Aux[F, Result]
}

object SpanBuilder {
Expand All @@ -202,12 +154,12 @@ object SpanBuilder {
type Result = A
}

def noop[F[_]: Applicative](
def noop[F[_]: MonadCancelThrow](
back: Span.Backend[F]
): SpanBuilder.Aux[F, Span[F]] =
make(back, Resource.pure(Span.fromBackend(back)))

private def make[F[_]: Applicative, Res <: Span[F]](
private def make[F[_]: MonadCancelThrow, Res <: Span[F]](
back: Span.Backend[F],
startSpan: Resource[F, Res]
): SpanBuilder.Aux[F, Res] =
Expand Down Expand Up @@ -244,11 +196,21 @@ object SpanBuilder {

def withStartTimestamp(timestamp: FiniteDuration): Builder = this

def startUnmanaged(implicit ev: Result =:= Span[F]): F[Span[F]] =
Applicative[F].pure(span)
def build = new SpanOps[F] {
type Result = Res

def startUnmanaged(implicit ev: Result =:= Span[F]): F[Span[F]] =
Applicative[F].pure(span)

def use[A](f: Res => F[A]): F[A] =
startSpan.use(res => f(res))

def use_ : F[Unit] =
startSpan.use_

val start: Resource[F, Res] =
startSpan
def surround[A](fa: F[A]): F[A] =
fa
}
}

}
81 changes: 81 additions & 0 deletions core/trace/src/main/scala/org/typelevel/otel4s/trace/SpanOps.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright 2022 Typelevel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.typelevel.otel4s.trace

trait SpanOps[F[_]] {
type Result <: Span[F]

/** Creates a [[Span]]. 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.
*
* Leaked span:
* {{{
* val tracer: Tracer[F] = ???
* val leaked: F[Unit] =
* tracer.spanBuilder("manual-span").build.startUnmanaged.flatMap { span =>
* span.setStatus(Status.Ok, "all good")
* }
* }}}
*
* Properly ended span:
* {{{
* val tracer: Tracer[F] = ???
* val ok: F[Unit] =
* tracer.spanBuilder("manual-span").build.startUnmanaged.flatMap { span =>
* span.setStatus(Status.Ok, "all good") >> span.end
* }
* }}}
*
* @see
* [[use]], [[use_]], or [[surround]] for a managed lifecycle
*/
def startUnmanaged(implicit ev: Result =:= Span[F]): F[Span[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
* produce the effect, and ended when the effect completes.
*
* The finalization strategy is determined by [[SpanFinalizer.Strategy]]. By
* default, the abnormal termination (error, cancelation) is recorded.
*
* @see
* default finalization strategy [[SpanFinalizer.Strategy.reportAbnormal]]
*
* @example
* {{{
* val tracer: Tracer[F] = ???
* val ok: F[Unit] =
* tracer.spanBuilder("auto-span").build.use { span =>
* span.setStatus(Status.Ok, "all good")
* }
* }}}
*/
def use[A](f: Result => F[A]): F[A]

def use_ : F[Unit]

def surround[A](fa: F[A]): F[A]
}

object SpanOps {
type Aux[F[_], A] = SpanOps[F] {
type Result = A
}
}
Loading