Skip to content

Commit

Permalink
oteljava: refactor Metrics, Traces, and OtelJava API
Browse files Browse the repository at this point in the history
  • Loading branch information
iRevive committed Dec 16, 2024
1 parent 4f534a1 commit 4ecaeaa
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 113 deletions.
11 changes: 3 additions & 8 deletions docs/oteljava/tracing-java-interop.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,18 @@ It can be constructed in the following way:
```scala mdoc:silent
import cats.effect._
import cats.mtl.Local
import cats.syntax.functor._
import org.typelevel.otel4s.instances.local._ // brings Local derived from IOLocal
import org.typelevel.otel4s.oteljava.context.Context
import org.typelevel.otel4s.oteljava.OtelJava
import io.opentelemetry.api.GlobalOpenTelemetry

def createOtel4s[F[_]: Async](implicit L: Local[F, Context]): F[OtelJava[F]] =
Async[F].delay(GlobalOpenTelemetry.get).map(OtelJava.local[F])

def program[F[_]: Async](otel4s: OtelJava[F])(implicit L: Local[F, Context]): F[Unit] = {
val _ = (otel4s, L) // both OtelJava and Local[F, Context] are available here
Async[F].unit
}

val run: IO[Unit] =
IOLocal(Context.root).flatMap { implicit ioLocal: IOLocal[Context] =>
createOtel4s[IO].flatMap(otel4s => program(otel4s))
OtelJava.global[IO].flatMap { otel4s =>
implicit val local: Local[IO, Context] = otel4s.localContext
program(otel4s)
}
```

Expand Down
12 changes: 6 additions & 6 deletions docs/tracing-context-propagation.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ You can find both examples below and choose which one suits your requirements.
```scala mdoc:silent:reset
import cats.effect._
import cats.mtl.Local
import cats.syntax.functor._
import cats.syntax.flatMap._
import org.typelevel.otel4s.instances.local._ // brings Local derived from IOLocal
import org.typelevel.otel4s.oteljava.context.Context
import org.typelevel.otel4s.oteljava.OtelJava
import io.opentelemetry.api.GlobalOpenTelemetry

def createOtel4s[F[_]: Async](implicit L: Local[F, Context]): F[OtelJava[F]] =
Async[F].delay(GlobalOpenTelemetry.get).map(OtelJava.local[F])
Async[F].delay(GlobalOpenTelemetry.get).flatMap(OtelJava.fromJOpenTelemetry[F])

def program[F[_]: Async](otel4s: OtelJava[F]): F[Unit] = {
val _ = otel4s
Expand All @@ -52,7 +52,7 @@ val run: IO[Unit] =
}
```

If you don't need direct access to the `IOLocal` instance, there is also a shortcut `OtelJava.forAsync`:
If you don't need direct access to the `IOLocal` instance, there is also a shortcut `OtelJava.fromJOpenTelemetry`:

```scala mdoc:silent:reset
import cats.effect._
Expand All @@ -61,7 +61,7 @@ import org.typelevel.otel4s.oteljava.OtelJava
import io.opentelemetry.api.GlobalOpenTelemetry

def createOtel4s[F[_]: Async: LiftIO]: F[OtelJava[F]] =
Async[F].delay(GlobalOpenTelemetry.get).flatMap(OtelJava.forAsync[F])
Async[F].delay(GlobalOpenTelemetry.get).flatMap(OtelJava.fromJOpenTelemetry[F])

def program[F[_]: Async](otel4s: OtelJava[F]): F[Unit] = {
val _ = otel4s
Expand Down Expand Up @@ -90,15 +90,15 @@ val run: IO[Unit] =

```scala mdoc:silent:reset
import cats.effect._
import cats.syntax.functor._
import cats.syntax.flatMap._
import cats.data.Kleisli
import cats.mtl.Local
import org.typelevel.otel4s.oteljava.context.Context
import org.typelevel.otel4s.oteljava.OtelJava
import io.opentelemetry.api.GlobalOpenTelemetry

