Skip to content

Commit

Permalink
Replace TraceHooks with TracedProvider (#39)
Browse files Browse the repository at this point in the history
And update otel4s to 0.10

The IOLocal solution doesn't work for propagating trace context to
traces created in the provider, and also doesn't provide much context
for the evaluation details
  • Loading branch information
alexcardell authored Oct 6, 2024
1 parent cefad31 commit a12deb1
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 155 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
21 changes: 15 additions & 6 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,22 @@ def clientWithHook(client: FeatureClient[IO]) =
```scala mdoc
import cats.effect.IO
import org.typelevel.otel4s.trace.Tracer
import io.cardell.openfeature.FeatureClient
import io.cardell.openfeature.otel4s.TraceHooks
import io.cardell.openfeature.provider.EvaluationProvider
import io.cardell.openfeature.otel4s.TracedProvider

def traceExample(
provider: EvaluationProvider[IO]
)(implicit T: Tracer[IO]) =
new TracedProvider[IO](provider)

// or

import io.cardell.openfeature.otel4s.syntax._

def tracedClient(
client: FeatureClient[IO]
)(implicit T: Tracer[IO]) = TraceHooks.ioLocal
.map(hooks => client.withHooks(hooks))
def tracedProviderSyntax(
provider: EvaluationProvider[IO]
)(implicit T: Tracer[IO]) =
provider.withTracing
```

### Variants
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* 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.MonadThrow
import cats.syntax.all._
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.ProviderMetadata
import io.cardell.openfeature.provider.ResolutionDetails

class TracedProvider[F[_]: Tracer: MonadThrow](
provider: EvaluationProvider[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
@@ -0,0 +1,37 @@
/*
* 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.syntax

import cats.MonadThrow
import org.typelevel.otel4s.trace.Tracer

import io.cardell.openfeature.otel4s.TracedProvider
import io.cardell.openfeature.provider.EvaluationProvider

class EvaluationProviderOps[F[_]: Tracer: MonadThrow](
provider: EvaluationProvider[F]
) {
def withTracing: EvaluationProvider[F] = new TracedProvider[F](provider)
}

trait EvaluationProviderSyntax {

implicit def ops[F[_]: Tracer: MonadThrow](
provider: EvaluationProvider[F]
): EvaluationProviderOps[F] = new EvaluationProviderOps[F](provider)

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

package object syntax extends EvaluationProviderSyntax
Loading

0 comments on commit a12deb1

Please sign in to comment.