otel4s

An OpenTelemetry library for Scala based on Cats-Effect

APACHE-2.0 License

Stars
145
Committers
24

Bot releases are visible (Hide)

otel4s - v0.5.0 Latest Release

Published by iRevive 7 months ago

We are happy to announce the 0.5.0 release.
This release brings new features, significant API improvements, and breaking changes.

The SDK tracing modules are now publicly available. Check out the documentation.

Kudos to @NthPortal for numerous API improvements and for making this release happen.

[!WARNING]
This version has several breaking changes and is binary incompatible with the 0.4.0 lineage.

[!NOTE]
Public Scala Steward will rename the artifacts automatically (see the changes below).

[!TIP]
Use the scalafix rule to simplify the migration:

$ sbt "scalafix dependency:[email protected]:otel4s-scalafix:0.5.0"

Artifacts replacement and package changes

For better clarity, we decided to rename the following artifacts:

  1. otel4s-java to otel4s-oteljava
  2. otel4s-testkit-metrics to otel4s-oteljava-metrics-testkit
- libraryDependencies += "org.typelevel" %% "otel4s-java" % "0.4.0"
+ libraryDependencies += "org.typelevel" %% "otel4s-oteljava" % "0.5.0-RC1"

- libraryDependencies += "org.typelevel" %% "otel4s-testkit-metrics" % "0.4.0"
+ libraryDependencies += "org.typelevel" %% "otel4s-oteljava-metrics-testkit" % "0.5.0-RC1"

The package is renamed, too:

- import org.typelevel.otel4s.java._
+ import org.typelevel.otel4s.oteljava._

The localForIOLocal is moved to a new package

- import org.typelevel.otel4s.java.instances._
+ import org.typelevel.otel4s.instances.local._

Trace Status is renamed to the StatusCode

- import org.typelevel.otel4s.trace.Status
+ import org.typelevel.otel4s.trace.StatusCode

Semantic conventions updated to the 1.24.0

There are several major breaking changes:

  • The experimental (incubating) attributes are moved to a separate module semconv-experimental.
  • Attributes are organized by root namespaces.
    Previously, there were two super-objects ResourceAttributes and SemanticAttributes.
    Now, http.request.header attribute lives in the HttpAttributes object.
  • The package has changed from org.typelevel.otel4s.semconv.trace to org.typelevel.otel4s.semconv.
  • All old attributes that were removed from semantic-conventions (without being deprecated there) have been removed

The stable attributes (for exampleJvmAttributes.scala) are distributed in the otel4s-semconv package:

libraryDependencies += "org.typelevel" %% "otel4s-semconv" % "0.5.0"

However, If you want to use experimental attributes, for example GraphqlExperimentalAttributes.scala, the following dependency is mandatory:

libraryDependencies += "org.typelevel" %% "otel4s-semconv-experimental" % "0.5.0"

Metrics API changes

New generic instrument builders

The instrument builders now require an explicit instrument type:

- val counter = Meter[F].counter("counter").create
+ val counter = Meter[F].counter[Long]("counter").create

- val histogram = Meter[F].histogram("histogram").create
+ val histogram = Meter[F].histogram[Double]("histogram").create

While there is a drawback, this approach allows us to provide a flexible API:

val longCounter: F[Counter[F, Long]] = Meter[F].counter[Long]("counter").create
val doubleCounter: F[Counter[F, Double]] = Meter[F].counter[Double]("counter").create
val longHistogram: F[Histogram[F, Long]] = Meter[F].histogram[Long]("histogram").create
val doubleHistogram: F[Histogram[F, Double]] = Meter[F].histogram[Double]("histogram").create

val doubleGauge: Resource[F, ObservableGauge] =
  Meter[F].observableGauge[Double]("double-gauge").create(Sync[F].delay(List(Measurement(1.0))))

val longGauge: Resource[F, ObservableGauge] =
  Meter[F].observableGauge[Long]("long-gauge").create(Sync[F].delay(List(Measurement(1L))))

As a result, instruments are also available for (almost) any type now.
The underlying type still denotes to either Long or Double. But you can still use a wrapper type:

