Skip to content

Commit

Permalink
Implement tracing API (#37)
Browse files Browse the repository at this point in the history
* Define tracing API

* Add `spanBuilder` to `Tracer`. Better naming of `SpanContext` members

* Rename `SamplingStrategy` -> `SamplingDecision`

* Update scaladoc. Add `SpanFinalizer`

* Fix scaladoc

* Implement tracing API

* Improve Scaladoc

* Improve Scaladoc

* Better phrasing

* Docs cleanup

* Properly handle 'noop' scope

* Hide implementation of `SpanFinalizer`

* Docs update

* Replace `asChildOf` with `childScope` and `withParent`

* Add `withParent` to `SpanBuilder`

* Replace `childOf` with `childScope` in `Tracer`

* Make `Span` always return `SpanContext`

* Test propagation of trace info over stream scopes

* Make `Span` always have a `SpanContext`

* Add `setAttribute` method to `Span`

* Use `IO.to` syntax

* Update layout

* Run `sbt githubWorkflowGenerate`

* Update layout

* Revert redundant changes

* Cleanup

* Run `sbt githubWorkflowGenerate`

* Update layout

* run `scalafixAll`

* Implement `Span#setAttribute` via macro

* Fix method name

* Update implementation

* Minor cleanup of `build.sbt`

* Remove duplicated `munit` dependency

* Discard statement instead of calling `void`

* Hide implementation details behind `package[java]`

* Rename `withAttribute` -> `addAttribute`

* Merge upstream

* Rename `SpanBuilder.create` -> `SpanBuilder.start`, `SpanBuilder.createManual` -> `SpanBuilder.startUnmanaged`

* Rename sbt modules `tracing` -> `trace`

Co-authored-by: Ross A. Baker <ross@rossabaker.com>
  • Loading branch information
iRevive and rossabaker authored Jan 23, 2023
1 parent 83bb37e commit c1e9cfd
Show file tree
Hide file tree
Showing 30 changed files with 2,551 additions and 154 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,11 @@ jobs:

- name: Make target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
run: mkdir -p testkit/metrics/jvm/target java/metrics/target testkit/common/jvm/target core/tracing/.jvm/target core/tracing/.js/target target core/common/.jvm/target .js/target core/metrics/.native/target core/all/.native/target site/target core/metrics/.jvm/target core/all/.js/target java/all/target java/common/target core/metrics/.js/target core/all/.jvm/target .jvm/target .native/target core/common/.native/target core/common/.js/target core/tracing/.native/target testkit/all/jvm/target project/target
run: mkdir -p testkit/metrics/jvm/target java/metrics/target testkit/common/jvm/target core/trace/.js/target target core/common/.jvm/target java/trace/target .js/target core/metrics/.native/target core/all/.native/target site/target core/metrics/.jvm/target core/all/.js/target java/all/target java/common/target core/metrics/.js/target core/all/.jvm/target .jvm/target core/trace/.native/target .native/target core/trace/.jvm/target core/common/.native/target core/common/.js/target testkit/all/jvm/target project/target

- name: Compress target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
run: tar cf targets.tar testkit/metrics/jvm/target java/metrics/target testkit/common/jvm/target core/tracing/.jvm/target core/tracing/.js/target target core/common/.jvm/target .js/target core/metrics/.native/target core/all/.native/target site/target core/metrics/.jvm/target core/all/.js/target java/all/target java/common/target core/metrics/.js/target core/all/.jvm/target .jvm/target .native/target core/common/.native/target core/common/.js/target core/tracing/.native/target testkit/all/jvm/target project/target
run: tar cf targets.tar testkit/metrics/jvm/target java/metrics/target testkit/common/jvm/target core/trace/.js/target target core/common/.jvm/target java/trace/target .js/target core/metrics/.native/target core/all/.native/target site/target core/metrics/.jvm/target core/all/.js/target java/all/target java/common/target core/metrics/.js/target core/all/.jvm/target .jvm/target core/trace/.native/target .native/target core/trace/.jvm/target core/common/.native/target core/common/.js/target testkit/all/jvm/target project/target

- name: Upload target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
Expand Down
49 changes: 34 additions & 15 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ ThisBuild / scalaVersion := Scala213 // the default Scala

val CatsVersion = "2.9.0"
val CatsEffectVersion = "3.4.5"
val FS2Version = "3.5.0"
val MUnitVersion = "1.0.0-M7"
val MUnitCatsEffectVersion = "2.0.0-M3"
val OpenTelemetryVersion = "1.22.0"
Expand All @@ -36,17 +37,25 @@ lazy val scalaReflectDependency = Def.settings(
}
)

