Skip to content

Instantly share code, notes, and snippets.

@dacr
Last active May 27, 2023 15:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save dacr/216d7f1f7aee3547b0ced689416d88c9 to your computer and use it in GitHub Desktop.
Save dacr/216d7f1f7aee3547b0ced689416d88c9 to your computer and use it in GitHub Desktop.
Playing/testing with logback configuration. / published by https://github.com/dacr/code-examples-manager #b45b0038-e0fc-4dd5-810f-5ad9b420f41e/96620eccd880f849f2ec1199c0d28231bdd0ae22
// summary : Playing/testing with logback configuration.
// keywords : scala, scalatest, logs, logback, @testable
// publish : gist
// authors : David Crosson
// license : Apache NON-AI License Version 2.0 (https://raw.githubusercontent.com/non-ai-licenses/non-ai-licenses/main/NON-AI-APACHE2)
// id : b45b0038-e0fc-4dd5-810f-5ad9b420f41e
// created-on : 2020-05-31T19:54:52Z
// managed-by : https://github.com/dacr/code-examples-manager
// run-with : scala-cli $file
// ---------------------
//> using scala "3.3.0"
//> using dep "org.scalatest::scalatest:3.2.16"
//> using dep "ch.qos.logback:logback-classic:1.4.7"
//> using dep "net.logstash.logback:logstash-logback-encoder:7.3"
//> using objectWrapper
// ---------------------
import org.scalatest._, flatspec._, matchers._
import org.slf4j._
trait LogBackHelpers {
def configureLogBack(xmlConfig:String):Unit = {
val loggerContext = LoggerFactory.getILoggerFactory.asInstanceOf[ch.qos.logback.classic.LoggerContext]
loggerContext.reset()
val configurator = new ch.qos.logback.classic.joran.JoranConfigurator()
val configStream = new java.io.ByteArrayInputStream(xmlConfig.getBytes)
configurator.setContext(loggerContext)
configurator.doConfigure(configStream) // loads logback file
}
def getRootLogger():ch.qos.logback.classic.Logger = {
LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME).asInstanceOf[ch.qos.logback.classic.Logger]
}
def configureDebugRootLogger():java.io.InputStream = {
val loggerContext = LoggerFactory.getILoggerFactory.asInstanceOf[ch.qos.logback.classic.LoggerContext]
loggerContext.reset()
val rootLogger = getRootLogger()
val output = new java.io.PipedOutputStream()
val encoder = new ch.qos.logback.classic.encoder.PatternLayoutEncoder()
encoder.setPattern("%date %level [%thread] %logger{10} [%file:%line] - %msg%n")
encoder.setContext(loggerContext)
val outputStreamAppender = new ch.qos.logback.core.OutputStreamAppender[ch.qos.logback.classic.spi.ILoggingEvent]()
outputStreamAppender.setContext(loggerContext)
outputStreamAppender.setOutputStream(output)
outputStreamAppender.setEncoder(encoder)
rootLogger.addAppender(outputStreamAppender)
new java.io.PipedInputStream(output)
}
}
class LoggingTesting extends AnyFlatSpec with should.Matchers with LogBackHelpers {
override val suiteName = "LoggingTesting"
// -------------------------------------------------------------------------------------------------
"logback" should "allow to programmically change default ROOT logger configuration" in {
val rootLogger = getRootLogger()
rootLogger.setLevel(ch.qos.logback.classic.Level.ERROR)
rootLogger.getLevel shouldBe ch.qos.logback.classic.Level.ERROR
}
// -------------------------------------------------------------------------------------------------
it should "be configurable through a xml configuration" in {
val config=
"""<configuration scan="false" scanPeriod="10 seconds">
| <root level="INFO">
| </root>
|</configuration>""".stripMargin
configureLogBack(config)
getRootLogger().getLevel shouldBe ch.qos.logback.classic.Level.INFO
}
// -------------------------------------------------------------------------------------------------
it should "raise an exception with an invalid configuration file" in {
val config=
"""<configuration scan="false" scanPeriod="10 seconds">
| <rootx level="INFO">
| <appender-ref ref="FILE"/>
| </root>
|</configuration>""".stripMargin
intercept[Exception] {
configureLogBack(config)
}
}
// -------------------------------------------------------------------------------------------------
ignore should "be possible to use a memory appender" in {
val logInputStream = configureDebugRootLogger()
val logEntries = scala.io.Source.fromInputStream(logInputStream)
val logger = LoggerFactory.getLogger("Test")
logger.info("HELLO")
logEntries.getLines.next should include regex "(?i)hello"
}
// -------------------------------------------------------------------------------------------------
it should "insert exception stack trace digest" in {
val logFileName="testFile.log"
val config =
"""<?xml version="1.0" encoding="UTF-8"?>
|<!-- application logging configuration to ship logs directly to Logstash -->
|<configuration>
| <!-- define stack trace element exclusion patterns as a global property -->
| <property name="STE_EXCLUSIONS" value="^ammonite\.,\$\$FastClassByCGLIB\$\$,\$\$EnhancerBySpringCGLIB\$\$,^sun\.reflect\..*\.invoke,^com\.sun\.,^sun\.net\.,^net\.sf\.cglib\.proxy\.MethodProxy\.invoke,^org\.junit\.,^org\.apache\.maven\.surefire\.,^java\.lang\.reflect\.Method\.invoke,^java\.util\.concurrent\.ThreadPoolExecutor\.runWorker,^java\.lang\.Thread\.run"/>
|
| <conversionRule conversionWord="sEx" converterClass="net.logstash.logback.stacktrace.ShortenedThrowableConverter" />
|
|<appender name="FILE" class="ch.qos.logback.core.FileAppender">
| <file>{{LOG_FILE_NAME}}</file>
| <append>false</append>
| <immediateFlush>true</immediateFlush>
| <encoder>
| <conversionRule conversionWord="exId"
| converterClass="net.logstash.logback.stacktrace.ShortenedThrowableConverter" />
| <pattern>
| %-4relative [%thread] %-5level %logger{35} - %msg - %sEx{3,full,full,inlineHash,${STE_EXCLUSIONS}}%n
| </pattern>
| <!-- computes and adds a 'stack_hash' field on errors -->
| <provider class="net.logstash.logback.composite.loggingevent.StackHashJsonProvider">
| <!-- use global property for exclusion patterns -->
| <exclusions>${STE_EXCLUSIONS}</exclusions>
| </provider>
| <!-- enriches the stack trace with unique hash -->
| <throwableConverter class="net.logstash.logback.stacktrace.ShortenedThrowableConverter">
| <!-- compute and inline hash in stack trace -->
| <inlineHash>true</inlineHash>
| <!-- use global property for exclusion patterns -->
| <exclusions>${STE_EXCLUSIONS}</exclusions>
| </throwableConverter>
| </encoder>
| </appender>
|
| <logger name="Test" level="DEBUG" />
|
| <root level="INFO">
| <appender-ref ref="FILE" />
| </root>
|
|</configuration>""".stripMargin.replaceAll("""\{\{LOG_FILE_NAME\}\}""", logFileName)
configureLogBack(config)
val logger = LoggerFactory.getLogger("Test")
logger.info("started")
logger.error("something wrong", new Exception("bad"))
logger.error("something wrong", new Exception("bad"))
logger.debug("debug message")
val logFileContent = scala.io.Source.fromFile(logFileName).getLines.toList.mkString("\n")
logFileContent should include regex """(?i)<#[A-F0-9]+>"""
}
}
org.scalatest.tools.Runner.main(Array("-oDF", "-s", classOf[LoggingTesting].getName))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment