Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Capture logs during testing and print them in case of failure #587

Merged
merged 6 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from 5 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
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,6 @@ trait BaseProps {
def BrightCoveVideoUri(accountId: String, videoId: String): Uri =
uri"https://cms.api.brightcove.com/v1/accounts/$accountId/videos/$videoId/sources"

def DisableLicense: Boolean = booleanPropOrElse("DISABLE_LICENSE", default = false)

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class TaxonomyApiClientTest extends UnitSuite with TestEnvironment {

override val taxonomyApiClient: TaxonomyApiClient = spy(new TaxonomyApiClient)

override protected def beforeEach(): Unit = {
override def beforeEach(): Unit = {
// Since we use spy, we reset the mock before each test allowing verify to be accurate
reset(taxonomyApiClient)
}
Expand Down
3 changes: 3 additions & 0 deletions log4j2-test.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
Configuration:
status: warn
Loggers:
Logger:
name: "no.ndla"
level: debug
Root:
level: warn
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class CloneFolderTest
val myndlaApiFolderUrl: String = s"$myndlaApiBaseUrl/myndla-api/v1/folders"

override def beforeAll(): Unit = {
super.beforeAll()
implicit val ec: ExecutionContextExecutorService =
ExecutionContext.fromExecutorService(Executors.newSingleThreadExecutor)
Future { myndlaApi.run() }: Unit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class FolderTest
val myndlaApiFolderUrl: String = s"$myndlaApiBaseUrl/myndla-api/v1/folders"

override def beforeAll(): Unit = {
super.beforeAll()
implicit val ec = ExecutionContext.fromExecutorService(Executors.newSingleThreadExecutor)
Future { myndlaApi.run() }: Unit
Thread.sleep(4000)
Expand Down
13 changes: 11 additions & 2 deletions network/src/main/scala/no/ndla/network/tapir/NdlaTapirMain.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ package no.ndla.network.tapir
import no.ndla.common.Environment.setPropsFromEnv
import no.ndla.common.configuration.BaseProps
import org.log4s.{Logger, getLogger}

import scala.concurrent.Future
import scala.io.Source
import scala.util.{Failure, Try}

trait NdlaTapirMain {
val logger: Logger = getLogger
Expand All @@ -22,7 +24,9 @@ trait NdlaTapirMain {
def beforeStart(): Unit

private def logCopyrightHeader(): Unit = {
logger.info(Source.fromInputStream(getClass.getResourceAsStream("/log-license.txt")).mkString)
if (!props.DisableLicense) {
logger.info(Source.fromInputStream(getClass.getResourceAsStream("/log-license.txt")).mkString)
}
}

private def performWarmup(): Unit = if (!props.disableWarmup) {
Expand All @@ -44,9 +48,14 @@ trait NdlaTapirMain {
setPropsFromEnv()

logCopyrightHeader()
startServer(props.ApplicationName, props.ApplicationPort) {
Try(startServer(props.ApplicationName, props.ApplicationPort) {
beforeStart()
performWarmup()
}) match {
case Failure(ex) =>
logger.error(ex)("Failed to start server, exiting...")
throw ex
case _ =>
}
}
}
17 changes: 11 additions & 6 deletions project/scalatestsuitelib.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ import sbt.Keys.*
object scalatestsuitelib extends Module {
override val moduleName: String = "scalatestsuite"
override val enableReleases: Boolean = false
lazy val dependencies: Seq[ModuleID] = Seq(
"org.scalatest" %% "scalatest" % ScalaTestV,
"org.testcontainers" % "elasticsearch" % TestContainersV,
"org.testcontainers" % "testcontainers" % TestContainersV,
"org.testcontainers" % "postgresql" % TestContainersV
) ++ database ++ vulnerabilityOverrides ++ mockito
lazy val dependencies: Seq[ModuleID] = withLogging(
Seq(
"org.scalatest" %% "scalatest" % ScalaTestV,
"org.testcontainers" % "elasticsearch" % TestContainersV,
"org.testcontainers" % "testcontainers" % TestContainersV,
"org.testcontainers" % "postgresql" % TestContainersV
),
database,
vulnerabilityOverrides,
mockito
)

override lazy val settings: Seq[Def.Setting[?]] = Seq(
libraryDependencies ++= dependencies
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Part of NDLA scalatestsuite
* Copyright (C) 2025 NDLA
*
* See LICENSE
*
*/

package no.ndla.scalatestsuite

import org.apache.logging.log4j.core.LogEvent
import org.apache.logging.log4j.core.appender.AbstractAppender
import org.apache.logging.log4j.core.layout.PatternLayout

import scala.collection.mutable.ListBuffer

object Layout {
lazy val prebuiltLayout: PatternLayout = PatternLayout
.newBuilder()
.withPattern("[%level] %C.%M#%L: %msg%n")
.build()
}

class BufferedLogAppender
extends AbstractAppender("BufferedAppender", null, Layout.prebuiltLayout, false, Array.empty) {
private val logQueue = ListBuffer.empty[String]
def clear(): Unit = logQueue.clear()
def printLogs(): Unit = logQueue.foreach(print)
def getAndClearLogs: List[String] = {
val toReturn = logQueue.toList
logQueue.clear()
toReturn
}

override def append(event: LogEvent): Unit = {
val logString = getLayout.toSerializable(event).toString
logQueue.append(logString)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Part of NDLA scalatestsuite
* Copyright (C) 2025 NDLA
*
* See LICENSE
*
*/

package no.ndla.scalatestsuite

object ColoredText {
val RED = "\u001b[31m"
val GREEN = "\u001b[32m"
val YELLOW = "\u001b[33m"
val BLUE = "\u001b[34m"
val RESET = "\u001b[0m"

def print(color: Colors, text: String) = {
color match {
case Red => println(s"$RED$text$RESET")
case Green => println(s"$GREEN$text$RESET")
case Yellow => println(s"$YELLOW$text$RESET")
case Blue => println(s"$BLUE$text$RESET")
}
}

}
15 changes: 15 additions & 0 deletions scalatestsuite/src/main/scala/no/ndla/scalatestsuite/Colors.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Part of NDLA scalatestsuite
* Copyright (C) 2025 NDLA
*
* See LICENSE
*
*/

package no.ndla.scalatestsuite

sealed trait Colors
case object Red extends Colors
case object Green extends Colors
case object Yellow extends Colors
case object Blue extends Colors
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,13 @@ abstract class IntegrationSuite(
})
}

override def beforeAll(): Unit = setDatabaseEnvironment()
override def beforeAll(): Unit = {
super.beforeAll()
setDatabaseEnvironment()
}

override def afterAll(): Unit = {
super.afterAll()
setPropEnv(previousDatabaseEnv)
elasticSearchContainer.foreach(c => c.stop())
postgresContainer.foreach(c => c.stop())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Part of NDLA scalatestsuite
* Copyright (C) 2025 NDLA
*
* See LICENSE
*
*/

package no.ndla.scalatestsuite

import org.apache.logging.log4j.core.LoggerContext
import org.apache.logging.log4j.{Level, LogManager}
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach, Outcome}

import scala.collection.mutable.ListBuffer

/** Sets up test environment to keep logs and print them if the test fails */
trait TestSuiteLoggingSetup extends AnyFunSuite with BeforeAndAfterEach with BeforeAndAfterAll {
private val appender = new BufferedLogAppender()
private def getNDLALogger = LogManager.getContext(false).asInstanceOf[LoggerContext].getLogger("no.ndla")
private val beforeAllLog = ListBuffer.empty[String]

private def setupLogger(): Unit = {
if (!appender.isStarted) appender.start()
val ndlaLogger = getNDLALogger
ndlaLogger.setLevel(Level.DEBUG)
ndlaLogger.addAppender(appender)
}

override def beforeEach(): Unit = {
val logs = appender.getAndClearLogs
beforeAllLog.addAll(logs)
setupLogger()
super.beforeEach()
}

override def beforeAll(): Unit = {
setupLogger()
super.beforeAll()
}

override def afterEach(): Unit = {
val ndlaLogger = getNDLALogger
ndlaLogger.removeAppender(appender)
appender.clear()
super.afterEach()
}

override def withFixture(test: NoArgTest): Outcome = {
val result = super.withFixture(test)
if (!result.isSucceeded) {
// If test fails, print the buffered logs
val testName = s"${this.suiteName}$$'${test.name}'"
ColoredText.print(Red, s"\n---- Captured Logs for test: $testName ----")
if (beforeAllLog.nonEmpty) {
ColoredText.print(Red, s">>> Captured Logs from $suiteName$$beforeAll: >>>")
beforeAllLog.foreach(print)
ColoredText.print(Red, s"<<< End of Captured Logs from $suiteName$$beforeAll <<<")
}
appender.printLogs()
ColoredText.print(Red, s"---- End of Captured Logs for test: $testName ----\n")
}
result
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@

package no.ndla.scalatestsuite

import org.scalatestplus.mockito.MockitoSugar
import org.scalatest._
import org.scalatest.*
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
import org.scalatestplus.mockito.MockitoSugar

import java.io.IOException
import java.net.ServerSocket
import scala.util.Properties.{propOrNone, setProp}
import scala.util.{Try, Success, Failure}
import scala.util.{Failure, Success, Try}

abstract class UnitTestSuite
extends AnyFunSuite
Expand All @@ -25,7 +25,10 @@ abstract class UnitTestSuite
with Inspectors
with MockitoSugar
with BeforeAndAfterEach
with BeforeAndAfterAll {
with BeforeAndAfterAll
with TestSuiteLoggingSetup {

setPropEnv("DISABLE_LICENSE", "true"): Unit

def setPropEnv(key: String, value: String): String = setProp(key, value)

Expand Down