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

Add LocalProvider #441

Merged
merged 2 commits into from
Feb 4, 2024
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 @@ -89,7 +89,7 @@ object TraceBenchmark {
.setTracerProvider(tracerProvider)
.build()

OtelJava.forAsync(otel).flatMap {
OtelJava.forAsync[IO](otel).flatMap {
_.tracerProvider.tracer("trace-benchmark").get
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
* 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.context

import cats.Applicative
import cats.effect.IOLocal
import cats.effect.LiftIO
import cats.effect.MonadCancelThrow
import cats.mtl.Local
import org.typelevel.otel4s.instances.local._

/** A utility class to simplify the creation of the [[cats.mtl.Local Local]].
*
* @tparam F
* the higher-kinded type of a polymorphic effect
*
* @tparam Ctx
* the type of the context
*/
@annotation.implicitNotFound("""
Cannot find the `LocalProvider` for effect `${F}` and context `${Ctx}`.
The `LocalProvider` can be derived implicitly:

1) from implicit `IOLocal[${Ctx}]` and `LiftIO[${F}]`:

IOLocal(${Ctx}.root).map { implicit ioLocal =>
val provider = LocalProvider[IO, ${Ctx}]
}

2) from implicit `Local[${F}, ${Ctx}]`:

implicit val local: Local[${F}, ${Ctx}] = ???
val provider = LocalProvider[${F}, ${Ctx}]

3) from implicit `LiftIO[${F}]`:

val provider = LocalProvider[IO, ${Ctx}]
""")
trait LocalProvider[F[_], Ctx] {

/** Creates a [[cats.mtl.Local Local]] instance. The method is invoked once
* per creation of the Otel4s instance.
*/
def local: F[Local[F, Ctx]]
}

object LocalProvider extends LocalProviderLowPriority {

def apply[F[_], C](implicit ev: LocalProvider[F, C]): LocalProvider[F, C] = ev

/** Creates [[LocalProvider]] that derives [[cats.mtl.Local Local]] instance
* from the given [[cats.effect.IOLocal IOLocal]].
*
* @param ioLocal
* the [[cats.effect.IOLocal IOLocal]] to use
*
* @tparam F
* the higher-kinded type of a polymorphic effect
*
* @tparam Ctx
* the type of the context
*/
def fromIOLocal[F[_]: MonadCancelThrow: LiftIO, Ctx](
ioLocal: IOLocal[Ctx]
): LocalProvider[F, Ctx] =
new LocalProvider[F, Ctx] {
val local: F[Local[F, Ctx]] =
MonadCancelThrow[F].pure(
localForIOLocal[F, Ctx](implicitly, implicitly, ioLocal)
NthPortal marked this conversation as resolved.
Show resolved Hide resolved
)

override def toString: String = "LocalProvider.fromIOLocal"
}

/** Creates [[LocalProvider]] that returns the given [[cats.mtl.Local Local]]
* instance.
*
* @param l
* the [[cats.mtl.Local Local]] to use
*
* @tparam F
* the higher-kinded type of a polymorphic effect
*
* @tparam Ctx
* the type of the context
*/
def fromLocal[F[_]: Applicative, Ctx](
l: Local[F, Ctx]
): LocalProvider[F, Ctx] =
new LocalProvider[F, Ctx] {
val local: F[Local[F, Ctx]] = Applicative[F].pure(l)
override def toString: String = "LocalProvider.fromLocal"
}

/** Creates [[LocalProvider]] that creates [[cats.effect.IOLocal IOLocal]]
* under the hood to derive the [[cats.mtl.Local Local]] instance.
*
* @note
* every invocation of the [[LocalProvider.local]] creates new
* [[cats.effect.IOLocal IOLocal]]. If you want to use a custom
* [[cats.effect.IOLocal IOLocal]] (e.g. to share it with other components)
* use [[LocalProvider.fromIOLocal]].
*
* @tparam F
* the higher-kinded type of a polymorphic effect
*
* @tparam Ctx
* the type of the context
*/
def fromLiftIO[
F[_]: MonadCancelThrow: LiftIO,
Ctx: Contextual
]: LocalProvider[F, Ctx] =
new LocalProvider[F, Ctx] {
def local: F[Local[F, Ctx]] =
IOLocal(Contextual[Ctx].root)
.map { implicit ioLocal: IOLocal[Ctx] =>
localForIOLocal[F, Ctx](implicitly, implicitly, ioLocal)
}
.to[F]

override def toString: String = "LocalProvider.fromLiftIO"
}

implicit def liftFromIOLocal[
F[_]: MonadCancelThrow: LiftIO,
Ctx
](implicit ioLocal: IOLocal[Ctx]): LocalProvider[F, Ctx] =
LocalProvider.fromIOLocal(ioLocal)

implicit def liftFromLocal[
F[_]: Applicative,
Ctx
](implicit local: Local[F, Ctx]): LocalProvider[F, Ctx] =
LocalProvider.fromLocal(local)
}

sealed trait LocalProviderLowPriority { self: LocalProvider.type =>

implicit def liftFromLiftIO[
F[_]: MonadCancelThrow: LiftIO,
Ctx: Contextual
]: LocalProvider[F, Ctx] =
LocalProvider.fromLiftIO

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* 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.context

import cats.data.Kleisli
import cats.effect.IO
import cats.effect.IOLocal
import cats.mtl.Local
import munit.CatsEffectSuite
import org.typelevel.otel4s.context.vault.VaultContext

class LocalProviderSuite extends CatsEffectSuite {

test("lift LocalProvider from implicit IOLocal") {
IOLocal(VaultContext.root).map { implicit ioLocal =>
assertEquals(
LocalProvider[IO, VaultContext].toString,
"LocalProvider.fromIOLocal"
)
}
}

test("lift LocalProvider from implicit Local (1)") {
IOLocal(VaultContext.root).map { ioLocal =>
import org.typelevel.otel4s.instances.local.localForIOLocal
implicit val local: Local[IO, VaultContext] =
localForIOLocal(implicitly, implicitly, ioLocal)

assertEquals(
LocalProvider[IO, VaultContext].toString,
"LocalProvider.fromLocal"
)
}
}

// liftFromLocal is prioritized over liftFromLiftIO
test("lift LocalProvider from implicit Local (2)") {
assertEquals(
LocalProvider[Kleisli[IO, VaultContext, *], VaultContext].toString,
"LocalProvider.fromLocal"
)
}

test("lift LocalProvider for IO") {
assertEquals(
LocalProvider[IO, VaultContext].toString,
"LocalProvider.fromLiftIO"
)
}

test("fail when multiple Local source are in the implicit scope") {
val err = compileErrors(
"""
implicit val ioLocal: IOLocal[VaultContext] = ???
implicit val local: Local[IO, VaultContext] = ???
val provider = LocalProvider[IO, VaultContext]
"""
)

assert(err.toLowerCase.contains("ambiguous"), err)
}

test("fail when no Local source are in the implicit scope") {
val err = compileErrors(
"""
val provider = LocalProvider[cats.effect.SyncIO, VaultContext]
"""
)

assert(err.contains("Cannot find the `LocalProvider` for"), err)
}
}
7 changes: 4 additions & 3 deletions docs/customization/histogram-custom-buckets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ import io.opentelemetry.sdk.metrics.Aggregation
import io.opentelemetry.sdk.metrics.InstrumentSelector
import io.opentelemetry.sdk.metrics.InstrumentType
import io.opentelemetry.sdk.metrics.View
import org.typelevel.otel4s.oteljava.context.LocalContextProvider
import org.typelevel.otel4s.oteljava.OtelJava
import org.typelevel.otel4s.metrics.Histogram

Expand All @@ -129,7 +130,7 @@ import scala.concurrent.duration._

object HistogramBucketsExample extends IOApp.Simple {

def work[F[_] : Temporal : Console](
def work[F[_]: Temporal: Console](
histogram: Histogram[F, Double],
random: Random[F]
): F[Unit] =
Expand All @@ -143,7 +144,7 @@ object HistogramBucketsExample extends IOApp.Simple {
)
} yield ()

def program[F[_] : Async : LiftIO : Parallel : Console]: F[Unit] =
def program[F[_]: Async: LocalContextProvider: Parallel: Console]: F[Unit] =
configureSdk[F]
.evalMap(_.meterProvider.get("histogram-example"))
.use { meter =>
Expand All @@ -157,7 +158,7 @@ object HistogramBucketsExample extends IOApp.Simple {
def run: IO[Unit] =
program[IO]

private def configureSdk[F[_] : Async : LiftIO]: Resource[F, OtelJava[F]] =
private def configureSdk[F[_]: Async: LocalContextProvider]: Resource[F, OtelJava[F]] =
OtelJava.autoConfigured { sdkBuilder =>
sdkBuilder
.addMeterProviderCustomizer { (meterProviderBuilder, _) =>
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/grafana/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ object ApiService {

object ExampleService extends IOApp.Simple {
def run: IO[Unit] =
OtelJava.autoConfigured()
OtelJava.autoConfigured[IO]()
.evalMap { otel4s =>
(
otel4s.tracerProvider.get("com.service.runtime"),
Expand Down
5 changes: 3 additions & 2 deletions docs/examples/honeycomb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ trait Work[F[_]] {
}

object Work {
def apply[F[_] : Async : Tracer : Console](histogram: Histogram[F, Double]): Work[F] =
def apply[F[_]: Async: Tracer: Console](histogram: Histogram[F, Double]): Work[F] =
new Work[F] {
def doWork: F[Unit] =
Tracer[F].span("Work.DoWork").use { span =>
Expand Down Expand Up @@ -135,7 +135,8 @@ object Work {

object TracingExample extends IOApp.Simple {
def run: IO[Unit] = {
OtelJava.autoConfigured()
OtelJava
.autoConfigured[IO]()
.evalMap { otel4s =>
otel4s.tracerProvider.get("com.service.runtime")
.flatMap { implicit tracer: Tracer[IO] =>
Expand Down
4 changes: 2 additions & 2 deletions docs/examples/jaeger-docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ trait Work[F[_]] {
}

object Work {
def apply[F[_] : Async : Tracer : Console]: Work[F] =
def apply[F[_]: Async: Tracer: Console]: Work[F] =
new Work[F] {
def doWork: F[Unit] =
Tracer[F].span("Work.DoWork").use { span =>
Expand Down Expand Up @@ -119,7 +119,7 @@ object Work {

object TracingExample extends IOApp.Simple {
def tracer: Resource[IO, Tracer[IO]] =
OtelJava.autoConfigured().evalMap(_.tracerProvider.get("Example"))
OtelJava.autoConfigured[IO]().evalMap(_.tracerProvider.get("Example"))

def run: IO[Unit] =
tracer
Expand Down
2 changes: 1 addition & 1 deletion docs/tracing-context-propagation.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ 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]): F[Unit] = {
def program[F[_]: Async](otel4s: OtelJava[F]): F[Unit] = {
val _ = otel4s
Async[F].unit
}
Expand Down
Loading