Skip to content

Commit b71f939

Browse files
authored
Merge pull request #867 from iRevive/oteljava/traces-metrics
oteljava: refactor Metrics, Traces, and OtelJava API
2 parents 4f534a1 + 6a24c98 commit b71f939

File tree

8 files changed

+174
-113
lines changed

8 files changed

+174
-113
lines changed

docs/oteljava/tracing-java-interop.md

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,23 +50,18 @@ It can be constructed in the following way:
5050
```scala mdoc:silent
5151
import cats.effect._
5252
import cats.mtl.Local
53-
import cats.syntax.functor._
54-
import org.typelevel.otel4s.instances.local._ // brings Local derived from IOLocal
5553
import org.typelevel.otel4s.oteljava.context.Context
5654
import org.typelevel.otel4s.oteljava.OtelJava
57-
import io.opentelemetry.api.GlobalOpenTelemetry
58-
59-
def createOtel4s[F[_]: Async](implicit L: Local[F, Context]): F[OtelJava[F]] =
60-
Async[F].delay(GlobalOpenTelemetry.get).map(OtelJava.local[F])
6155

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

6761
val run: IO[Unit] =
68-
IOLocal(Context.root).flatMap { implicit ioLocal: IOLocal[Context] =>
69-
createOtel4s[IO].flatMap(otel4s => program(otel4s))
62+
OtelJava.global[IO].flatMap { otel4s =>
63+
import otel4s.localContext
64+
program(otel4s)
7065
}
7166
```
7267

docs/tracing-context-propagation.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,14 @@ You can find both examples below and choose which one suits your requirements.
3232
```scala mdoc:silent:reset
3333
import cats.effect._
3434
import cats.mtl.Local
35-
import cats.syntax.functor._
35+
import cats.syntax.flatMap._
3636
import org.typelevel.otel4s.instances.local._ // brings Local derived from IOLocal
3737
import org.typelevel.otel4s.oteljava.context.Context
3838
import org.typelevel.otel4s.oteljava.OtelJava
3939
import io.opentelemetry.api.GlobalOpenTelemetry
4040

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

4444
def program[F[_]: Async](otel4s: OtelJava[F]): F[Unit] = {
4545
val _ = otel4s
@@ -52,7 +52,7 @@ val run: IO[Unit] =
5252
}
5353
```
5454

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

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

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

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