lazy val munitDependencies = Def.settings(
libraryDependencies ++= Seq(
"org.scalameta" %%% "munit" % MUnitVersion % Test,
"org.typelevel" %%% "munit-cats-effect" % MUnitCatsEffectVersion % Test
)
)

lazy val root = tlCrossRootProject
.aggregate(
`core-common`,
`core-metrics`,
`core-tracing`,
`core-trace`,
core,
`testkit-common`,
`testkit-metrics`,
testkit,
`java-common`,
`java-metrics`,
`java-trace`,
java
)
.settings(name := "otel4s")
Expand All @@ -67,36 +76,33 @@ lazy val `core-metrics` = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.in(file("core/metrics"))
.dependsOn(`core-common`)
.settings(scalaReflectDependency)
.settings(munitDependencies)
.settings(
name := "otel4s-core-metrics",
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-effect-kernel" % CatsEffectVersion,
"org.scalameta" %%% "munit" % MUnitVersion % Test,
"org.typelevel" %%% "munit-cats-effect" % MUnitCatsEffectVersion % Test,
"org.typelevel" %%% "cats-effect-testkit" % CatsEffectVersion % Test
)
)

lazy val `core-tracing` = crossProject(JVMPlatform, JSPlatform, NativePlatform)
lazy val `core-trace` = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("core/tracing"))
.in(file("core/trace"))
.dependsOn(`core-common`)
.settings(scalaReflectDependency)
.settings(munitDependencies)
.settings(
name := "otel4s-core-tracing",
name := "otel4s-core-trace",
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-effect-kernel" % CatsEffectVersion,
"org.scodec" %%% "scodec-bits" % ScodecVersion,
"org.scalameta" %%% "munit" % MUnitVersion % Test,
"org.typelevel" %%% "munit-cats-effect" % MUnitCatsEffectVersion % Test,
"org.typelevel" %%% "cats-effect-testkit" % CatsEffectVersion % Test
"org.scodec" %%% "scodec-bits" % ScodecVersion
)
)

lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("core/all"))
.dependsOn(`core-common`, `core-metrics`, `core-tracing`)
.dependsOn(`core-common`, `core-metrics`, `core-trace`)
.settings(
name := "otel4s-core"
)
Expand Down Expand Up @@ -151,20 +157,33 @@ lazy val `java-common` = project
lazy val `java-metrics` = project
.in(file("java/metrics"))
.dependsOn(`java-common`, `core-metrics`.jvm, `testkit-metrics`.jvm)
.settings(munitDependencies)
.settings(
name := "otel4s-java-metrics",
libraryDependencies ++= Seq(
"io.opentelemetry" % "opentelemetry-api" % OpenTelemetryVersion,
"io.opentelemetry" % "opentelemetry-sdk" % OpenTelemetryVersion % Test,
"io.opentelemetry" % "opentelemetry-sdk-testing" % OpenTelemetryVersion % Test
)
)

lazy val `java-trace` = project
.in(file("java/trace"))
.dependsOn(`java-common`, `core-trace`.jvm)
.settings(munitDependencies)
.settings(
name := "otel4s-java-trace",
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-effect" % CatsEffectVersion,
"io.opentelemetry" % "opentelemetry-sdk" % OpenTelemetryVersion % Test,
"io.opentelemetry" % "opentelemetry-sdk-testing" % OpenTelemetryVersion % Test,
"org.scalameta" %% "munit" % MUnitVersion % Test,
"org.typelevel" %% "munit-cats-effect" % MUnitCatsEffectVersion % Test
"org.typelevel" %%% "cats-effect-testkit" % CatsEffectVersion % Test,
"co.fs2" %% "fs2-core" % FS2Version % Test
)
)

lazy val java = project
.in(file("java/all"))
.dependsOn(core.jvm, `java-metrics`)
.dependsOn(core.jvm, `java-metrics`, `java-trace`)
.settings(
name := "otel4s-java"
)
Expand Down
5 changes: 5 additions & 0 deletions core/all/src/main/scala/org/typelevel/otel4s/Otel4s.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@
package org.typelevel.otel4s

import org.typelevel.otel4s.metrics.MeterProvider
import org.typelevel.otel4s.trace.TracerProvider

trait Otel4s[F[_]] {

/** A registry for creating named meters.
*/
def meterProvider: MeterProvider[F]

/** An entry point of the tracing API.
*/
def tracerProvider: TracerProvider[F]
}
202 changes: 202 additions & 0 deletions core/trace/src/main/scala-2/org/typelevel/otel4s/trace/SpanMacro.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
/*
* 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
package trace

import scala.concurrent.duration.FiniteDuration

private[otel4s] trait SpanMacro[F[_]] {
self: Span[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.
*
* @param attribute
* the attribute to add to the span
*/
def addAttribute[A](attribute: Attribute[A]): F[Unit] =
macro SpanMacro.addAttribute[A]

