Skip to content

Commit

Permalink
Merge pull request #240 from kovstas/sdk-resource-class
Browse files Browse the repository at this point in the history
Add Resource entity
  • Loading branch information
armanbilge committed Jul 10, 2023
2 parents fddda3d + de74548 commit e64156a
Show file tree
Hide file tree
Showing 13 changed files with 684 additions and 47 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,11 @@ jobs:

- name: Make target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
run: mkdir -p testkit/metrics/jvm/target java/metrics/target testkit/common/jvm/target benchmarks/target examples/target core/trace/.js/target target semconv/.jvm/target core/common/.jvm/target java/trace/target unidocs/target .js/target core/metrics/.native/target core/all/.native/target site/target core/metrics/.jvm/target core/all/.js/target java/all/target java/common/target core/metrics/.js/target core/all/.jvm/target .jvm/target core/trace/.native/target .native/target semconv/.js/target core/trace/.jvm/target core/common/.native/target core/common/.js/target semconv/.native/target testkit/all/jvm/target project/target
run: mkdir -p testkit/metrics/jvm/target java/metrics/target testkit/common/jvm/target sdk/common/.native/target benchmarks/target examples/target sdk/common/.js/target core/trace/.js/target target semconv/.jvm/target core/common/.jvm/target java/trace/target unidocs/target .js/target core/metrics/.native/target core/all/.native/target site/target core/metrics/.jvm/target core/all/.js/target java/all/target java/common/target core/metrics/.js/target core/all/.jvm/target sdk/common/.jvm/target .jvm/target core/trace/.native/target .native/target semconv/.js/target core/trace/.jvm/target core/common/.native/target core/common/.js/target semconv/.native/target testkit/all/jvm/target project/target

- name: Compress target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
run: tar cf targets.tar testkit/metrics/jvm/target java/metrics/target testkit/common/jvm/target benchmarks/target examples/target core/trace/.js/target target semconv/.jvm/target core/common/.jvm/target java/trace/target unidocs/target .js/target core/metrics/.native/target core/all/.native/target site/target core/metrics/.jvm/target core/all/.js/target java/all/target java/common/target core/metrics/.js/target core/all/.jvm/target .jvm/target core/trace/.native/target .native/target semconv/.js/target core/trace/.jvm/target core/common/.native/target core/common/.js/target semconv/.native/target testkit/all/jvm/target project/target
run: tar cf targets.tar testkit/metrics/jvm/target java/metrics/target testkit/common/jvm/target sdk/common/.native/target benchmarks/target examples/target sdk/common/.js/target core/trace/.js/target target semconv/.jvm/target core/common/.jvm/target java/trace/target unidocs/target .js/target core/metrics/.native/target core/all/.native/target site/target core/metrics/.jvm/target core/all/.js/target java/all/target java/common/target core/metrics/.js/target core/all/.jvm/target sdk/common/.jvm/target .jvm/target core/trace/.native/target .native/target semconv/.js/target core/trace/.jvm/target core/common/.native/target core/common/.js/target semconv/.native/target testkit/all/jvm/target project/target

- name: Upload target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
Expand Down
33 changes: 32 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ ThisBuild / tlSonatypeUseLegacyHost := false
ThisBuild / tlSitePublishBranch := Some("main")

ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.6.0"
ThisBuild / semanticdbOptions ++= Seq("-P:semanticdb:synthetics:on").filter(_ =>
!tlIsScala3.value
)

ThisBuild / tlMimaPreviousVersions ~= (_.filterNot(_ == "0.2.0"))

Expand Down Expand Up @@ -58,6 +61,7 @@ lazy val root = tlCrossRootProject
`core-metrics`,
`core-trace`,
core,
`sdk-common`,
`testkit-common`,
`testkit-metrics`,
testkit,
Expand Down Expand Up @@ -125,6 +129,31 @@ lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform)
name := "otel4s-core"
)

lazy val `sdk-common` = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.crossType(CrossType.Pure)
.enablePlugins(BuildInfoPlugin)
.enablePlugins(NoPublishPlugin)
.in(file("sdk/common"))
.dependsOn(`core-common`, semconv)
.settings(
name := "otel4s-sdk-common",
startYear := Some(2023),
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-effect-kernel" % CatsEffectVersion,
"org.typelevel" %%% "cats-mtl" % CatsMtlVersion,
"org.typelevel" %%% "cats-laws" % CatsVersion % Test,
"org.typelevel" %%% "discipline-munit" % DisciplineMUnitVersion % Test,
"org.scalameta" %%% "munit" % MUnitVersion % Test,
"org.scalameta" %%% "munit-scalacheck" % MUnitVersion % Test,
),
buildInfoPackage := "org.typelevel.otel4s.sdk",
buildInfoOptions += sbtbuildinfo.BuildInfoOption.PackagePrivate,
buildInfoKeys := Seq[BuildInfoKey](
version
)
)
.settings(munitDependencies)

