Skip to content

Commit

Permalink
Merge pull request #472 from iRevive/sdk-common/autoconfigure-error-d…
Browse files Browse the repository at this point in the history
…etails

sdk-common: `AutoConfigure` - use config keys in the error message
  • Loading branch information
iRevive committed Feb 2, 2024
2 parents 30516f9 + b28e719 commit 5a0782c
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,30 @@ trait AutoConfigure[F[_], A] {

object AutoConfigure {

/** If the component cannot be created due to an error, the meaningful debug
* information will be added to the exception.
*
* @param hint
* the name of the component. For example: Sampler, TelemetryResource
*
* @param configKeys
* the config keys that could be used to autoconfigure the component
*
* @tparam F
* the higher-kinded type of a polymorphic effect
*
* @tparam A
* the type of the component
*/
abstract class WithHint[F[_]: MonadThrow, A](
hint: String
hint: String,
configKeys: Set[Config.Key[_]]
) extends AutoConfigure[F, A] {

final def configure(config: Config): Resource[F, A] =
fromConfig(config).adaptError {
case e: AutoConfigureError => e
case cause => new AutoConfigureError(hint, cause)
case cause => AutoConfigureError(hint, cause, configKeys, config)
}

protected def fromConfig(config: Config): Resource[F, A]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,69 @@

package org.typelevel.otel4s.sdk.autoconfigure

final class AutoConfigureError(
hint: String,
final class AutoConfigureError private (
message: String,
cause: Throwable
) extends RuntimeException(s"Cannot auto configure [$hint]", cause)
) extends RuntimeException(message, cause)

object AutoConfigureError {

/** Creates an [[AutoConfigureError]] with the given `hint` and `cause`.
*
* @param hint
* the name of the component
*
* @param cause
* the cause
*/
def apply(
hint: String,
cause: Throwable
): AutoConfigureError =
new AutoConfigureError(
s"Cannot autoconfigure [$hint]. Cause: ${cause.getMessage}.",
cause
)

/** Creates an [[AutoConfigureError]] with the given `hint` and `cause`. The
* debug information associated with the `configKeys` will be added to the
* message.
*
* @param hint
* the name of the component
*
* @param cause
* the cause
*
* @param configKeys
* the config keys that could be used to autoconfigure the component
*
* @param config
* the config
*/
def apply(
hint: String,
cause: Throwable,
configKeys: Set[Config.Key[_]],
config: Config
): AutoConfigureError =
if (configKeys.nonEmpty) {
val params = configKeys.zipWithIndex
.map { case (key, i) =>
val name = key.name
val value =
config.get[String](key.name).toOption.flatten.getOrElse("N/A")
val idx = i + 1
s"$idx) `$name` - $value"
}
.mkString("\n")

new AutoConfigureError(
s"Cannot autoconfigure [$hint].\nCause: ${cause.getMessage}.\nConfig:\n$params",
cause
)
} else {
apply(hint, cause)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,15 @@ object Config {
Impl(default ++ env ++ props)
}

/** Creates a [[Config]] with the given properties. The properties will be
* treated as the system props.
*
* @param properties
* the properties to use
*/
def ofProps(properties: Map[String, String]): Config =
apply(properties, Map.empty, Map.empty)

/** Creates a [[Config]] by loading properties from the env variables and
* system props.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ import java.nio.charset.StandardCharsets
* [[https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/README.md#opentelemetry-resource]]
*/
private final class TelemetryResourceAutoConfigure[F[_]: MonadThrow]
extends AutoConfigure.WithHint[F, TelemetryResource]("TelemetryResource") {
extends AutoConfigure.WithHint[F, TelemetryResource](
"TelemetryResource",
TelemetryResourceAutoConfigure.ConfigKeys.All
) {

import TelemetryResourceAutoConfigure.ConfigKeys

Expand Down Expand Up @@ -98,6 +101,8 @@ private[sdk] object TelemetryResourceAutoConfigure {

val ServiceName: Config.Key[String] =
Config.Key("otel.service.name")

val All: Set[Config.Key[_]] = Set(DisabledKeys, Attributes, ServiceName)
}

/** Returns [[AutoConfigure]] that configures the [[TelemetryResource]].
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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.autoconfigure

import munit.ScalaCheckSuite
import org.scalacheck.Gen
import org.scalacheck.Prop

class AutoConfigureErrorSuite extends ScalaCheckSuite {

test("use hint in the error message") {
Prop.forAll(Gen.alphaNumStr, Gen.alphaNumStr) { (hint, reason) =>
val error = AutoConfigureError(hint, new Err(reason))

assertEquals(
error.getMessage,
s"Cannot autoconfigure [$hint]. Cause: $reason."
)
}
}

test("use hint and config keys in the error message") {
Prop.forAll(Gen.alphaNumStr, Gen.alphaNumStr) { (hint, reason) =>
val config = Config.ofProps(
Map(
"a.b.c.d" -> "value1",
"c" -> "value;for;key;3"
)
)

val keys: Set[Config.Key[_]] = Set(
Config.Key[String]("a.b.c.d"),
Config.Key[Set[String]]("a1.b1.c.1.d3"),
Config.Key[Map[String, String]]("c"),
Config.Key[Double]("d")
)

val error = AutoConfigureError(hint, new Err(reason), keys, config)

val expected =
s"""Cannot autoconfigure [$hint].
|Cause: $reason.
|Config:
|1) `a.b.c.d` - value1
|2) `a1.b1.c.1.d3` - N/A
|3) `c` - value;for;key;3
|4) `d` - N/A""".stripMargin

assertEquals(error.getMessage, expected)
}
}

private class Err(reason: String) extends RuntimeException(reason)

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

import cats.effect.IO
import cats.effect.Resource
import cats.syntax.either._
import munit.CatsEffectSuite

import scala.util.control.NoStackTrace

class AutoConfigureSuite extends CatsEffectSuite {

test("use hint in the error, ignore empty set of keys") {
val config = Config.ofProps(Map.empty)

val io =
alwaysFailing("Component", Set.empty).configure(config).use_.attempt

io.map(_.leftMap(_.getMessage))
.assertEquals(Left(AutoConfigureError("Component", Err).getMessage))
}

test("use hint in the error, add keys to the error message") {
val config = Config.ofProps(Map.empty)
val keys: Set[Config.Key[_]] = Set(
Config.Key[String]("a"),
Config.Key[Set[String]]("b"),
Config.Key[Map[String, String]]("c"),
Config.Key[Double]("d")
)

val io = alwaysFailing("Component", keys).configure(config).use_.attempt

io.map(_.leftMap(_.getMessage))
.assertEquals(
Left(AutoConfigureError("Component", Err, keys, config).getMessage)
)
}

private def alwaysFailing(
hint: String,
configKeys: Set[Config.Key[_]]
): AutoConfigure.WithHint[IO, String] = {
new AutoConfigure.WithHint[IO, String](hint, configKeys) {
protected def fromConfig(config: Config): Resource[IO, String] =
Resource.raiseError(Err: Throwable)
}
}

private object Err
extends RuntimeException("Something went wrong")
with NoStackTrace

}
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,6 @@ class ConfigSuite extends FunSuite {
}

private def makeConfig(props: Map[String, String]): Config =
Config(props, Map.empty, Map.empty)
Config.ofProps(props)

}

0 comments on commit 5a0782c

Please sign in to comment.