diff --git a/build.sbt b/build.sbt index 3912acef..367452cd 100644 --- a/build.sbt +++ b/build.sbt @@ -329,7 +329,8 @@ lazy val xray = crossProject(JSPlatform, JVMPlatform) libraryDependencies ++= Seq( "io.circe" %%% "circe-core" % "0.14.16", "co.fs2" %%% "fs2-io" % fs2Version, - "com.comcast" %%% "ip4s-core" % "3.8.0" + "com.comcast" %%% "ip4s-core" % "3.8.0", + "io.circe" %%% "circe-literal" % "0.14.16" % Test ) ) .jsSettings( diff --git a/modules/xray/src/main/scala/natchez/xray/XRaySpan.scala b/modules/xray/src/main/scala/natchez/xray/XRaySpan.scala index b36bff69..9047d43e 100644 --- a/modules/xray/src/main/scala/natchez/xray/XRaySpan.scala +++ b/modules/xray/src/main/scala/natchez/xray/XRaySpan.scala @@ -75,32 +75,6 @@ private[xray] final case class XRaySpan[F[_]: Concurrent: Clock: Random]( private def toEpochSeconds(t: FiniteDuration): Double = t.toMicros.toDouble / 1000000 - implicit val exceptionEncoder: Encoder.AsObject[XRayException] = - Encoder.AsObject.instance { xex => - val ex = xex.ex - JsonObject( - "fault" -> true.asJson, - "cause" -> Json.obj( - "exceptions" -> Json.arr( - Json.obj( - "id" -> xex.id.asJson, - "message" -> ex.getMessage.asJson, - "type" -> ex.getClass.getName.asJson, - "stack" -> ex.getStackTrace - .map(x => - Json.obj( - "line" -> x.getLineNumber.asJson, - "path" -> x.getFileName.asJson, - "label" -> x.getMethodName.asJson - ) - ) - .asJson - ) - ) - ) - ) - } - def serialize(end: FiniteDuration, exitCase: ExitCase): F[JsonObject] = (fields.get, children.get, XRaySpan.segmentId[F]).mapN { (fs, cs, id) => val (badKeys: Map[String, Json], goodKeys: Map[String, Json]) = @@ -153,6 +127,33 @@ private[xray] object XRaySpan { Encoder[String].contramap(_.toString) final case class XRayException(id: String, ex: Throwable) + object XRayException extends scala.runtime.AbstractFunction2[String, Throwable, XRayException] { + private[xray] implicit val exceptionEncoder: Encoder.AsObject[XRayException] = + Encoder.AsObject.instance { xex => + val ex = xex.ex + JsonObject( + "fault" -> true.asJson, + "cause" -> Json.obj( + "exceptions" -> Json.arr( + Json.obj( + "id" -> xex.id.asJson, + "message" -> Option(ex.getMessage).asJson, + "type" -> ex.getClass.getName.asJson, + "stack" -> ex.getStackTrace + .map(x => + Json.obj( + "line" -> x.getLineNumber.asJson, + "path" -> Option(x.getFileName).asJson, + "label" -> x.getMethodName.asJson + ) + ) + .asJson + ) + ) + ) + ) + } + } implicit val EncodeTraceValue: Encoder[TraceValue] = Encoder.instance { diff --git a/modules/xray/src/test/scala/XRaySpanSuite.scala b/modules/xray/src/test/scala/XRaySpanSuite.scala new file mode 100644 index 00000000..5dd963e3 --- /dev/null +++ b/modules/xray/src/test/scala/XRaySpanSuite.scala @@ -0,0 +1,57 @@ +// Copyright (c) 2019-2020 by Rob Norris and Contributors +// This software is licensed under the MIT License (MIT). +// For more information see LICENSE or https://opensource.org/licenses/MIT + +package natchez +package xray + +import io.circe.syntax.* +import io.circe.literal.* +import munit.ScalaCheckSuite +import natchez.xray.XRaySpan.XRayException +import org.scalacheck.Arbitrary +import org.scalacheck.Arbitrary.arbitrary +import org.scalacheck.Gen +import org.scalacheck.Prop.* + +class XRaySpanSuite extends ScalaCheckSuite { + + val genXRayException: Gen[XRayException] = + for { + id <- arbitrary[String] + cause <- arbitrary[Throwable] + } yield new XRayException(id, cause) + implicit val arbXRayException: Arbitrary[XRayException] = Arbitrary(genXRayException) + + property("header encoding/parsing round-trip") { + forAll { (exception: XRayException) => + val output = exception.asJson + + val stackTrace = exception.ex.getStackTrace.map { x => + json"""{ + "line": ${x.getLineNumber}, + "path": ${Option(x.getFileName)}, + "label": ${x.getMethodName} + }""" + } + + val expected = + json"""{ + "fault": true, + "cause": { + "exceptions": [ + { + "id": ${exception.id}, + "message": ${Option(exception.ex.getMessage).asJson}, + "type": ${exception.ex.getClass.getName}, + "stack": ${stackTrace} + } + ] + } + }""" + + assertEquals(output, expected) + } + } + +}