Fix serialization of NPE in logger (#4055)

Fixes an error when the logger processes NPE:
```
[internal-logger-error] One of the printers failed to write a message: java.lang.NullPointerException
```
This commit is contained in:
Dmitry Bushev 2023-01-16 21:38:28 +03:00 committed by GitHub
parent 97ab0d7d5a
commit b8967b96b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 66 additions and 13 deletions

View File

@ -16,7 +16,7 @@ import io.circe._
*/
case class SerializedException(
name: String,
message: String,
message: Option[String],
stackTrace: Seq[SerializedException.TraceElement],
cause: Option[SerializedException]
)
@ -33,7 +33,7 @@ object SerializedException {
): SerializedException =
SerializedException(
name = name,
message = message,
message = Some(message),
stackTrace = stackTrace,
cause = Some(cause)
)
@ -45,18 +45,13 @@ object SerializedException {
message: String,
stackTrace: Seq[SerializedException.TraceElement]
): SerializedException =
SerializedException(
new SerializedException(
name = name,
message = message,
message = Some(message),
stackTrace = stackTrace,
cause = None
)
/** Creates a [[SerializedException]] from a [[Throwable]].
*/
def apply(throwable: Throwable): SerializedException =
fromException(throwable)
/** Encodes a JVM [[Throwable]] as [[SerializedException]].
*/
def fromException(throwable: Throwable): SerializedException = {
@ -66,7 +61,7 @@ object SerializedException {
else Option(throwable.getCause).map(fromException)
SerializedException(
name = Option(clazz.getCanonicalName).getOrElse(clazz.getName),
message = throwable.getMessage,
message = Option(throwable.getMessage),
stackTrace = throwable.getStackTrace.toSeq.map(encodeStackTraceElement),
cause = cause
)
@ -164,7 +159,7 @@ object SerializedException {
): Decoder.Result[SerializedException] = {
for {
name <- json.get[String](JsonFields.Name)
message <- json.get[String](JsonFields.Message)
message <- json.get[Option[String]](JsonFields.Message)
stackTrace <- json.get[Seq[TraceElement]](JsonFields.StackTrace)
cause <-
json.getOrElse[Option[SerializedException]](JsonFields.Cause)(None)

View File

@ -0,0 +1,33 @@
package org.enso.loggingservice.internal
import org.enso.loggingservice.LogLevel
import org.enso.loggingservice.internal.protocol.{
SerializedException,
WSLogMessage
}
import org.scalatest.OptionValues
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import java.time.Instant
import java.time.temporal.ChronoUnit
class DefaultLogMessageRendererSpec
extends AnyWordSpec
with Matchers
with OptionValues {
"DefaultLogMessageRenderer" should {
"render NullPointerException" in {
val renderer = new DefaultLogMessageRenderer(printExceptions = true)
val ts = Instant.now().truncatedTo(ChronoUnit.MILLIS)
val exception =
SerializedException.fromException(new NullPointerException)
val message =
WSLogMessage(LogLevel.Trace, ts, "group", "message", Some(exception))
noException should be thrownBy renderer.render(message)
}
}
}

View File

@ -10,6 +10,7 @@ class SerializedExceptionSpec
extends AnyWordSpec
with Matchers
with OptionValues {
"SerializedException" should {
"serialize and deserialize with nested causes" in {
val cause = SerializedException(
@ -25,7 +26,7 @@ class SerializedExceptionSpec
SerializedException.TraceElement("e2", "loc2"),
SerializedException.TraceElement("e3", "loc3")
),
cause = cause
cause
)
exception.asJson
@ -33,5 +34,15 @@ class SerializedExceptionSpec
.toOption
.value shouldEqual exception
}
"be created from NullPointerException" in {
val exception = new NullPointerException()
val result = SerializedException.fromException(exception)
result.name shouldEqual exception.getClass.getName
result.message shouldEqual None
result.cause shouldEqual None
result.stackTrace shouldBe Symbol("nonEmpty")
}
}
}

View File

@ -14,17 +14,31 @@ import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
class WSLogMessageSpec extends AnyWordSpec with Matchers with OptionValues {
"WSLogMessage" should {
"serialize and deserialize to the same thing" in {
val ts = Instant.now().truncatedTo(ChronoUnit.MILLIS)
val message1 = WSLogMessage(LogLevel.Trace, ts, "group", "message", None)
message1.asJson.as[WSLogMessage].toOption.value shouldEqual message1
noException should be thrownBy message1.asJson.noSpaces
val exception = SerializedException("name", "message", Seq(), None)
val exception = SerializedException("name", "message", Seq())
val message2 =
WSLogMessage(LogLevel.Trace, ts, "group", "message", Some(exception))
message2.asJson.as[WSLogMessage].toOption.value shouldEqual message2
noException should be thrownBy message2.asJson.noSpaces
}
"serialize NullPointerException" in {
val ts = Instant.now().truncatedTo(ChronoUnit.MILLIS)
val exception =
SerializedException.fromException(new NullPointerException)
val message =
WSLogMessage(LogLevel.Trace, ts, "group", "message", Some(exception))
message.asJson.as[WSLogMessage].toOption.value shouldEqual message
noException should be thrownBy message.asJson.noSpaces
}
}
}