final case class OpaqueWrapper(value: Long)
implicit val measurementValue: MeasurementValue[OpaqueWrapper] = MeasurementValue[Long].contramap(_.value)

for {
  counter <- Meter[F].counter[OpaqueWrapper]("counter").create
  _       <- counter.add(OpaqueWrapper(42L))
} yield ()

Custom bucket boundaries can be configured via the histogram builder

Meter[F]
  .histogram("service.latency")
  .withExplicitBucketBoundaries(BucketBoundaries(Vector(0.005, 0.05, 0.5, 1.0, 1.5)))
  .create

Tracer API improvements

New Tracer#currentSpanOrThrow API

currentSpanOrThrow throws an exception if no span is available.

val span: F[Span[F]] = Tracer[F].currentSpanOrThrow

SpanOps.Res extractor

It provides access to the span and trace (natural transformation).

tracer.span("resource").resource.use { case SpanOps.Res(span, trace) =>
  ???
}

Array-like attributes use Seq under the hood

Previously, an array-like attribute could be made only with the List collection:

val attribute: Attribute[List[String]] = Attribute("key", List("a", "b"))

We relaxed the type constraint to the Seq:

val seq: Attribute[Seq[String]] = Attribute("key", Seq("a", "b"))
val vectorAsSeq: Attribute[Seq[String]] = Attribute("key", Vector("a", "b"): Seq[String])

Overloaded alternatives to pass attributes

There are overloaded alternatives that take varargs and a collection:

Tracer[F].span("span", Attribute("key", "value")) // varargs
Tracer[F].span("span", List(Attribute("key", "value"))) // collection

Attributes collection

Attributes is a typesafe collection of attributes with handy methods (get, added, concat):

val attributes = Attributes(Attribute("key", "value"), Attribute("frequency", 12.1))
val frequency: Option[Attribute[Double]] = attributes.get[Double]("frequency")
val withAttribute = attributes.added("some.attribute", 123L)

Attributes can be used with Tracer API too:

val attributes = Attributes(Attribute("http.latency", 0.123d), Attribute("user.id", 1L))
Tracer[IO].span("span", attributes)

OtelJava API improvements

New OtelJava.autoConfigured API

A handy option to get an autoconfigured instance which can be customized:

val otelJava: Resource[F, OtelJava[F]] = OtelJava.autoConfigured[F] { builder =>
  builder.addTracerProviderCustomizer((b, _) => b.setSampler(Sampler.alwaysOn()))
}

New OtelJava.noop API

A handy option to get a no-op instance:

val otelJava: F[OtelJava[F]] = OtelJava.noop[F]

New LocalProvider

LocalProvider simplifies the creation of the Local[F, Context]. It automatically detects an available instance or creates a new one. The priorities are the following:

  1. Uses Local[F, Context] available in the scope
  2. Creates Local from IOLocal[Context] and LiftIO[F] available in the scope
  3. Creates new Local[F, Context] by creating IOLocal[Context]

In most cases, you will be unaffected by this change.

OtelJava.localContext provides access to the Local[F, Context]

Now you can access the Local[F, Context] that OtelJava uses for context propagation.
For example, you can inject a baggage:

val otel4s: OtelJava[F] = ???
val program: F[Unit] = ??? 
val baggage = Baggage.builder().put("key", "value").build()

otel4s.localContext.local(program)(ctx => Context.wrap(ctx.underlying.`with`(baggage)))

Simplified conversion between OpenTelemetry Java and otel4s Attributes

import io.opentelemetry.api.common.{Attributes => JAttributes}
import org.typelevel.otel4s.Attributes
import org.typelevel.otel4s.oteljava.AttributeConverters._

val asOtel4s: Attributes =
  JAttributes.builder().put("key", "value").build().toScala
  
val asOpenTelemetry: JAttributes =
  Attributes(Attribute("key", "value")).toJava

OtelJava.underlying provides access to the JOpenTelemetry

Now you can access the JOpenTelemetry that OtelJava uses under the hood:

OtelJava.autoConfigured[IO]() { otel4s =>
  val openTelemetry: io.opentelemetry.api.OpenTelemetry = otel4s.underlying
  ???
}

What's Changed

Improvements and enhancements

sdk module

Internal

Upgrades

New Contributors

Full Changelog: https://github.com/typelevel/otel4s/compare/v0.4.0...v0.5.0

otel4s - v0.5.0-RC3

Published by iRevive 7 months ago

otel4s-sdk module had a missing dependency in 0.5.0-RC2. 0.5.0-RC3 addresses this issue.

What's Changed

Full Changelog: https://github.com/typelevel/otel4s/compare/v0.5.0-RC2...v0.5.0-RC3

otel4s - v0.5.0-RC2

Published by iRevive 7 months ago

We are happy to announce the second candidate for the upcoming 0.5.0 release.
The SDK tracing modules are now publicly available. Check out the documentation.

API improvements

There are several improvements. Kudos to @NthPortal for the dedicated work!

1) Attributes collection

Attributes is a typesafe collection of attributes with handy methods (get, added, concat):

val attributes = Attributes(Attribute("key", "value"), Attribute("frequency", 12.1))
val frequency: Option[Attribute[Double]] = attributes.get[Double]("frequency")
val withAttribute = attributes.added("some.attribute", 123L)

Attributes can be used with Tracer API too:

val attributes = Attributes(Attribute("http.latency", 0.123d), Attribute("user.id", 1L))
Tracer[IO].span("span", attributes)

2) Simplified conversion between OpenTelemetry Java and otel4s Attributes

import io.opentelemetry.api.common.{Attributes => JAttributes}
import org.typelevel.otel4s.Attributes
import org.typelevel.otel4s.oteljava.AttributeConverters._

val asOtel4s: Attributes =
  JAttributes.builder().put("key", "value").build().toScala
  
val asOpenTelemetry: JAttributes =
  Attributes(Attribute("key", "value")).toJava

3) OtelJava.underlying provides access to the JOpenTelemetry

Now you can access the JOpenTelemetry that OtelJava uses under the hood:

OtelJava.autoConfigured[IO]() { otel4s =>
  val openTelemetry: io.opentelemetry.api.OpenTelemetry = otel4s.underlying
  ???
}

What's Changed

Improvements and enhancements

Internal

Upgrades

Full Changelog: https://github.com/typelevel/otel4s/compare/v0.5.0-RC1...v0.5.0-RC2

otel4s - v0.5.0-RC1

Published by iRevive 8 months ago

We are happy to announce the first candidate for the upcoming 0.5.0 release.
This release brings significant API improvements and breaking changes.

[!WARNING]
This version has several breaking changes and is binary incompatible with the 0.4.0 lineage.

[!NOTE]
Public Scala Steward will rename the artifacts automatically (see the changes below).

[!TIP]
Use the scalafix rule to simplify the migration:

$ sbt "scalafix dependency:[email protected]:otel4s-scalafix:0.5.0-RC1"

Artifacts replacement and package changes

For better clarity, we decided to rename the following artifacts:

  1. otel4s-java to otel4s-oteljava
  2. otel4s-testkit-metrics to otel4s-oteljava-metrics-testkit
- libraryDependencies += "org.typelevel" %% "otel4s-java" % "0.4.0"
+ libraryDependencies += "org.typelevel" %% "otel4s-oteljava" % "0.5.0-RC1"

- libraryDependencies += "org.typelevel" %% "otel4s-testkit-metrics" % "0.4.0"
+ libraryDependencies += "org.typelevel" %% "otel4s-oteljava-metrics-testkit" % "0.5.0-RC1"

The package is renamed, too:

- import org.typelevel.otel4s.java._
+ import org.typelevel.otel4s.oteljava._

The localForIOLocal is moved to a new package

- import org.typelevel.otel4s.java.instances._
+ import org.typelevel.otel4s.instances.local._

Trace Status is renamed to the StatusCode

- import org.typelevel.otel4s.trace.Status
+ import org.typelevel.otel4s.trace.StatusCode

Notable API changes

Metrics. New generic instrument builders

The instrument builders now require an explicit instrument type:

- val counter = Meter[F].counter("counter").create
+ val counter = Meter[F].counter[Long]("counter").create

- val histogram = Meter[F].histogram("histogram").create
+ val histogram = Meter[F].histogram[Double]("histogram").create

While there is a drawback, this approach allows us to provide a flexible API:

val longCounter: F[Counter[F, Long]] = Meter[F].counter[Long]("counter").create
val doubleCounter: F[Counter[F, Double]] = Meter[F].counter[Double]("counter").create
val longHistogram: F[Histogram[F, Long]] = Meter[F].histogram[Long]("histogram").create
val doubleHistogram: F[Histogram[F, Double]] = Meter[F].histogram[Double]("histogram").create

val doubleGauge: Resource[F, ObservableGauge] =
  Meter[F].observableGauge[Double]("double-gauge").create(Sync[F].delay(List(Measurement(1.0))))

val longGauge: Resource[F, ObservableGauge] =
  Meter[F].observableGauge[Long]("long-gauge").create(Sync[F].delay(List(Measurement(1L))))

As a result, instruments are also available for (almost) any type now.
The underlying type still denotes to either Long or Double. But you can still use a wrapper type:

final case class OpaqueWrapper(value: Long)
implicit val measurementValue: MeasurementValue[OpaqueWrapper] = MeasurementValue[Long].contramap(_.value)

for {
  counter <- Meter[F].counter[OpaqueWrapper]("counter").create
  _       <- counter.add(OpaqueWrapper(42L))
} yield ()

Metrics. Custom bucket boundaries can be configured via the histogram builder

Meter[F]
  .histogram("service.latency")
  .withExplicitBucketBoundaries(BucketBoundaries(Vector(0.005, 0.05, 0.5, 1.0, 1.5)))
  .create

New OtelJava.autoConfigured API

A handy option to get an autoconfigured which can be customized:

val otelJava: Resource[F, OtelJava[F]] = OtelJava.autoConfigured[F] { builder =>
  builder.addTracerProviderCustomizer((b, _) => b.setSampler(Sampler.alwaysOn()))
}

New LocalProvider

LocalProvider simplifies the creation of the Local[F, Context]. It automatically detects an available instance or creates a new one. The priorities are the following:

  1. Uses Local[F, Context] available in the scope
  2. Creates Local from IOLocal[Context] and LiftIO[F] available in the scope
  3. Creates new Local[F, Context] by creating IOLocal[Context]

In most cases, you will be unaffected by this change.

OtelJava.localContext provides access to the Local[F, Context]

Now you can access the Local[F, Context] that OtelJava uses for context propagation.
For example, you can inject a baggage:

val otel4s: OtelJava[F] = ???
val program: F[Unit] = ??? 
val baggage = Baggage.builder().put("key", "value").build()

otel4s.localContext.local(program)(ctx => Context.wrap(ctx.underlying.`with`(baggage)))

Improved Tracer API

There are overloaded alternatives that take varargs and a collection:

Tracer[F].span("span", Attribute("key", "value")) // varargs
Tracer[F].span("span", List(Attribute("key", "value"))) // collection

What's Changed

Improvements and enhancements

sdk module - unpublished yet

Internal

Upgrades

New Contributors

Full Changelog: https://github.com/typelevel/otel4s/compare/v0.4.0...v0.5.0-RC1

otel4s - v0.4.0

Published by iRevive 10 months ago

We are happy to announce the 0.4.0 release.

[!WARNING]
This version has several breaking changes and is binary incompatible with the 0.3.0 lineage.

Notable API changes

1) Upgraded semantic conventions to 1.23.1

1.23.0 release of the semantic conventions marks HTTP conventions stable.

2) Attributes is a collection now

The Attributes has been redefined as a collection. Now we can take advantages of the built-in methods:

// create using varargs
val attributs: Attributes =
  Attributes(Attribute("key", "value"), Attribute("user_id", 42L))

// or from a collection
val attributs: Attributes =
  Attributes.fromSpecific(Seq(Attribute("key", "value"), Attribute("user_id", 42L)))

