diff --git a/modules/log/shared/src/main/scala/LogSpan.scala b/modules/log/shared/src/main/scala/LogSpan.scala index e4c3d97c..d8cb3419 100644 --- a/modules/log/shared/src/main/scala/LogSpan.scala +++ b/modules/log/shared/src/main/scala/LogSpan.scala @@ -75,7 +75,7 @@ private[log] final case class LogSpan[F[_]: Sync: Logger]( putAny( "exit.case" -> "error".asJson, "exit.error.class" -> err.getClass.getName.asJson, - "exit.error.message" -> err.getMessage.asJson, + "exit.error.message" -> Option(err.getMessage).map(_.asJson).getOrElse(Json.Null), "exit.error.stackTrace" -> err.getStackTrace.map(_.toString).asJson ) *> put(fields: _*) diff --git a/modules/log/shared/src/test/scala/LogSuite.scala b/modules/log/shared/src/test/scala/LogSuite.scala index 2843e461..dc69306c 100644 --- a/modules/log/shared/src/test/scala/LogSuite.scala +++ b/modules/log/shared/src/test/scala/LogSuite.scala @@ -7,6 +7,7 @@ package log import munit.CatsEffectSuite import cats.effect.IO +import cats.syntax.traverse._ import io.circe.Json import natchez.Span.SpanKind @@ -28,6 +29,7 @@ class LogSuite extends CatsEffectSuite { .remove("trace.span_id") .remove("trace.parent_id") .remove("trace.trace_id") + .remove("exit.error.stackTrace") // contains thread info .mapValues(filter) ) ) @@ -90,4 +92,48 @@ class LogSuite extends CatsEffectSuite { } } + test("log formatter should handle exceptions") { + val exWithMsg = new RuntimeException("oops") + val exNull = new RuntimeException(null: String) + + val tests = List[(Throwable, String)]( + exWithMsg -> """|test: [info] { + | "name" : "root span", + | "service" : "service", + | "span.kind" : "Server", + | "span.links" : [ + | ], + | "exit.case" : "succeeded", + | "exit.error.class" : "java.lang.RuntimeException", + | "exit.error.message" : "oops", + | "children" : [ + | ] + |} + |""".stripMargin, + exNull -> """|test: [info] { + | "name" : "root span", + | "service" : "service", + | "span.kind" : "Server", + | "span.links" : [ + | ], + | "exit.case" : "succeeded", + | "exit.error.class" : "java.lang.RuntimeException", + | "exit.error.message" : null, + | "children" : [ + | ] + |} + |""".stripMargin + ) + + tests.traverse { case (exception, expected) => + MockLogger.newInstance[IO]("test").flatMap { implicit log => + Log + .entryPoint[IO]("service", filter(_).spaces2) + .root("root span", Span.Options.Defaults.withSpanKind(SpanKind.Server)) + .use { root => + root.attachError(err = exception) + } *> log.get.assertEquals(expected) + } + } + } }