Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
53 changes: 27 additions & 26 deletions modules/xray/src/main/scala/natchez/xray/XRaySpan.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]) =
Expand Down Expand Up @@ -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 {
Expand Down
57 changes: 57 additions & 0 deletions modules/xray/src/test/scala/XRaySpanSuite.scala
Original file line number Diff line number Diff line change
@@ -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)
}
}

}