def createOtel4s[F[_]: Async](implicit L: Local[F, Context]): F[OtelJava[F]] =
Async[F].delay(GlobalOpenTelemetry.get).map(OtelJava.local[F])
Async[F].delay(GlobalOpenTelemetry.get).flatMap(OtelJava.fromJOpenTelemetry[F])

def program[F[_]: Async](otel4s: OtelJava[F]): F[Unit] = {
val _ = otel4s
Expand Down
6 changes: 1 addition & 5 deletions examples/src/main/scala/KleisliExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import cats.effect.Async
import cats.effect.IO
import cats.effect.IOApp
import cats.effect.Resource
import io.opentelemetry.api.GlobalOpenTelemetry
import org.typelevel.otel4s.oteljava.OtelJava
import org.typelevel.otel4s.oteljava.context.Context
import org.typelevel.otel4s.oteljava.context.LocalContext
Expand All @@ -30,10 +29,7 @@ object KleisliExample extends IOApp.Simple {
Tracer[F].span("work").surround(Async[F].delay(println("I'm working")))

private def tracerResource[F[_]: Async: LocalContext]: Resource[F, Tracer[F]] =
Resource
.eval(Async[F].delay(GlobalOpenTelemetry.get))
.map(OtelJava.local[F])
.evalMap(_.tracerProvider.get("kleisli-example"))
Resource.eval(OtelJava.global[F]).evalMap(_.tracerProvider.get("kleisli-example"))

def run: IO[Unit] =
tracerResource[Kleisli[IO, Context, *]]
Expand Down
9 changes: 3 additions & 6 deletions examples/src/main/scala/PekkoHttpExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import cats.effect.Async
import cats.effect.IO
import cats.effect.IOApp
import cats.effect.IOLocal
import cats.effect.Resource
import cats.effect.Sync
import cats.effect.std.Random
Expand All @@ -26,7 +25,6 @@ import cats.mtl.Local
import cats.syntax.applicative._
import cats.syntax.flatMap._
import cats.syntax.functor._
import io.opentelemetry.api.GlobalOpenTelemetry
import io.opentelemetry.context.{Context => JContext}
import io.opentelemetry.instrumentation.annotations.WithSpan
import org.apache.pekko.actor.ActorSystem
Expand All @@ -37,9 +35,9 @@ import org.apache.pekko.http.scaladsl.server.Directives._
import org.apache.pekko.http.scaladsl.server.Route
import org.apache.pekko.util.ByteString
import org.typelevel.otel4s.Attribute
import org.typelevel.otel4s.instances.local._
import org.typelevel.otel4s.oteljava.OtelJava
import org.typelevel.otel4s.oteljava.context.Context
import org.typelevel.otel4s.oteljava.context.LocalContext
import org.typelevel.otel4s.trace.Tracer

import scala.concurrent.Future
Expand Down Expand Up @@ -76,9 +74,8 @@ import scala.concurrent.duration._
object PekkoHttpExample extends IOApp.Simple {

def run: IO[Unit] =
IOLocal(Context.root).flatMap { implicit ioLocal: IOLocal[Context] =>
implicit val local: Local[IO, Context] = localForIOLocal
val otelJava: OtelJava[IO] = OtelJava.local(GlobalOpenTelemetry.get())
OtelJava.global[IO].flatMap { otelJava =>
implicit val local: LocalContext[IO] = otelJava.localContext

otelJava.tracerProvider.get("com.example").flatMap { implicit tracer: Tracer[IO] =>
createSystem.use { implicit actorSystem: ActorSystem =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import org.typelevel.otel4s.metrics.MeterProvider
import org.typelevel.otel4s.oteljava.context.Context
import org.typelevel.otel4s.oteljava.context.LocalContext
import org.typelevel.otel4s.oteljava.context.LocalContextProvider
import org.typelevel.otel4s.oteljava.context.propagation.PropagatorConverters._
import org.typelevel.otel4s.oteljava.metrics.Metrics
import org.typelevel.otel4s.oteljava.trace.Traces
import org.typelevel.otel4s.trace.TracerProvider
Expand All @@ -53,64 +52,6 @@ final class OtelJava[F[_]] private (

object OtelJava {

/** Creates an [[org.typelevel.otel4s.Otel4s]] from a Java OpenTelemetry instance.
*
* @param jOtel
* A Java OpenTelemetry instance. It is the caller's responsibility to shut this down. Failure to do so may result
* in lost metrics and traces.
*
* @return
* An effect of an [[org.typelevel.otel4s.Otel4s]] resource.
*/
def forAsync[F[_]: Async: LocalContextProvider](
jOtel: JOpenTelemetry
): F[OtelJava[F]] =
LocalProvider[F, Context].local.map { implicit l =>
local[F](jOtel)
}

def local[F[_]: Async: LocalContext](
jOtel: JOpenTelemetry
): OtelJava[F] = {
val contextPropagators = jOtel.getPropagators.asScala

val metrics = Metrics.forAsync(jOtel)
val traces = Traces.local(jOtel, contextPropagators)
new OtelJava[F](
jOtel,
contextPropagators,
metrics.meterProvider,
traces.tracerProvider,
)
}

/** Creates a no-op implementation of the [[OtelJava]].
*/
def noop[F[_]: Applicative: LocalContextProvider]: F[OtelJava[F]] =
for {
local <- LocalProvider[F, Context].local
} yield new OtelJava(
JOpenTelemetry.noop(),
ContextPropagators.noop,
MeterProvider.noop,
TracerProvider.noop
)(local)

/** Lifts the acquisition of a Java OpenTelemetrySdk instance to a Resource.
*
* @param acquire
* OpenTelemetrySdk resource
*
* @return
* An [[org.typelevel.otel4s.Otel4s]] resource.
*/
def resource[F[_]: Async: LocalContextProvider](
acquire: F[JOpenTelemetrySdk]
): Resource[F, OtelJava[F]] =
Resource
.make(acquire)(sdk => asyncFromCompletableResultCode(Sync[F].delay(sdk.shutdown())))
.evalMap(forAsync[F])

/** Creates a [[cats.effect.Resource `Resource`]] of the automatic configuration of a Java `OpenTelemetrySdk`
* instance.
*
Expand Down Expand Up @@ -147,7 +88,67 @@ object OtelJava {
* [[autoConfigured]]
*/
def global[F[_]: Async: LocalContextProvider]: F[OtelJava[F]] =
Sync[F].delay(GlobalOpenTelemetry.get).flatMap(forAsync[F])
Sync[F].delay(GlobalOpenTelemetry.get).flatMap(fromJOpenTelemetry[F])

/** Lifts the acquisition of a Java OpenTelemetrySdk instance to a Resource. The acquired SDK will be shutdown upon
* release.
*
* @param acquire
* OpenTelemetrySdk resource
*
* @return
* An [[org.typelevel.otel4s.Otel4s]] resource.
*/
def resource[F[_]: Async: LocalContextProvider](acquire: F[JOpenTelemetrySdk]): Resource[F, OtelJava[F]] =
Resource
.make(acquire)(sdk => asyncFromCompletableResultCode(Sync[F].delay(sdk.shutdown())))
.evalMap(fromJOpenTelemetry[F])

/** Creates an [[org.typelevel.otel4s.Otel4s]] from a Java OpenTelemetry instance.
*
* @param jOtel
* A Java OpenTelemetry instance. It is the caller's responsibility to shut this down. Failure to do so may result
* in lost metrics and traces.
*
* @return
* An effect of an [[org.typelevel.otel4s.Otel4s]] resource.
*/
def fromJOpenTelemetry[F[_]: Async: LocalContextProvider](jOtel: JOpenTelemetry): F[OtelJava[F]] =
LocalProvider[F, Context].local.map { implicit l =>
create[F](jOtel)
}

/** Creates a no-op implementation of the [[OtelJava]].
*/
def noop[F[_]: Applicative: LocalContextProvider]: F[OtelJava[F]] =
for {
local <- LocalProvider[F, Context].local
} yield new OtelJava(
JOpenTelemetry.noop(),
ContextPropagators.noop,
MeterProvider.noop,
TracerProvider.noop
)(local)

/** Creates an [[org.typelevel.otel4s.Otel4s]] from a Java OpenTelemetry instance using the given `Local` instance.
*
* @param jOtel
* A Java OpenTelemetry instance. It is the caller's responsibility to shut this down. Failure to do so may result
* in lost metrics and traces.
*
* @return
* An effect of an [[org.typelevel.otel4s.Otel4s]] resource.
*/
private def create[F[_]: Async: LocalContext](jOtel: JOpenTelemetry): OtelJava[F] = {
val metrics = Metrics.create(jOtel)
val traces = Traces.create(jOtel)
new OtelJava[F](
jOtel,
traces.propagators,
metrics.meterProvider,
traces.tracerProvider,
)
}

private[this] def asyncFromCompletableResultCode[F[_]](
codeF: F[CompletableResultCode],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class OtelJavaSuite extends CatsEffectSuite {
test("OtelJava toString returns useful info") {
val testSdk: JOpenTelemetrySdk = JOpenTelemetrySdk.builder().build()
OtelJava
.forAsync[IO](testSdk)
.fromJOpenTelemetry[IO](testSdk)
.map(testOtel4s => {
val res = testOtel4s.toString()
assert(clue(res).contains("OpenTelemetrySdk"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,58 @@

package org.typelevel.otel4s.oteljava.metrics

import cats.effect.kernel.Async
import cats.effect.Async
import cats.mtl.Ask
import cats.syntax.functor._
import io.opentelemetry.api.{OpenTelemetry => JOpenTelemetry}
import io.opentelemetry.api.GlobalOpenTelemetry
import org.typelevel.otel4s.metrics.MeterProvider
import org.typelevel.otel4s.oteljava.context.AskContext
import org.typelevel.otel4s.oteljava.context.Context

trait Metrics[F[_]] {
/** The configured metrics module.
*
* @tparam F
* the higher-kinded type of a polymorphic effect
*/
sealed trait Metrics[F[_]] {

/** The [[org.typelevel.otel4s.metrics.MeterProvider MeterProvider]].
*/
def meterProvider: MeterProvider[F]
}

object Metrics {

def forAsync[F[_]: Async: AskContext](jOtel: JOpenTelemetry): Metrics[F] =
new Metrics[F] {
val meterProvider: MeterProvider[F] =
new MeterProviderImpl[F](jOtel.getMeterProvider)
}
/** Creates a [[org.typelevel.otel4s.oteljava.metrics.Metrics]] from the global Java OpenTelemetry instance.
*
* @note
* the created module is isolated and exemplars won't be collected. Use `OtelJava` if you need to capture
* exemplars.
*/
def global[F[_]: Async]: F[Metrics[F]] =
Async[F].delay(GlobalOpenTelemetry.get).map(fromJOpenTelemetry[F])

/** Creates a [[org.typelevel.otel4s.oteljava.metrics.Metrics]] from a Java OpenTelemetry instance.
*
* @note
* the created module is isolated and exemplars won't be collected. Use `OtelJava` if you need to capture
* exemplars.
*
* @param jOtel
* A Java OpenTelemetry instance. It is the caller's responsibility to shut this down. Failure to do so may result
* in lost metrics and traces.
*/
def fromJOpenTelemetry[F[_]: Async](jOtel: JOpenTelemetry): Metrics[F] = {
implicit val askContext: AskContext[F] = Ask.const(Context.root)
create(jOtel)
}

private[oteljava] def create[F[_]: Async: AskContext](jOtel: JOpenTelemetry): Metrics[F] =
new Impl(new MeterProviderImpl(jOtel.getMeterProvider))

private final class Impl[F[_]](val meterProvider: MeterProvider[F]) extends Metrics[F] {
override def toString: String = s"Metrics{meterProvider=$meterProvider}"
}

}
Loading

0 comments on commit 4ecaeaa

Please sign in to comment.