9191
```scala mdoc:silent:reset
9292
import cats.effect._
93-
import cats.syntax.functor._
93+
import cats.syntax.flatMap._
9494
import cats.data.Kleisli
9595
import cats.mtl.Local
9696
import org.typelevel.otel4s.oteljava.context.Context
9797
import org.typelevel.otel4s.oteljava.OtelJava
9898
import io.opentelemetry.api.GlobalOpenTelemetry
9999

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

103103
def program[F[_]: Async](otel4s: OtelJava[F]): F[Unit] = {
104104
val _ = otel4s

examples/src/main/scala/KleisliExample.scala

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import cats.effect.Async
1919
import cats.effect.IO
2020
import cats.effect.IOApp
2121
import cats.effect.Resource
22-
import io.opentelemetry.api.GlobalOpenTelemetry
2322
import org.typelevel.otel4s.oteljava.OtelJava
2423
import org.typelevel.otel4s.oteljava.context.Context
2524
import org.typelevel.otel4s.oteljava.context.LocalContext
@@ -30,10 +29,7 @@ object KleisliExample extends IOApp.Simple {
3029
Tracer[F].span("work").surround(Async[F].delay(println("I'm working")))
3130

3231
private def tracerResource[F[_]: Async: LocalContext]: Resource[F, Tracer[F]] =
33-
Resource
34-
.eval(Async[F].delay(GlobalOpenTelemetry.get))
35-
.map(OtelJava.local[F])
36-
.evalMap(_.tracerProvider.get("kleisli-example"))
32+
Resource.eval(OtelJava.global[F]).evalMap(_.tracerProvider.get("kleisli-example"))
3733

3834
def run: IO[Unit] =
3935
tracerResource[Kleisli[IO, Context, *]]

examples/src/main/scala/PekkoHttpExample.scala

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import cats.effect.Async
1818
import cats.effect.IO
1919
import cats.effect.IOApp
20-
import cats.effect.IOLocal
2120
import cats.effect.Resource
2221
import cats.effect.Sync
2322
import cats.effect.std.Random
@@ -26,7 +25,6 @@ import cats.mtl.Local
2625
import cats.syntax.applicative._
2726
import cats.syntax.flatMap._
2827
import cats.syntax.functor._
29-
import io.opentelemetry.api.GlobalOpenTelemetry
3028
import io.opentelemetry.context.{Context => JContext}
3129
import io.opentelemetry.instrumentation.annotations.WithSpan
3230
import org.apache.pekko.actor.ActorSystem
@@ -37,7 +35,6 @@ import org.apache.pekko.http.scaladsl.server.Directives._
3735
import org.apache.pekko.http.scaladsl.server.Route
3836
import org.apache.pekko.util.ByteString
3937
import org.typelevel.otel4s.Attribute
40-
import org.typelevel.otel4s.instances.local._
4138
import org.typelevel.otel4s.oteljava.OtelJava
4239
import org.typelevel.otel4s.oteljava.context.Context
4340
import org.typelevel.otel4s.trace.Tracer
@@ -76,9 +73,8 @@ import scala.concurrent.duration._
7673
object PekkoHttpExample extends IOApp.Simple {
7774

7875
def run: IO[Unit] =
79-
IOLocal(Context.root).flatMap { implicit ioLocal: IOLocal[Context] =>
80-
implicit val local: Local[IO, Context] = localForIOLocal
81-
val otelJava: OtelJava[IO] = OtelJava.local(GlobalOpenTelemetry.get())
76+
OtelJava.global[IO].flatMap { otelJava =>
77+
import otelJava.localContext
8278

8379
otelJava.tracerProvider.get("com.example").flatMap { implicit tracer: Tracer[IO] =>
8480
createSystem.use { implicit actorSystem: ActorSystem =>

oteljava/all/src/main/scala/org/typelevel/otel4s/oteljava/OtelJava.scala

Lines changed: 61 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import org.typelevel.otel4s.metrics.MeterProvider
3434
import org.typelevel.otel4s.oteljava.context.Context
3535
import org.typelevel.otel4s.oteljava.context.LocalContext
3636
import org.typelevel.otel4s.oteljava.context.LocalContextProvider
37-
import org.typelevel.otel4s.oteljava.context.propagation.PropagatorConverters._
3837
import org.typelevel.otel4s.oteljava.metrics.Metrics
3938
import org.typelevel.otel4s.oteljava.trace.Traces
4039
import org.typelevel.otel4s.trace.TracerProvider
@@ -53,64 +52,6 @@ final class OtelJava[F[_]] private (
5352

5453
object OtelJava {
5554

56-
/** Creates an [[org.typelevel.otel4s.Otel4s]] from a Java OpenTelemetry instance.
57-
*
58-
* @param jOtel
59-
* A Java OpenTelemetry instance. It is the caller's responsibility to shut this down. Failure to do so may result
60-
* in lost metrics and traces.
61-
*
62-
* @return
63-
* An effect of an [[org.typelevel.otel4s.Otel4s]] resource.
64-
*/
65-
def forAsync[F[_]: Async: LocalContextProvider](
66-
jOtel: JOpenTelemetry
67-
): F[OtelJava[F]] =
68-
LocalProvider[F, Context].local.map { implicit l =>
69-
local[F](jOtel)
70-
}
71-
72-
def local[F[_]: Async: LocalContext](
73-
jOtel: JOpenTelemetry
74-
): OtelJava[F] = {
75-
val contextPropagators = jOtel.getPropagators.asScala
76-
77-
val metrics = Metrics.forAsync(jOtel)
78-
val traces = Traces.local(jOtel, contextPropagators)
79-
new OtelJava[F](
80-
jOtel,
81-
contextPropagators,
82-
metrics.meterProvider,
83-
traces.tracerProvider,
84-
)
85-
}
86-
87-
/** Creates a no-op implementation of the [[OtelJava]].
88-
*/
89-
def noop[F[_]: Applicative: LocalContextProvider]: F[OtelJava[F]] =
90-
for {
91-
local <- LocalProvider[F, Context].local
92-
} yield new OtelJava(
93-
JOpenTelemetry.noop(),
94-
ContextPropagators.noop,
95-
MeterProvider.noop,
96-
TracerProvider.noop
97-
)(local)
98-
99-
/** Lifts the acquisition of a Java OpenTelemetrySdk instance to a Resource.
100-
*
101-
* @param acquire
102-
* OpenTelemetrySdk resource
103-
*
104-
* @return
105-
* An [[org.typelevel.otel4s.Otel4s]] resource.
106-
*/
107-
def resource[F[_]: Async: LocalContextProvider](
108-
acquire: F[JOpenTelemetrySdk]
109-
): Resource[F, OtelJava[F]] =
110-
Resource
111-
.make(acquire)(sdk => asyncFromCompletableResultCode(Sync[F].delay(sdk.shutdown())))
112-
.evalMap(forAsync[F])
113-
11455
/** Creates a [[cats.effect.Resource `Resource`]] of the automatic configuration of a Java `OpenTelemetrySdk`
11556
* instance.
11657
*
@@ -147,7 +88,67 @@ object OtelJava {
14788
* [[autoConfigured]]
14889
*/
14990
def global[F[_]: Async: LocalContextProvider]: F[OtelJava[F]] =
150-
Sync[F].delay(GlobalOpenTelemetry.get).flatMap(forAsync[F])
91+
Sync[F].delay(GlobalOpenTelemetry.get).flatMap(fromJOpenTelemetry[F])
92+
93+
/** Lifts the acquisition of a Java OpenTelemetrySdk instance to a Resource. The acquired SDK will be shutdown upon
94+
* release.
95+
*
96+
* @param acquire
97+
* OpenTelemetrySdk resource
98+
*
99+
* @return
100+
* An [[org.typelevel.otel4s.Otel4s]] resource.
101+
*/
102+
def resource[F[_]: Async: LocalContextProvider](acquire: F[JOpenTelemetrySdk]): Resource[F, OtelJava[F]] =
103+
Resource
104+
.make(acquire)(sdk => asyncFromCompletableResultCode(Sync[F].delay(sdk.shutdown())))
105+
.evalMap(fromJOpenTelemetry[F])
106+
107+
/** Creates an [[org.typelevel.otel4s.Otel4s]] from a Java OpenTelemetry instance.
108+
*
109+
* @param jOtel
110+
* A Java OpenTelemetry instance. It is the caller's responsibility to shut this down. Failure to do so may result
111+
* in lost metrics and traces.
112+
*
113+
* @return
114+
* An effect of an [[org.typelevel.otel4s.Otel4s]] resource.
115+
*/
116+
def fromJOpenTelemetry[F[_]: Async: LocalContextProvider](jOtel: JOpenTelemetry): F[OtelJava[F]] =
117+
LocalProvider[F, Context].local.map { implicit l =>
118+
create[F](jOtel)
119+
}
120+
121+
/** Creates a no-op implementation of the [[OtelJava]].
122+
*/
123+
def noop[F[_]: Applicative: LocalContextProvider]: F[OtelJava[F]] =
124+
for {
125+
local <- LocalProvider[F, Context].local
126+
} yield new OtelJava(
127+
JOpenTelemetry.noop(),
128+
ContextPropagators.noop,
129+
MeterProvider.noop,
130+
TracerProvider.noop
131+
)(local)
132+
133+
/** Creates an [[org.typelevel.otel4s.Otel4s]] from a Java OpenTelemetry instance using the given `Local` instance.
134+
*
135+
* @param jOtel
136+
* A Java OpenTelemetry instance. It is the caller's responsibility to shut this down. Failure to do so may result
137+
* in lost metrics and traces.
138+
*
139+
* @return
140+
* An effect of an [[org.typelevel.otel4s.Otel4s]] resource.
141+
*/
142+
private def create[F[_]: Async: LocalContext](jOtel: JOpenTelemetry): OtelJava[F] = {
143+
val metrics = Metrics.create(jOtel)
144+
val traces = Traces.create(jOtel)
145+
new OtelJava[F](
146+
jOtel,
147+
traces.propagators,
148+
metrics.meterProvider,
149+
traces.tracerProvider,
150+
)
151+
}
151152

152153
private[this] def asyncFromCompletableResultCode[F[_]](
153154
codeF: F[CompletableResultCode],

oteljava/all/src/test/scala/org/typelevel/otel4s/oteljava/OtelJavaSuite.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class OtelJavaSuite extends CatsEffectSuite {
2525
test("OtelJava toString returns useful info") {
2626
val testSdk: JOpenTelemetrySdk = JOpenTelemetrySdk.builder().build()
2727
OtelJava
28-
.forAsync[IO](testSdk)
28+
.fromJOpenTelemetry[IO](testSdk)
2929
.map(testOtel4s => {
3030
val res = testOtel4s.toString()
3131
assert(clue(res).contains("OpenTelemetrySdk"))

oteljava/metrics/src/main/scala/org/typelevel/otel4s/oteljava/metrics/Metrics.scala

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,58 @@
1616

1717
package org.typelevel.otel4s.oteljava.metrics
1818

19-
import cats.effect.kernel.Async
19+
import cats.effect.Async
20+
import cats.mtl.Ask
21+
import cats.syntax.functor._
2022
import io.opentelemetry.api.{OpenTelemetry => JOpenTelemetry}
23+
import io.opentelemetry.api.GlobalOpenTelemetry
2124
import org.typelevel.otel4s.metrics.MeterProvider
2225
import org.typelevel.otel4s.oteljava.context.AskContext
26+
import org.typelevel.otel4s.oteljava.context.Context
2327

24-
trait Metrics[F[_]] {
28+
/** The configured metrics module.
29+
*
30+
* @tparam F
31+
* the higher-kinded type of a polymorphic effect
32+
*/
33+
sealed trait Metrics[F[_]] {
34+
35+
/** The [[org.typelevel.otel4s.metrics.MeterProvider MeterProvider]].
36+
*/
2537
def meterProvider: MeterProvider[F]
2638
}
2739

2840
object Metrics {
2941

30-
def forAsync[F[_]: Async: AskContext](jOtel: JOpenTelemetry): Metrics[F] =
31-
new Metrics[F] {
32-
val meterProvider: MeterProvider[F] =
33-
new MeterProviderImpl[F](jOtel.getMeterProvider)
34-
}
42+
/** Creates a [[org.typelevel.otel4s.oteljava.metrics.Metrics]] from the global Java OpenTelemetry instance.
43+
*
44+
* @note
45+
* the created module is isolated and exemplars won't be collected. Use `OtelJava` if you need to capture
46+
* exemplars.
47+
*/
48+
def global[F[_]: Async]: F[Metrics[F]] =
49+
Async[F].delay(GlobalOpenTelemetry.get).map(fromJOpenTelemetry[F])
50+
51+
/** Creates a [[org.typelevel.otel4s.oteljava.metrics.Metrics]] from a Java OpenTelemetry instance.
52+
*
53+
* @note
54+
* the created module is isolated and exemplars won't be collected. Use `OtelJava` if you need to capture
55+
* exemplars.
56+
*
57+
* @param jOtel
58+
* A Java OpenTelemetry instance. It is the caller's responsibility to shut this down. Failure to do so may result
59+
* in lost metrics and traces.
60+
*/
61+
def fromJOpenTelemetry[F[_]: Async](jOtel: JOpenTelemetry): Metrics[F] = {
62+
implicit val askContext: AskContext[F] = Ask.const(Context.root)
63+
create(jOtel)
64+
}
65+
66+
private[oteljava] def create[F[_]: Async: AskContext](jOtel: JOpenTelemetry): Metrics[F] =
67+
new Impl(new MeterProviderImpl(jOtel.getMeterProvider))
68+
69+
private final class Impl[F[_]](val meterProvider: MeterProvider[F]) extends Metrics[F] {
70+
override def toString: String = s"Metrics{meterProvider=$meterProvider}"
71+
}
3572

3673
}

0 commit comments

Comments
 (0)