lazy val `testkit-common` = crossProject(JVMPlatform)
.crossType(CrossType.Full)
.in(file("testkit/common"))
Expand Down Expand Up @@ -239,7 +268,8 @@ lazy val semconv = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.in(file("semconv"))
.dependsOn(`core-common`)
.settings(
name := "otel4s-semconv"
name := "otel4s-semconv",
startYear := Some(2023),
)

lazy val benchmarks = project
Expand Down Expand Up @@ -308,6 +338,7 @@ lazy val unidocs = project
`core-metrics`.jvm,
`core-trace`.jvm,
core.jvm,
`sdk-common`.jvm,
`testkit-common`.jvm,
`testkit-metrics`.jvm,
testkit.jvm,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022 Typelevel
* Copyright 2023 Typelevel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -60,10 +60,7 @@ package {{pkg | trim}}
import org.typelevel.otel4s.AttributeKey
import org.typelevel.otel4s.AttributeKey._

import scala.annotation.nowarn

// DO NOT EDIT, this is an Auto-generated file from buildscripts/semantic-convention{{template}}
@nowarn("msg=never used")
object {{class}} {
/**
* The URL of the OpenTelemetry schema for these keys and values.
Expand Down Expand Up @@ -94,7 +91,7 @@ object {{class}} {
{%- if attribute.is_enum %}
{%- set class_name = attribute.fqn | to_camelcase(True) ~ "Value" %}
{%- set type = to_scala_return_type(attribute.attr_type.enum_type) %}
abstract class {{ class_name }}(value: {{ type }})
abstract class {{ class_name }}(val value: {{ type }})
object {{class_name}} {
{%- for member in attribute.attr_type.members %}
/** {% filter escape %}{{member.brief | to_doc_brief}}.{% endfilter %} */
Expand Down
12 changes: 10 additions & 2 deletions core/common/src/main/scala/org/typelevel/otel4s/Attribute.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@

package org.typelevel.otel4s

import cats.Show
import cats.implicits.showInterpolator
import cats.kernel.Hash

/** Represents the key-value attribute.
*
* @param key
* the key of the attribute. Denotes the types of the `value`
*
* @param value
* the value of the attribute
*
* @tparam A
* the type of the attribute's value. One of [[AttributeType]]
*/
Expand Down Expand Up @@ -68,4 +70,10 @@ String, Boolean, Long, Double, List[String], List[Boolean], List[Long], List[Dou
def apply[A: KeySelect](name: String, value: A): Attribute[A] =
Attribute(KeySelect[A].make(name), value)

implicit val showAttribute: Show[Attribute[_]] = (a: Attribute[_]) =>
s"${show"${a.key}"}=${a.value}"

implicit def hashAttribute[T: Hash]: Hash[Attribute[T]] =
Hash.by(a => (a.key, a.value))

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright 2023 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.sdk

import cats.Applicative
import cats.Monad
import cats.Monoid
import cats.Show
import cats.implicits._
import org.typelevel.otel4s.Attribute
import org.typelevel.otel4s.Attribute.KeySelect
import org.typelevel.otel4s.AttributeKey

/** An immutable collection of [[Attribute]]s.
*/
final class Attributes private (
private val m: Map[AttributeKey[_], Attribute[_]]
) {
def get[T: KeySelect](name: String): Option[Attribute[T]] = {
val key = KeySelect[T].make(name)
m.get(key).map(_.asInstanceOf[Attribute[T]])
}
def get[T](key: AttributeKey[T]): Option[Attribute[T]] =
m.get(key).map(_.asInstanceOf[Attribute[T]])

def isEmpty: Boolean = m.isEmpty
def size: Int = m.size
def contains(key: AttributeKey[_]): Boolean = m.contains(key)
def foldLeft[F[_]: Monad, B](z: B)(f: (B, Attribute[_]) => F[B]): F[B] =
m.foldLeft(Monad[F].pure(z)) { (acc, v) =>
acc.flatMap { b =>
f(b, v._2)
}
}
def forall[F[_]: Monad](p: Attribute[_] => F[Boolean]): F[Boolean] =
foldLeft(true)((b, a) => {
if (b) p(a).map(b && _)
else Monad[F].pure(false)
})
def foreach[F[_]: Applicative](f: Attribute[_] => F[Unit]): F[Unit] =
m.foldLeft(Applicative[F].unit) { (acc, v) =>
acc *> f(v._2)
}

def toMap: Map[AttributeKey[_], Attribute[_]] = m
def toList: List[Attribute[_]] = m.values.toList

}

object Attributes {

val Empty = new Attributes(Map.empty)

def apply(attributes: Attribute[_]*): Attributes = {
val builder = Map.newBuilder[AttributeKey[_], Attribute[_]]
attributes.foreach { a =>
builder += (a.key -> a)
}
new Attributes(builder.result())
}

implicit val showAttributes: Show[Attributes] = Show.show { attributes =>
attributes.toList
.map(a => show"$a")
.mkString("Attributes(", ", ", ")")
}

implicit val monoidAttributes: Monoid[Attributes] =
new Monoid[Attributes] {
def empty: Attributes = Attributes.Empty
def combine(x: Attributes, y: Attributes): Attributes = {
if (y.isEmpty) x
else if (x.isEmpty) y
else new Attributes(x.m ++ y.m)
}
}
}
125 changes: 125 additions & 0 deletions sdk/common/src/main/scala/org/typelevel/otel4s/sdk/Resource.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright 2023 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.sdk

import cats.Show
import cats.implicits.catsSyntaxEitherId
import cats.implicits.catsSyntaxOptionId
import cats.implicits.catsSyntaxSemigroup
import cats.implicits.showInterpolator
import org.typelevel.otel4s.Attribute
import org.typelevel.otel4s.sdk.BuildInfo
import org.typelevel.otel4s.sdk.Resource.ResourceInitiationError
import org.typelevel.otel4s.semconv.resource.attributes.ResourceAttributes._

/** [[Resource]] serves as a representation of a resource that captures
* essential identifying information regarding the entities associated with
* reported signals, such as statistics or traces.
* @param attributes
* \- a collection of [[Attribute]]s
* @param schemaUrl
* \- an optional schema URL
*/
final case class Resource(
attributes: Attributes,
schemaUrl: Option[String]
) {

/** Merges [[Resource]] into another [[Resource]]. If the same attribute
* exists in both resources, the attribute in the other [[Resource]] will be
* used.
* @param other
* \- the other [[Resource]] to merge into.
* @return
* a new [[Resource]] with the merged attributes.
*/
def mergeInto(other: Resource): Either[ResourceInitiationError, Resource] = {
if (other == Resource.Empty) this.asRight
else {
val schemaUrlOptEither = (other.schemaUrl, schemaUrl) match {
case (Some(otherUrl), Some(url)) =>
if (otherUrl == url)
url.some.asRight
else
ResourceInitiationError.SchemaUrlConflict.asLeft
case (otherUrl, url) =>
otherUrl.orElse(url).asRight
}

schemaUrlOptEither.map(
Resource(
other.attributes |+| attributes,
_
)
)
}
}

/** Unsafe version of [[Resource.mergeInto]] which throws an exception if the
* merge fails.
*/
private def mergeIntoUnsafe(other: Resource): Resource =
mergeInto(other).fold(
throw _,
identity
)
}

object Resource {
sealed abstract class ResourceInitiationError extends Throwable
object ResourceInitiationError {
case object SchemaUrlConflict extends ResourceInitiationError
}

def apply(attributes: Attributes): Resource =
Resource(attributes, None)

/** Returns an empty [[Resource]]. It is strongly recommended to start with
* [[Resource.Default]] instead of this method to include SDK required
* attributes.
*
* @return
* an empty [[Resource]].
*/
val Empty: Resource = Resource(Attributes.Empty)

private val TelemetrySdk: Resource = Resource(
Attributes(
Attribute(TelemetrySdkName, "otel4s"),
Attribute(TelemetrySdkLanguage, TelemetrySdkLanguageValue.Scala.value),
Attribute(TelemetrySdkVersion, BuildInfo.version)
)
)

private val Mandatory: Resource = Resource(
Attributes(
Attribute(ServiceName, "unknown_service:scala")
)
)

/** Returns the default [[Resource]]. This resource contains the default
* attributes provided by the SDK.
*
* @return
* a [[Resource]].
*/
val Default: Resource = TelemetrySdk.mergeIntoUnsafe(Mandatory)

implicit val showResource: Show[Resource] =
r => show"Resource(${r.attributes}, ${r.schemaUrl})"

}
Loading

0 comments on commit e64156a

Please sign in to comment.