Skip to content

Commit

Permalink
Improve docs and polish API (#737)
Browse files Browse the repository at this point in the history
* Refactor build.sbt

* Polish StatusMapper API

* Remove unnecessary aspects from opencensus Tracing

* Polish OpenTracing API

* Make opentelemtry Tracing API more safe

* Update opentracing doc

* Fix compileExamples

* Add possibility to set status code based on a failure result
  • Loading branch information
grouzen authored Jul 23, 2023
1 parent 0cafcd5 commit 2ea2c21
Show file tree
Hide file tree
Showing 13 changed files with 498 additions and 417 deletions.
80 changes: 44 additions & 36 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ enablePlugins(ZioSbtEcosystemPlugin, ZioSbtCiPlugin)

inThisBuild(
List(
name := "ZIO Telemetry",
organization := "dev.zio",
zioVersion := "2.0.15",
homepage := Some(url("https://zio.dev/zio-telemetry/")),
licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")),
developers := List(
name := "ZIO Telemetry",
organization := "dev.zio",
zioVersion := "2.0.15",
homepage := Some(url("https://zio.dev/zio-telemetry/")),
licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")),
developers := List(
Developer(
"mijicd",
"Dejan Mijic",
Expand All @@ -21,12 +21,11 @@ inThisBuild(
url("https://github.com/runtologist")
)
),
crossScalaVersions := Seq(scala212.value, scala213.value, "3.3.0"),
ciEnabledBranches := Seq("series/2.x"),
pgpPassphrase := sys.env.get("PGP_PASSWORD").map(_.toArray),
pgpPublicRing := file("/tmp/public.asc"),
pgpSecretRing := file("/tmp/secret.asc"),
scmInfo := Some(
ciEnabledBranches := Seq("series/2.x"),
pgpPassphrase := sys.env.get("PGP_PASSWORD").map(_.toArray),
pgpPublicRing := file("/tmp/public.asc"),
pgpSecretRing := file("/tmp/secret.asc"),
scmInfo := Some(
ScmInfo(
url("https://github.com/zio/zio-telemetry/"),
"scm:git:[email protected]:zio/zio-telemetry.git"
Expand All @@ -45,9 +44,27 @@ addCommandAlias(
"opentracingExample/compile;opentelemetryExample/compile;opentelemetryInstrumentationExample/compile"
)

// Fix 'Flag set repeatedly' error allegedly introduced by the usage of sdtSettings
lazy val tempFixScalacOptions =
Seq("-deprecation", "-encoding", "utf8", "-feature", "-unchecked", "-language:implicitConversions")
def stdModuleSettings(name: Option[String], packageName: Option[String]) =
stdSettings(name, packageName) ++
Seq(
crossScalaVersions := Seq(scala212.value, scala213.value, scala3.value),
// Fix 'Flag set repeatedly' error allegedly introduced by the usage of sdtSettings: https://github.com/zio/zio-sbt/issues/221
scalacOptions --= Seq(
"-deprecation",
"-encoding",
"utf8",
"-feature",
"-unchecked",
"-language:implicitConversions"
)
)

def stdExampleSettings(name: Option[String], packageName: Option[String]) =
stdSettings(name, packageName) ++
Seq(
crossScalaVersions := Seq(scala212.value, scala213.value),
publish / skip := true
)

lazy val root =
project
Expand All @@ -60,11 +77,10 @@ lazy val opentracing =
.in(file("opentracing"))
.settings(enableZIO())
.settings(
stdSettings(
stdModuleSettings(
name = Some("zio-opentracing"),
packageName = Some("zio.telemetry.opentracing")
),
scalacOptions --= tempFixScalacOptions
)
)
.settings(libraryDependencies ++= Dependencies.opentracing)

Expand All @@ -73,23 +89,21 @@ lazy val opentelemetry =
.in(file("opentelemetry"))
.settings(enableZIO())
.settings(
stdSettings(
stdModuleSettings(
name = Some("zio-opentelemetry"),
packageName = Some("zio.telemetry.opentelemetry")
),
scalacOptions --= tempFixScalacOptions
)
)
.settings(libraryDependencies ++= Dependencies.opentelemetry)

lazy val opencensus = project
.in(file("opencensus"))
.settings(enableZIO())
.settings(
stdSettings(
stdModuleSettings(
name = Some("zio-opencensus"),
packageName = Some("zio.telemetry.opencensus")
),
scalacOptions --= tempFixScalacOptions
)
)
.settings(libraryDependencies ++= Dependencies.opencensus)

Expand All @@ -98,13 +112,11 @@ lazy val opentracingExample =
.in(file("opentracing-example"))
.settings(enableZIO())
.settings(
crossScalaVersions := Seq(scala212.value, scala213.value),
stdSettings(
stdExampleSettings(
name = Some("opentracing-example"),
packageName = Some("zio.telemetry.opentracing.example")
)
)
.settings(publish / skip := true)
.settings(libraryDependencies ++= Dependencies.opentracingExample)
.dependsOn(opentracing)

Expand All @@ -113,13 +125,11 @@ lazy val opentelemetryExample =
.in(file("opentelemetry-example"))
.settings(enableZIO())
.settings(
crossScalaVersions := Seq(scala212.value, scala213.value),
stdSettings(
stdExampleSettings(
name = Some("opentelemetry-example"),
packageName = Some("zio.telemetry.opentelemetry.example")
)
)
.settings(publish / skip := true)
.settings(libraryDependencies ++= Dependencies.opentelemetryExample)
.dependsOn(opentelemetry)

Expand All @@ -128,27 +138,25 @@ lazy val opentelemetryInstrumentationExample =
.in(file("opentelemetry-instrumentation-example"))
.settings(enableZIO())
.settings(
crossScalaVersions := Seq(scala212.value, scala213.value),
stdSettings(
stdExampleSettings(
name = Some("opentelemetry-instrumentation-example"),
packageName = Some("zio.telemetry.opentelemetry.instrumentation.example")
)
)
.settings(publish / skip := true)
.settings(libraryDependencies ++= Dependencies.opentelemetryInstrumentationExample)
.dependsOn(opentelemetry)

lazy val docs =
project
.in(file("zio-telemetry-docs"))
.settings(
crossScalaVersions := Seq(scala212.value, scala213.value, scala3.value),
moduleName := "zio-telemetry-docs",
scalacOptions -= "-Yno-imports",
scalacOptions -= "-Xfatal-warnings",
projectName := "ZIO Telemetry",
mainModuleName := (opentracing / moduleName).value,
projectStage := ProjectStage.ProductionReady,
ScalaUnidoc / unidoc / unidocProjectFilter := inProjects(opentracing, opentelemetry, opencensus)
ScalaUnidoc / unidoc / unidocProjectFilter := inProjects(opentracing, opentelemetry, opencensus),
scalacOptions --= Seq("-Yno-imports", "-Xfatal-warnings")
)
.dependsOn(opentracing, opentelemetry, opencensus)
.enablePlugins(WebsitePlugin)
4 changes: 2 additions & 2 deletions docs/opentelemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import zio.telemetry.opentelemetry.example.JaegerTracer
import io.opentelemetry.api.trace.{ SpanKind, StatusCode }
import zio._

val statusMapper = StatusMapper.failure[Unit](StatusCode.UNSET)
val statusMapper = StatusMapper.failureThrowable(_ => StatusCode.UNSET)

val app =
ZIO.serviceWithZIO[Tracing] { tracing =>
Expand Down Expand Up @@ -133,7 +133,7 @@ import zio.telemetry.opentelemetry.example.JaegerTracer
import io.opentelemetry.api.trace.{SpanKind, StatusCode}
import zio._

val statusMapper = StatusMapper.failure[Unit](StatusCode.UNSET)
val statusMapper = StatusMapper.failureNoException(_ => StatusCode.UNSET)

val app =
ZIO.serviceWithZIO[Tracing] { tracing =>
Expand Down
23 changes: 11 additions & 12 deletions docs/opentracing.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ val app =
import tracing.aspects._

(for {
_ <- ZIO.unit @@ tag(Tags.SPAN_KIND.getKey, Tags.SPAN_KIND_CLIENT)
_ <- ZIO.unit @@ tag(Tags.HTTP_METHOD.getKey, "GET")
_ <- ZIO.unit @@ setBaggageItem("proxy-baggage-item-key", "proxy-baggage-item-value")
_ <- tracing.tag(Tags.SPAN_KIND.getKey, Tags.SPAN_KIND_CLIENT)
_ <- tracing.tag(Tags.HTTP_METHOD.getKey, "GET")
_ <- tracing.setBaggageItem("proxy-baggage-item-key", "proxy-baggage-item-value")
message <- Console.readline
_ <- ZIO.unit @@ log("Message has been read")
_ <- tracing.log("Message has been read")
} yield message) @@ root("/app")
}.provide(OpenTracing.live, JaegerTracer.live("my-app"))
```
Expand All @@ -48,15 +48,14 @@ ZIO.serviceWithZIO[OpenTracing] { tracing =>
import tracing.aspects._

// start a new root span and set some baggage item
val zio1 = ZIO.unit @@
setBaggage("foo", "bar") @@
root("root span")
val zio1 = tracing.setBaggage("foo", "bar") @@ root("root span")

// start a child of the current span, set a tag and log a message
val zio2 = ZIO.unit @@
tag("http.status_code", 200) @@
log("doing some serious work here!") @@
span("child span")
val zio2 =
(for {
_ <- tracing.tag("http.status_code", 200)
_ <- tracing.log("doing some serious work here!")
} yield ()) @@ span("child span")
}
```

Expand All @@ -76,7 +75,7 @@ ZIO.serviceWithZIO[OpenTracing] { tracing =>

val buffer = new TextMapAdapter(mutable.Map.empty.asJava)
for {
_ <- ZIO.unit @@ inject(Format.Builtin.TEXT_MAP, buffer)
_ <- tracing.inject(Format.Builtin.TEXT_MAP, buffer)
_ <- ZIO.unit @@ spanFrom(Format.Builtin.TEXT_MAP, buffer, "child of remote span")
} yield buffer
}
Expand Down
18 changes: 0 additions & 18 deletions opencensus/src/main/scala/zio/telemetry/opencensus/Tracing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -100,24 +100,6 @@ trait Tracing { self =>
self.fromRootSpan(format, carrier, getter, name, kind, toErrorStatus, attributes)(zio)
}

def inject[C](
format: TextFormat,
carrier: C,
setter: TextFormat.Setter[C]
): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] =
new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] {
override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] =
self.inject(format, carrier, setter) *> zio
}

def withAttributes(
attrs: (String, AttributeValue)*
): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] =
new ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] {
override def apply[R, E, A](zio: ZIO[R, E, A])(implicit trace: Trace): ZIO[R, E, A] =
self.putAttributes(attrs.toMap) *> zio
}

}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package zio.telemetry.opentelemetry.example

import io.opentelemetry.api.common.Attributes
import io.opentelemetry.api.trace.Tracer
import io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter
import io.opentelemetry.sdk.OpenTelemetrySdk
import io.opentelemetry.sdk.resources.Resource
import io.opentelemetry.sdk.trace.SdkTracerProvider
Expand All @@ -23,7 +23,7 @@ object JaegerTracer {

def makeTracer(host: String): Task[Tracer] =
for {
spanExporter <- ZIO.attempt(JaegerGrpcSpanExporter.builder().setEndpoint(host).build())
spanExporter <- ZIO.attempt(OtlpGrpcSpanExporter.builder().setEndpoint(host).build())
spanProcessor <- ZIO.succeed(SimpleSpanProcessor.create(spanExporter))
tracerProvider <-
ZIO.attempt(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ case class ProxyHttpApp(client: Client, tracing: Tracing, baggage: Baggage) {

import tracing.aspects._

private val statusMapper: StatusMapper[Throwable, Any] = StatusMapper.failure(StatusCode.UNSET)
private val statusMapper: StatusMapper[Throwable, Any] = StatusMapper.failureThrowable(_ => StatusCode.UNSET)

val routes: HttpApp[Any, Throwable] =
Http.collectZIO { case Method.GET -> _ / "statuses" =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,80 @@
package zio.telemetry.opentelemetry.tracing

import io.opentelemetry.api.trace.StatusCode
import zio.telemetry.opentelemetry.tracing.StatusMapper.StatusMapperResult
import zio.telemetry.opentelemetry.tracing.StatusMapper.Result

case class StatusMapper[-E, -A](
failure: PartialFunction[E, StatusMapperResult[Throwable]],
success: PartialFunction[A, StatusMapperResult[String]]
/**
* Maps the result of a wrapped ZIO effect to the status of the [[io.opentelemetry.api.trace.Span]].
*
* For more details, see:
* [[https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#set-status Set status]]
*
* Usage examples:
* {{{
* StatusMapper.failure[MyError](_ => StatusCode.ERROR)(e => Some(new RuntimeException(e.message)))
* StatusMapper.failureNoException(_ => StatusCode.ERROR)
* StatusMapper.failureThrowable(StatusCode.ERROR)
*
* StatusMapper.success[Response] {
* resp => if(resp.code == 500) StatusCode.ERROR else StatusCode.OK
* } { resp =>
* if(resp.code == 500) Some(resp.errorMessage) else None
* }
* StatusMapper.successNoDescription[Response](_ => StatusCode.OK)
*
* StatusMapper.both(
* StatusMapper.failureThrowable(StatusCode.ERROR),
* StatusMapper.successNoDescription[Any](_ => StatusCode.OK)
* )
* }}}
* @param failure
* partial function to map the ZIO failure to [[io.opentelemetry.api.trace.StatusCode]] and [[java.lang.Throwable]].
* The latter is used to record the exception, see:
* [[https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/exceptions.md#recording-an-exception Recording an exception]]
* @param success
* partial function to map the ZIO success to [[io.opentelemetry.api.trace.StatusCode]] and status description
* @tparam E
* @tparam A
*/
sealed abstract class StatusMapper[-E, -A](
val failure: PartialFunction[E, Result[Throwable]],
val success: PartialFunction[A, Result[String]]
)

object StatusMapper {

final case class Failure[-E](pf: PartialFunction[E, Result[Throwable]])
extends StatusMapper[E, Any](pf, PartialFunction.empty)
final case class Success[-A](pf: PartialFunction[A, Result[String]])
extends StatusMapper[Any, A](PartialFunction.empty, pf)

final case class Result[+T](statusCode: StatusCode, error: Option[T] = None)

private[tracing] def apply[E, A](
failure: PartialFunction[E, Result[Throwable]],
success: PartialFunction[A, Result[String]]
): StatusMapper[E, A] =
new StatusMapper[E, A](failure, success) {}

def both[E, A](failure: Failure[E], success: Success[A]): StatusMapper[E, A] =
StatusMapper[E, A](failure.pf, success.pf)

val default: StatusMapper[Any, Any] =
StatusMapper(PartialFunction.empty, PartialFunction.empty)

final case class StatusMapperResult[+T](statusCode: StatusCode, error: Option[T] = None)

def failure(statusCode: StatusCode): StatusMapper[Throwable, Any] = StatusMapper(
{ case e => StatusMapperResult(statusCode, Option(e)) },
PartialFunction.empty
)

def success[A](
statusCode: StatusCode,
f: A => Option[String] = { (_: A) => Option.empty[String] }
): StatusMapper[Any, A] =
StatusMapper(
PartialFunction.empty,
{ case a => StatusMapperResult(statusCode, f(a)) }
)
def failure[E](toStatusCode: E => StatusCode)(toError: E => Option[Throwable]): Failure[E] =
Failure { case e => Result(toStatusCode(e), toError(e)) }

def failureNoException[E](toStatusCode: E => StatusCode): Failure[E] =
Failure { case e => Result(toStatusCode(e)) }

def failureThrowable(toStatusCode: Throwable => StatusCode): Failure[Throwable] =
Failure { case e => Result(toStatusCode(e), Option(e)) }

def success[A](toStatusCode: A => StatusCode)(toError: A => Option[String]): Success[A] =
Success { case a => Result(toStatusCode(a), toError(a)) }

def successNoDescription[A](toStatusCode: A => StatusCode): Success[A] =
Success { case a => Result(toStatusCode(a)) }

}
Loading

0 comments on commit 2ea2c21

Please sign in to comment.