// or using a builder
val attributes: Attributes =
  Attributes.newBuilder.addOne("key", "value").addOne("user_id", 42L).result()

// iterable ops: 
attributes.size
attributes.foreach(attribute => println(attribute))
attributes.contains(AttributeKey.string("key"))

// transform to Vector
val asVector: Vector[Attribute[_]] = attributes.to(Vector)

Improvements and clean-ups

Upcoming trace SDK (not published yet)

Behind the scene

Upgrades

otel4s - v0.3.0

Published by iRevive 12 months ago

We are happy to announce the 0.3.0 release. We are stabilizing the API, enhancing the documentation with practical, hands-on examples, and addressing the bugs to provide a more reliable experience.

[!WARNING]
This version has several breaking changes and is binary incompatible with the 0.2.0 lineage.

Notable API changes

1) New Tracer[F].propagate

[!NOTE]
The propagation mechanism relies on the configured propagators.
The propagators can be enabled via environment variable, for example: OTEL_PROPAGATORS="tracecontext,baggage".

This enhancement allows users to inject the tracing information into the carrier conveniently:

val traceHeaders: IO[Map[String, String]] = 
  Tracer[IO].propagate(Map.empty[String, String])

The carrier can be of any type that has an instance of TextMapUpdater. For example, the tracing information can be injected into the http4s Response:

implicit def responseTextMapUpdater[F[_]]: TextMapUpdater[Response[F]] =
  (carrier: Response[F], key: String, value: String) => 
    carrier.withHeaders(carrier.headers.put(Header.Raw(CIString(key), value)))

val responseWithTraceInfo: IO[Response[IO]] =
  Tracer[IO].propagate(Response())

2) New Tracer[F].mapK

This enhancement unblocks the implementation of various middleware, especially in the http4s.

val tracer: Tracer[IO] = ???
val eitherTTracer: Tracer[EitherT[IO, Error, *]] = tracer.mapK[EitherT[IO, Error, *]]

3) New Tracer[F].currentSpanOrNoop

[!NOTE]
The no-op span will be returned if there is no active span in the local scope.

val tracer: Tracer[IO] = ???
val span: IO[Span[IO]] = tracer.currentSpanOrNoop

4) New OtelJava.global[F]

If you want to construct Otel4s instance from the global OpenTelemetry, you can use:

val otel4s: IO[OtelJava[IO]] = OtelJava.global[IO]

It's a shortcut for:

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

5) Removed SpanBuilder#wrapResource

Unfortunately, this API makes implementing a natural transformation for Tracer impossible.

6) Removed dependency on Vault

This change makes context implementation options more flexible for various backends.
If you want to supply your own IOLocal instance, replace Vault.empty with Context.root:

import org.typelevel.otel4s.java.context.Context
import org.typelevel.otel4s.java.instances._

IOLocal(Context.root).flatMap { implicit ioLocal =>
  for {
    otel4s <- IO.delay(GlobalOpenTelemetry.get).map(OtelJava.forAsync)
    // ...
  } yield ()
}

New site articles

  1. Integration with Grafana, Prometheus, and Jaeger - how to export metrics and traces, and visualize them in Grafana
  2. Customization of histogram buckets - how to set custom bucket boundaries for a specific histogram instrument
  3. Modules structure - the structure of the otel4s project
  4. Tracing context propagation - how the context propagation within the effect works
  5. Tracing instrumentation - how to instrument a library with tracing information
  6. Tracing - interop with Java-instrumented libraries - how to interoperate with OpenTelemetry Java libraries

The changes

Improvements and clean-ups

Bugfix

Documentation

Behind the scene

Upgrades

New Contributors

otel4s - v0.3.0-RC2

Published by iRevive about 1 year ago

What's Changed

The changes

Upgrades

Full Changelog: https://github.com/typelevel/otel4s/compare/v0.3.0-RC1...v0.3.0-RC2

otel4s - v0.3.0-RC1

Published by iRevive about 1 year ago

We are happy to announce the first candidate for the upcoming 0.3.0 release. We are stabilizing the API, enhancing the documentation with practical, hands-on examples, and addressing the bugs to provide a more reliable experience.

