// SPDX-License-Identifier: Apache-2.0 package loggertests import logger.Logger.OutputCaptor import logger.{LazyLogging, LogLevel, Logger} import org.scalatest.OneInstancePerTest import org.scalatest.freespec.AnyFreeSpec import org.scalatest.matchers.should.Matchers object LoggerSpec { val ErrorMsg = "message error" val WarnMsg = "message warn" val InfoMsg = "message info" val DebugMsg = "message debug" val TraceMsg = "message trace" val globalLevel = LogLevel.Warn } class Logger1 extends LazyLogging { def run(): Unit = { logger.error(LoggerSpec.ErrorMsg) logger.warn(LoggerSpec.WarnMsg) logger.info(LoggerSpec.InfoMsg) logger.debug(LoggerSpec.DebugMsg) logger.trace(LoggerSpec.TraceMsg) } } class LogsInfo2 extends LazyLogging { def run(): Unit = { logger.info("logger2") } } class LogsInfo3 extends LazyLogging { def run(): Unit = { logger.info("logger3") } } class LoggerSpec extends AnyFreeSpec with Matchers with OneInstancePerTest with LazyLogging { "Logger is a simple but powerful logging system" - { "Following tests show how global level can control logging" - { "setting level to None will result in warn messages" in { Logger.makeScope() { val captor = new OutputCaptor Logger.setOutput(captor.printStream) Logger.setLevel(LogLevel.None) val r1 = new Logger1 r1.run() val messagesLogged = captor.getOutputAsString messagesLogged.contains(LoggerSpec.ErrorMsg) should be(true) messagesLogged.contains(LoggerSpec.WarnMsg) should be(true) messagesLogged.contains(LoggerSpec.InfoMsg) should be(false) messagesLogged.contains(LoggerSpec.DebugMsg) should be(false) messagesLogged.contains(LoggerSpec.TraceMsg) should be(false) } } "only warn and error shows up by default" in { Logger.makeScope() { val captor = new OutputCaptor Logger.setOutput(captor.printStream) val r1 = new Logger1 r1.run() val messagesLogged = captor.getOutputAsString messagesLogged.contains(LoggerSpec.ErrorMsg) should be(true) messagesLogged.contains(LoggerSpec.WarnMsg) should be(LogLevel.Warn <= LoggerSpec.globalLevel) messagesLogged.contains(LoggerSpec.InfoMsg) should be(LogLevel.Info <= LoggerSpec.globalLevel) messagesLogged.contains(LoggerSpec.DebugMsg) should be(LogLevel.Debug <= LoggerSpec.globalLevel) messagesLogged.contains(LoggerSpec.TraceMsg) should be(LogLevel.Trace <= LoggerSpec.globalLevel) } } "setting level to warn will result in error and warn messages" in { Logger.makeScope() { val captor = new OutputCaptor Logger.setOutput(captor.printStream) Logger.setLevel(LogLevel.Warn) val r1 = new Logger1 r1.run() val messagesLogged = captor.getOutputAsString messagesLogged.contains(LoggerSpec.ErrorMsg) should be(true) messagesLogged.contains(LoggerSpec.WarnMsg) should be(true) messagesLogged.contains(LoggerSpec.InfoMsg) should be(false) messagesLogged.contains(LoggerSpec.DebugMsg) should be(false) } } "setting level to info will result in error, info, and warn messages" in { Logger.makeScope() { val captor = new OutputCaptor Logger.setOutput(captor.printStream) Logger.setLevel(LogLevel.Info) val r1 = new Logger1 r1.run() val messagesLogged = captor.getOutputAsString messagesLogged.contains(LoggerSpec.ErrorMsg) should be(true) messagesLogged.contains(LoggerSpec.WarnMsg) should be(true) messagesLogged.contains(LoggerSpec.InfoMsg) should be(true) messagesLogged.contains(LoggerSpec.DebugMsg) should be(false) } } "setting level to debug will result in error, info, debug, and warn messages" in { Logger.makeScope() { val captor = new OutputCaptor Logger.setOutput(captor.printStream) Logger.setLevel(LogLevel.Error) Logger.setOutput(captor.printStream) Logger.setLevel(LogLevel.Debug) val r1 = new Logger1 r1.run() val messagesLogged = captor.getOutputAsString messagesLogged.contains(LoggerSpec.ErrorMsg) should be(true) messagesLogged.contains(LoggerSpec.WarnMsg) should be(true) messagesLogged.contains(LoggerSpec.InfoMsg) should be(true) messagesLogged.contains(LoggerSpec.DebugMsg) should be(true) messagesLogged.contains(LoggerSpec.TraceMsg) should be(false) } } "setting level to trace will result in error, info, debug, trace, and warn messages" in { Logger.makeScope() { val captor = new OutputCaptor Logger.setOutput(captor.printStream) Logger.setLevel(LogLevel.Error) Logger.setOutput(captor.printStream) Logger.setLevel(LogLevel.Trace) val r1 = new Logger1 r1.run() val messagesLogged = captor.getOutputAsString messagesLogged.contains(LoggerSpec.ErrorMsg) should be(true) messagesLogged.contains(LoggerSpec.WarnMsg) should be(true) messagesLogged.contains(LoggerSpec.InfoMsg) should be(true) messagesLogged.contains(LoggerSpec.DebugMsg) should be(true) messagesLogged.contains(LoggerSpec.TraceMsg) should be(true) } } } "the following tests show how logging can be controlled by package and class name" - { "only capture output by class name" - { "capture logging from LogsInfo2" in { Logger.makeScope() { val captor = new OutputCaptor Logger.setOutput(captor.printStream) Logger.setLevel("loggertests.LogsInfo2", LogLevel.Info) val r2 = new LogsInfo2 val r3 = new LogsInfo3 r3.run() r2.run() val messagesLogged = captor.getOutputAsString messagesLogged.contains("logger3") should be(false) messagesLogged.contains("logger2") should be(true) } } "capture logging from LogsInfo2 using class" in { Logger.makeScope() { val captor = new OutputCaptor Logger.setOutput(captor.printStream) Logger.setLevel(classOf[LogsInfo2], LogLevel.Info) val r2 = new LogsInfo2 val r3 = new LogsInfo3 r3.run() r2.run() val messagesLogged = captor.getOutputAsString messagesLogged.contains("logger3") should be(false) messagesLogged.contains("logger2") should be(true) } } "capture logging from LogsInfo3" in { Logger.makeScope() { val captor = new OutputCaptor Logger.setOutput(captor.printStream) Logger.setLevel("loggertests.LogsInfo3", LogLevel.Info) val r2 = new LogsInfo2 val r3 = new LogsInfo3 r2.run() r3.run() val messagesLogged = captor.getOutputAsString messagesLogged.contains("logger2") should be(false) messagesLogged.contains("logger3") should be(true) } } } "log based on package name" - { "both log because of package, also showing re-run after change works" in { Logger.makeScope() { val captor = new OutputCaptor Logger.setOutput(captor.printStream) Logger.setLevel(LogLevel.Error) Logger.setLevel("loggertests", LogLevel.Error) val r2 = new LogsInfo2 val r3 = new LogsInfo3 r2.run() r3.run() var messagesLogged = captor.getOutputAsString messagesLogged.contains("logger2") should be(false) messagesLogged.contains("logger3") should be(false) Logger.setLevel("loggertests", LogLevel.Debug) r2.run() r3.run() messagesLogged = captor.getOutputAsString messagesLogged.contains("logger2") should be(true) messagesLogged.contains("logger3") should be(true) } } } "check for false positives" in { Logger.makeScope() { val captor = new OutputCaptor Logger.setOutput(captor.printStream) Logger.setLevel("bad-loggertests", LogLevel.Info) val r2 = new LogsInfo2 val r3 = new LogsInfo3 r2.run() r3.run() val messagesLogged = captor.getOutputAsString messagesLogged.contains("logger2") should be(false) messagesLogged.contains("logger3") should be(false) } } "show that class specific level supercedes global level" in { Logger.makeScope() { val captor = new OutputCaptor Logger.setOutput(captor.printStream) Logger.setLevel(LogLevel.Info) Logger.setLevel("loggertests.LogsInfo2", LogLevel.Error) val r2 = new LogsInfo2 val r3 = new LogsInfo3 r2.run() r3.run() val messagesLogged = captor.getOutputAsString messagesLogged.contains("logger2") should be(false) messagesLogged.contains("logger3") should be(true) } } "Show that printstream remains across makeScopes" in { Logger.makeScope() { val captor = new Logger.OutputCaptor Logger.setOutput(captor.printStream) logger.error("message 1") Logger.makeScope() { logger.error("message 2") } val logText = captor.getOutputAsString logText should include("message 1") logText should include("message 2") } } "Show that nested makeScopes share same state" in { Logger.getGlobalLevel should be(LoggerSpec.globalLevel) Logger.makeScope() { Logger.setLevel(LogLevel.Info) Logger.getGlobalLevel should be(LogLevel.Info) Logger.makeScope() { Logger.getGlobalLevel should be(LogLevel.Info) } Logger.makeScope() { Logger.setLevel(LogLevel.Debug) Logger.getGlobalLevel should be(LogLevel.Debug) } Logger.getGlobalLevel should be(LogLevel.Debug) } Logger.getGlobalLevel should be(LoggerSpec.globalLevel) } "Show that first makeScope starts with fresh state" in { Logger.getGlobalLevel should be(LoggerSpec.globalLevel) Logger.setLevel(LogLevel.Warn) Logger.getGlobalLevel should be(LogLevel.Warn) Logger.makeScope() { Logger.getGlobalLevel should be(LoggerSpec.globalLevel) Logger.setLevel(LogLevel.Trace) Logger.getGlobalLevel should be(LogLevel.Trace) } Logger.getGlobalLevel should be(LogLevel.Warn) } } } }