Skip to content

Commit

Permalink
Replace TraceHooks with TracedProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
alexcardell committed Oct 5, 2024
1 parent cefad31 commit 53217fa
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 148 deletions.
3 changes: 1 addition & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import build.V

Global / onChangedBuildSource := ReloadOnSourceChanges

// https://typelevel.org/sbt-typelevel/faq.html#what-is-a-base-version-anyway
ThisBuild / tlBaseVersion := "0.4" // your current series x.y
ThisBuild / tlBaseVersion := "0.5"

ThisBuild / organization := "io.cardell"
ThisBuild / organizationName := "Alex Cardell"
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Copyright 2023 Alex Cardell
*
* 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 io.cardell.openfeature.otel4s

import cats.Applicative
import cats.Monad
import cats.syntax.all._
import org.typelevel.otel4s.Attribute
import org.typelevel.otel4s.AttributeKey
import org.typelevel.otel4s.Attributes
import org.typelevel.otel4s.trace.StatusCode
import org.typelevel.otel4s.trace.Tracer

import io.cardell.openfeature.EvaluationContext
import io.cardell.openfeature.StructureCodec
import io.cardell.openfeature.otel4s.FeatureFlagAttributes.FeatureFlagKey
import io.cardell.openfeature.otel4s.FeatureFlagAttributes.FeatureFlagProviderName
import io.cardell.openfeature.otel4s.FeatureFlagAttributes.FeatureFlagVariant
import io.cardell.openfeature.provider.EvaluationProvider
import io.cardell.openfeature.provider.Provider
import io.cardell.openfeature.provider.ProviderMetadata
import io.cardell.openfeature.provider.ResolutionDetails
import cats.MonadThrow

class TracedProvider[F[_]: Tracer: MonadThrow](
provider: Provider[F]
) extends EvaluationProvider[F] {

override def metadata: ProviderMetadata = provider.metadata

override def resolveBooleanValue(
flagKey: String,
defaultValue: Boolean,
context: EvaluationContext
): F[ResolutionDetails[Boolean]] =
trace("boolean", flagKey)(
provider.resolveBooleanValue(
flagKey,
defaultValue,
context
)
)

override def resolveStringValue(
flagKey: String,
defaultValue: String,
context: EvaluationContext
): F[ResolutionDetails[String]] =
trace("string", flagKey)(
provider.resolveStringValue(
flagKey,
defaultValue,
context
)
)

override def resolveIntValue(
flagKey: String,
defaultValue: Int,
context: EvaluationContext
): F[ResolutionDetails[Int]] =
trace("int", flagKey)(
provider.resolveIntValue(
flagKey,
defaultValue,
context
)
)

override def resolveDoubleValue(
flagKey: String,
defaultValue: Double,
context: EvaluationContext
): F[ResolutionDetails[Double]] =
trace("double", flagKey)(
provider.resolveDoubleValue(
flagKey,
defaultValue,
context
)
)

override def resolveStructureValue[A: StructureCodec](
flagKey: String,
defaultValue: A,
context: EvaluationContext
): F[ResolutionDetails[A]] =
trace("structure", flagKey)(
provider.resolveStructureValue(
flagKey,
defaultValue,
context
)
)

private def flagAttributes(flagKey: String): Attributes =
Attributes(FeatureFlagKey(flagKey), FeatureFlagProviderName(metadata.name))

private def variantAttributes(maybeVariant: Option[String]): Attributes =
Attributes.empty.concat(FeatureFlagVariant.maybe(maybeVariant))

private def trace[A](flagType: String, flagKey: String)(
fa: F[ResolutionDetails[A]]
): F[ResolutionDetails[A]] =
Tracer[F].span(s"evaluate-${flagType}-flag").use { span =>
for {
_ <- span.addAttributes(flagAttributes(flagKey))
res <- fa.onError(span.recordException(_))
_ <- span.addAttributes(variantAttributes(res.variant))
_ <- span.setStatus(StatusCode.Ok)
} yield res
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import io.cardell.openfeature.FlagValue
import io.cardell.openfeature.provider.ProviderImpl
import io.cardell.openfeature.provider.memory.MemoryProvider

class TraceHookTest extends CatsEffectSuite {
class TracedProviderTest extends CatsEffectSuite {

val setupProvider = MemoryProvider[IO](
Map("boolean-flag" -> FlagValue.BooleanValue(true))
Expand All @@ -37,7 +37,7 @@ class TraceHookTest extends CatsEffectSuite {

test("span is applied") {
val expectedFlagResult = true
val expectedSpanName = "resolve-flag"
val expectedSpanName = "evaluate-boolean-flag"
val expectedSpanCount = 1
val expectedSpanStatus = StatusCode.Ok

Expand All @@ -46,10 +46,9 @@ class TraceHookTest extends CatsEffectSuite {

setupTracer.flatMap { implicit tracer =>
for {
hooks <- TraceHooks.ioLocal
provider <- setupProvider.map(ProviderImpl[IO])
hookedProvider = hooks.foldLeft(provider)(_ withHook _)
flagResolution <- hookedProvider.resolveBooleanValue(
tracedProvider = new TracedProvider[IO](provider)
flagResolution <- tracedProvider.resolveBooleanValue(
"boolean-flag",
false,
EvaluationContext.empty
Expand Down Expand Up @@ -79,53 +78,4 @@ class TraceHookTest extends CatsEffectSuite {

}

test("span is recorded as exception when exception thrown in after hook") {
val expectedSpanName = "resolve-flag"
val expectedSpanCount = 1
val expectedSpanStatus = StatusCode.Error

val throwingHook = AfterHook[IO] { case _ =>
IO.raiseError(new Throwable("throwing hook"))
}

setupTestkit.use { kit =>
val setupTracer = kit.tracerProvider.tracer("name").get

setupTracer.flatMap { implicit tracer =>
for {
traceHooks <- TraceHooks.ioLocal
(before, others) = (traceHooks.head, traceHooks.tail)
hooks = List(before, throwingHook) ++ others
provider <- setupProvider.map(ProviderImpl[IO])
hookedProvider = hooks.foldLeft(provider)(_ withHook _)
_ <-
hookedProvider
.resolveBooleanValue(
"boolean-flag",
false,
EvaluationContext.empty
)
.attempt
spans <- kit.finishedSpans
spanCount = spans.size
headSpan = spans.headOption
spanName = headSpan.map(_.name)
spanStatus = headSpan.map(_.status.status)
spanEnded = headSpan.map(_.hasEnded)
spanAttrs = headSpan.map(_.attributes.elements)
flagKeyAttrExists = spanAttrs.map(
_.exists(_ == FeatureFlagAttributes.FeatureFlagKey("boolean-flag"))
)
} yield {
assertEquals(spanCount, expectedSpanCount)
assertEquals(spanName, Some(expectedSpanName))
assertEquals(spanStatus, Some(expectedSpanStatus))
assertEquals(spanEnded, Some(true))
assertEquals(flagKeyAttrExists, Some(true))
}
}
}

}

}
2 changes: 1 addition & 1 deletion project/Versions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ object V {
val catsEffect = "3.5.4"
val http4s = "0.23.26"
val circe = "0.14.7"
val otel4s = "0.9.0"
val otel4s = "0.10.0"

val munit = "1.0.0-RC1"
val munitCatsEffect = "2.0.0-M5"
Expand Down

0 comments on commit 53217fa

Please sign in to comment.