[!WARNING]
This version has several breaking changes and is binary incompatible with the 0.2.0 lineage.

Notable API changes

1) New Tracer[F].propagate

[!NOTE]
The propagation mechanism relies on the configured propagators.
The propagators can be enabled via environment variable, for example: OTEL_PROPAGATORS="tracecontext,baggage".

This enhancement allows users to inject the tracing information into the carrier conveniently:

val traceHeaders: IO[Map[String, String]] = 
  Tracer[IO].propagate(Map.empty[String, String])

The carrier can be of any type that has an instance of TextMapUpdater. For example, the tracing information can be injected into the http4s Response:

implicit def responseTextMapUpdater[F[_]]: TextMapUpdater[Response[F]] =
  (carrier: Response[F], key: String, value: String) => 
    carrier.withHeaders(carrier.headers.put(Header.Raw(CIString(key), value)))

val responseWithTraceInfo: IO[Response[IO]] =
  Tracer[IO].propagate(Response())

2) New Tracer[F].mapK

This enhancement unblocks the implementation of various middleware, especially in the http4s.

val tracer: Tracer[IO] = ???
val eitherTTracer: Tracer[EitherT[IO, Error, *]] = tracer.mapK[EitherT[IO, Error, *]]

3) New OtelJava.global[F]

If you want to construct Otel4s instance from the global OpenTelemetry, you can use:

val otel4s: IO[OtelJava[IO]] = OtelJava.global[IO]

It's a shortcut for:

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

4) Removed SpanBuilder#wrapResource

Unfortunately, this API makes implementing a natural transformation for Tracer impossible.

5) Removed dependency on Vault

This change makes context implementation options more flexible for various backends.
If you want to supply your own IOLocal instance, replace Vault.empty with Context.root:

import org.typelevel.otel4s.java.context.Context
import org.typelevel.otel4s.java.instances._

IOLocal(Context.root).flatMap { implicit ioLocal =>
  for {
    otel4s <- IO.delay(GlobalOpenTelemetry.get).map(OtelJava.forAsync)
    // ...
  } yield ()
}

New site articles

  1. Integration with Grafana, Prometheus, and Jaeger - how to export metrics and traces, and visualize them in Grafana
  2. Customization of histogram buckets - how to set custom bucket boundaries for a specific histogram instrument

The changes

Improvements and clean-ups

Bugfix

Documentation

Behind the scene

Upgrades

New Contributors

Full Changelog: https://github.com/typelevel/otel4s/compare/v0.2.1...v0.3.0-RC1

otel4s - v0.2.1

Published by rossabaker over 1 year ago

otel4s-v0.2.1 is a breaking change from v0.1.0. It contains mutliple bugfixes and new features, highlighted by propagation.

Where's v0.2.0?

The tag hit a non-deterministic test on publish. v0.2.1 is the first release in this series.

Noteworthy

Upgrades

Documentation

Behind the scenes

New Contributors

Discussion

We are continuing the original discussion from v0.2.0.

Full Changelog: https://github.com/typelevel/otel4s/compare/v0.1.0...v0.2.1

otel4s - v0.2.0

Published by rossabaker over 1 year ago

Cursed tag, didn't release. Please proceed to v0.2.1.

otel4s - v0.1.0

Published by rossabaker over 1 year ago

This is the first public release of otel4s, an OpenTelemetry implementation for Cats Effect. Rather than another ad hoc metrics or tracing abstraction, we are leaning fully into the OpenTelemetry specification. By embracing this ecosystem:

  • We can provide a high-fidelity implementation.
  • The core vocabulary is shared across the multiple languages in your distributed system.
  • Your telemetry can be exported to the growing network of vendors who support the protocol.

Meanwhile, the API is purely functional, built on core Cats Effect abstractions, making it a natural fit into the Typelevel Scala ecosystem.

The API is highly experimental, but already capable of exporting traces and metrics. We welcome all library and application developers who want to try it on for size, but discourage making it a dependency of a binary-stable library at this point in time. We welcome your feedback in the form of issues, discussions, and pull requests.