/** Adds attributes to the span. If the span previously contained a mapping
* for any of the keys, the old values are replaced by the specified values.
*
* @param attributes
* the set of attributes to add to the span
*/
def addAttributes(attributes: Attribute[_]*): F[Unit] =
macro SpanMacro.addAttributes

/** Adds an event to the span with the given attributes. The timestamp of the
* event will be the current time.
*
* @param name
* the name of the event
*
* @param attributes
* the set of attributes to associate with the event
*/
def addEvent(name: String, attributes: Attribute[_]*): F[Unit] =
macro SpanMacro.addEvent

/** Adds an event to the span with the given attributes and timestamp.
*
* '''Note''': the timestamp should be based on `Clock[F].realTime`. Using
* `Clock[F].monotonic` may lead to an incorrect data.
*
* @param name
* the name of the event
*
* @param timestamp
* the explicit event timestamp since epoch
*
* @param attributes
* the set of attributes to associate with the event
*/
def addEvent(
name: String,
timestamp: FiniteDuration,
attributes: Attribute[_]*
): F[Unit] =
macro SpanMacro.addEventWithTimestamp

/** Records information about the `Throwable` to the span.
*
* @param exception
* the `Throwable` to record
*
* @param attributes
* the set of attributes to associate with the value
*/
def recordException(
exception: Throwable,
attributes: Attribute[_]*
): F[Unit] =
macro SpanMacro.recordException

/** Sets the status to the span.
*
* Only the value of the last call will be recorded, and implementations are
* free to ignore previous calls.
*
* @param status
* the [[Status]] to set
*/
def setStatus(status: Status): F[Unit] =
macro SpanMacro.setStatus

/** Sets the status to the span.
*
* Only the value of the last call will be recorded, and implementations are
* free to ignore previous calls.
*
* @param status
* the [[Status]] to set
*
* @param description
* the description of the [[Status]]
*/
def setStatus(status: Status, description: String): F[Unit] =
macro SpanMacro.setStatusWithDescription

}

object SpanMacro {
import scala.reflect.macros.blackbox

def addAttribute[A](c: blackbox.Context)(
attribute: c.Expr[Attribute[A]]
): c.universe.Tree = {
import c.universe._

val backend = q"${c.prefix}.backend"
val meta = q"$backend.meta"

q"if ($meta.isEnabled) $backend.addAttributes($attribute) else $meta.unit"
}

def addAttributes(c: blackbox.Context)(
attributes: c.Expr[Attribute[_]]*
): c.universe.Tree = {
import c.universe._

val backend = q"${c.prefix}.backend"
val meta = q"$backend.meta"

q"if ($meta.isEnabled) $backend.addAttributes(..$attributes) else $meta.unit"
}

def addEvent(c: blackbox.Context)(
name: c.Expr[String],
attributes: c.Expr[Attribute[_]]*
): c.universe.Tree = {
import c.universe._

val backend = q"${c.prefix}.backend"
val meta = q"$backend.meta"

q"if ($meta.isEnabled) $backend.addEvent($name, ..$attributes) else $meta.unit"
}

def addEventWithTimestamp(c: blackbox.Context)(
name: c.Expr[String],
timestamp: c.Expr[FiniteDuration],
attributes: c.Expr[Attribute[_]]*
): c.universe.Tree = {
import c.universe._

val backend = q"${c.prefix}.backend"
val meta = q"$backend.meta"

q"if ($meta.isEnabled) $backend.addEvent($name, $timestamp, ..$attributes) else $meta.unit"
}

def recordException(c: blackbox.Context)(
exception: c.Expr[Throwable],
attributes: c.Expr[Attribute[_]]*
): c.universe.Tree = {
import c.universe._

val backend = q"${c.prefix}.backend"
val meta = q"$backend.meta"

q"if ($meta.isEnabled) $backend.recordException($exception, ..$attributes) else $meta.unit"
}

def setStatus(c: blackbox.Context)(
status: c.Expr[Status]
): c.universe.Tree = {
import c.universe._

val backend = q"${c.prefix}.backend"
val meta = q"$backend.meta"

q"if ($meta.isEnabled) $backend.setStatus($status) else $meta.unit"
}

def setStatusWithDescription(c: blackbox.Context)(
status: c.Expr[Status],
description: c.Expr[String]
): c.universe.Tree = {
import c.universe._

val backend = q"${c.prefix}.backend"
val meta = q"$backend.meta"

q"if ($meta.isEnabled) $backend.setStatus($status, $description) else $meta.unit"
}

}
Loading

0 comments on commit c1e9cfd

Please sign in to comment.