diff --git a/akka-actor-testkit-typed/src/main/resources/reference.conf b/akka-actor-testkit-typed/src/main/resources/reference.conf index c0e2ca1742..c25fdacfc5 100644 --- a/akka-actor-testkit-typed/src/main/resources/reference.conf +++ b/akka-actor-testkit-typed/src/main/resources/reference.conf @@ -6,26 +6,32 @@ # Make your edits/overrides in your application.conf. akka.actor.testkit.typed { - # factor by which to scale timeouts during tests, e.g. to account for shared - # build system load + # Factor by which to scale timeouts during tests, e.g. to account for shared + # build system load. timefactor = 1.0 - # duration to wait in expectMsg and friends outside of within() block - # by default, will be dilated by the timefactor. + # Duration to wait in expectMsg and friends outside of within() block + # by default. + # Dilated by the timefactor. single-expect-default = 3s - # duration to wait in expectNoMessage by default, - # will be dilated by the timefactor. + # Duration to wait in expectNoMessage by default. + # Dilated by the timefactor. expect-no-message-default = 100ms - # The timeout that is added as an implicit by DefaultTimeout trait, will be dilated by the timefactor. + # The timeout that is used as an implicit Timeout. + # Dilated by the timefactor. default-timeout = 5s - # Default timeout for shutting down the actor system (used when no explicit timeout specified), - # will be dilated by the timefactor. + # Default timeout for shutting down the actor system (used when no explicit timeout specified). + # Dilated by the timefactor. system-shutdown-default=10s # Throw an exception on shutdown if the timeout is hit, if false an error is printed to stdout instead. throw-on-shutdown-timeout=true + # Duration to wait for all required logging events in LoggingEventFilter.intercept. + # Dilated by the timefactor. + filter-leeway = 3s + } diff --git a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/CapturedLogEvent.scala b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/CapturedLogEvent.scala index 10fa9396d2..6d75daffcc 100644 --- a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/CapturedLogEvent.scala +++ b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/CapturedLogEvent.scala @@ -6,69 +6,63 @@ package akka.actor.testkit.typed import java.util.Optional -import akka.actor.typed.LogMarker import akka.annotation.InternalApi -import akka.event.Logging.LogLevel import akka.util.OptionVal - -import akka.util.ccompat.JavaConverters._ import scala.compat.java8.OptionConverters._ +import org.slf4j.Marker +import org.slf4j.event.Level + /** * Representation of a Log Event issued by a [[akka.actor.typed.Behavior]] + * when testing with [[akka.actor.testkit.typed.scaladsl.BehaviorTestKit`]] + * or [[akka.actor.testkit.typed.javadsl.BehaviorTestKit`]]. */ -final case class CapturedLogEvent( - logLevel: LogLevel, - message: String, - cause: Option[Throwable], - marker: Option[LogMarker], - mdc: Map[String, Any]) { +final case class CapturedLogEvent(level: Level, message: String, cause: Option[Throwable], marker: Option[Marker]) { /** * Constructor for Java API */ def this( - logLevel: LogLevel, + level: Level, message: String, errorCause: Optional[Throwable], - marker: Optional[LogMarker], + marker: Optional[Marker], mdc: java.util.Map[String, Any]) { - this(logLevel, message, errorCause.asScala, marker.asScala, mdc.asScala.toMap) + this(level, message, errorCause.asScala, marker.asScala) } /** * Constructor for Java API */ - def this(logLevel: LogLevel, message: String) { - this(logLevel, message, Option.empty, Option.empty, Map.empty[String, Any]) + def this(level: Level, message: String) { + this(level, message, Option.empty, Option.empty) } /** * Constructor for Java API */ - def this(logLevel: LogLevel, message: String, errorCause: Throwable) { - this(logLevel, message, Some(errorCause), Option.empty[LogMarker], Map.empty[String, Any]) + def this(level: Level, message: String, errorCause: Throwable) { + this(level, message, Some(errorCause), Option.empty[Marker]) } /** * Constructor for Java API */ - def this(logLevel: LogLevel, message: String, marker: LogMarker) { - this(logLevel, message, Option.empty[Throwable], Some(marker), Map.empty[String, Any]) + def this(level: Level, message: String, marker: Marker) { + this(level, message, Option.empty[Throwable], Some(marker)) } /** * Constructor for Java API */ - def this(logLevel: LogLevel, message: String, errorCause: Throwable, marker: LogMarker) { - this(logLevel, message, Some(errorCause), Some(marker), Map.empty[String, Any]) + def this(level: Level, message: String, errorCause: Throwable, marker: Marker) { + this(level, message, Some(errorCause), Some(marker)) } - def getMdc: java.util.Map[String, Any] = mdc.asJava - def getErrorCause: Optional[Throwable] = cause.asJava - def getLogMarker: Optional[LogMarker] = marker.asJava + def getMarker: Optional[Marker] = marker.asJava } object CapturedLogEvent { @@ -81,8 +75,8 @@ object CapturedLogEvent { case _ => None } - def apply(logLevel: LogLevel, message: String): CapturedLogEvent = { - CapturedLogEvent(logLevel, message, None, None, Map.empty[String, Any]) + def apply(level: Level, message: String): CapturedLogEvent = { + CapturedLogEvent(level, message, None, None) } /** @@ -91,11 +85,10 @@ object CapturedLogEvent { */ @InternalApi private[akka] def apply( - logLevel: LogLevel, + level: Level, message: String, errorCause: OptionVal[Throwable], - logMarker: OptionVal[LogMarker], - mdc: Map[String, Any]): CapturedLogEvent = { - new CapturedLogEvent(logLevel, message, toOption(errorCause), toOption(logMarker), mdc) + logMarker: OptionVal[Marker]): CapturedLogEvent = { + new CapturedLogEvent(level, message, toOption(errorCause), toOption(logMarker)) } } diff --git a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/LoggingEvent.scala b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/LoggingEvent.scala new file mode 100644 index 0000000000..5cdbfb9082 --- /dev/null +++ b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/LoggingEvent.scala @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2019 Lightbend Inc. + */ + +package akka.actor.testkit.typed + +import scala.compat.java8.OptionConverters._ +import akka.util.ccompat.JavaConverters._ + +import java.util.Optional + +import org.slf4j.Marker +import org.slf4j.event.Level + +object LoggingEvent { + + /** + * Scala API + */ + def apply(level: Level, loggerName: String, threadName: String, message: String, timeStamp: Long): LoggingEvent = + new LoggingEvent(level, loggerName, threadName, message, timeStamp, None, None, Map.empty) + + /** + * Java API + */ + def create(level: Level, loggerName: String, threadName: String, message: String, timeStamp: Long): LoggingEvent = + apply(level, loggerName, threadName, message, timeStamp) + + /** + * Java API + */ + def create( + level: Level, + loggerName: String, + threadName: String, + message: String, + timeStamp: Long, + marker: Optional[Marker], + throwable: Optional[Throwable], + mdc: java.util.Map[String, String]) = + apply(level, loggerName, threadName, message, timeStamp, marker.asScala, throwable.asScala, mdc.asScala.toMap) +} + +/** + * Representation of logging event when testing with [[akka.actor.testkit.typed.scaladsl.LoggingEventFilter]] + * or [[akka.actor.testkit.typed.javadsl.LoggingEventFilter]]. + */ +final case class LoggingEvent( + level: Level, + loggerName: String, + threadName: String, + message: String, + timeStamp: Long, + marker: Option[Marker], + throwable: Option[Throwable], + mdc: Map[String, String]) { + + /** + * Java API + */ + def getMarker: Optional[Marker] = + marker.asJava + + /** + * Java API + */ + def getThrowable: Optional[Throwable] = + throwable.asJava + + /** + * Java API + */ + def getMdc: java.util.Map[String, String] = { + import akka.util.ccompat.JavaConverters._ + mdc.asJava + } + +} diff --git a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/TestKitSettings.scala b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/TestKitSettings.scala index a9f9a427ae..a2d103dc7d 100644 --- a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/TestKitSettings.scala +++ b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/TestKitSettings.scala @@ -5,11 +5,13 @@ package akka.actor.testkit.typed import com.typesafe.config.Config - import scala.concurrent.duration.{ Duration, FiniteDuration } + import akka.util.JavaDurationConverters._ import akka.util.Timeout import akka.actor.typed.ActorSystem +import akka.actor.typed.Extension +import akka.actor.typed.ExtensionId object TestKitSettings { @@ -17,7 +19,7 @@ object TestKitSettings { * Reads configuration settings from `akka.actor.testkit.typed` section. */ def apply(system: ActorSystem[_]): TestKitSettings = - apply(system.settings.config.getConfig("akka.actor.testkit.typed")) + Ext(system).settings /** * Reads configuration settings from given `Config` that @@ -38,30 +40,42 @@ object TestKitSettings { */ def create(config: Config): TestKitSettings = new TestKitSettings(config) + + private object Ext extends ExtensionId[Ext] { + override def createExtension(system: ActorSystem[_]): Ext = new Ext(system) + def get(system: ActorSystem[_]): Ext = apply(system) + } + + private class Ext(system: ActorSystem[_]) extends Extension { + val settings: TestKitSettings = TestKitSettings(system.settings.config.getConfig("akka.actor.testkit.typed")) + } } final class TestKitSettings(val config: Config) { import akka.util.Helpers._ - val TestTimeFactor = config + val TestTimeFactor: Double = config .getDouble("timefactor") .requiring(tf => !tf.isInfinite && tf > 0, "timefactor must be positive finite double") - /** dilated with `TestTimeFactor` */ + /** Dilated with `TestTimeFactor`. */ val SingleExpectDefaultTimeout: FiniteDuration = dilated(config.getMillisDuration("single-expect-default")) - /** dilated with `TestTimeFactor` */ + /** Dilated with `TestTimeFactor`. */ val ExpectNoMessageDefaultTimeout: FiniteDuration = dilated(config.getMillisDuration("expect-no-message-default")) - /** dilated with `TestTimeFactor` */ + /** Dilated with `TestTimeFactor`. */ val DefaultTimeout: Timeout = Timeout(dilated(config.getMillisDuration("default-timeout"))) - /** dilated with `TestTimeFactor` */ + /** Dilated with `TestTimeFactor`. */ val DefaultActorSystemShutdownTimeout: FiniteDuration = dilated(config.getMillisDuration("system-shutdown-default")) val ThrowOnShutdownTimeout: Boolean = config.getBoolean("throw-on-shutdown-timeout") + /** Dilated with `TestTimeFactor`. */ + val FilterLeeway: FiniteDuration = dilated(config.getMillisDuration("filter-leeway")) + /** * Scala API: Scale the `duration` with the configured `TestTimeFactor` */ diff --git a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/ActorSystemStub.scala b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/ActorSystemStub.scala index 4b7e481dd5..ce67a6da03 100644 --- a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/ActorSystemStub.scala +++ b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/ActorSystemStub.scala @@ -21,14 +21,15 @@ import akka.annotation.InternalApi import akka.{ actor => classic } import akka.Done import com.typesafe.config.ConfigFactory - import scala.compat.java8.FutureConverters import scala.concurrent._ + import akka.actor.ActorRefProvider +import akka.actor.ReflectiveDynamicAccess import akka.actor.typed.internal.InternalRecipientRef import com.github.ghik.silencer.silent import org.slf4j.Logger -import org.slf4j.helpers.SubstituteLoggerFactory +import org.slf4j.LoggerFactory /** * INTERNAL API @@ -42,7 +43,14 @@ import org.slf4j.helpers.SubstituteLoggerFactory override val path: classic.ActorPath = classic.RootActorPath(classic.Address("akka", name)) / "user" - override val settings: Settings = new Settings(getClass.getClassLoader, ConfigFactory.empty, name) + override val settings: Settings = { + val classLoader = getClass.getClassLoader + val dynamicAccess = new ReflectiveDynamicAccess(classLoader) + val config = + classic.ActorSystem.Settings.amendSlf4jConfig(ConfigFactory.defaultReference(classLoader), dynamicAccess) + val untypedSettings = new classic.ActorSystem.Settings(classLoader, config, name) + new Settings(untypedSettings) + } override def tell(message: Nothing): Unit = throw new UnsupportedOperationException("must not send message to ActorSystemStub") @@ -104,7 +112,5 @@ import org.slf4j.helpers.SubstituteLoggerFactory override def hasExtension(ext: ExtensionId[_ <: Extension]): Boolean = throw new UnsupportedOperationException("ActorSystemStub cannot register extensions") - val loggerFactory = new SubstituteLoggerFactory() - - override def log: Logger = loggerFactory.getLogger("StubbedLogger") + override def log: Logger = LoggerFactory.getLogger(getClass) } diff --git a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/BehaviorTestKitImpl.scala b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/BehaviorTestKitImpl.scala index c5f84600e4..dd215a24b7 100644 --- a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/BehaviorTestKitImpl.scala +++ b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/BehaviorTestKitImpl.scala @@ -141,7 +141,7 @@ private[akka] final class BehaviorTestKitImpl[T](_path: ActorPath, _initialBehav override def getAllLogEntries(): util.List[CapturedLogEvent] = logEntries().asJava - override def logEntries(): immutable.Seq[CapturedLogEvent] = Nil + override def logEntries(): immutable.Seq[CapturedLogEvent] = context.logEntries override def clearLog(): Unit = context.clearLog() } diff --git a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/CapturingAppender.scala b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/CapturingAppender.scala new file mode 100644 index 0000000000..be3ac0c39f --- /dev/null +++ b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/CapturingAppender.scala @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2019 Lightbend Inc. + */ + +package akka.actor.testkit.typed.internal + +import akka.annotation.InternalApi +import ch.qos.logback.classic.spi.ILoggingEvent +import ch.qos.logback.core.AppenderBase + +/** + * INTERNAL API + */ +@InternalApi private[akka] object CapturingAppender { + import LogbackUtil._ + + private val CapturingAppenderName = "CapturingAppender" + + def get(loggerName: String): CapturingAppender = { + val logbackLogger = getLogbackLogger(loggerName) + logbackLogger.getAppender(CapturingAppenderName) match { + case null => + throw new IllegalStateException( + s"$CapturingAppenderName not defined for [${loggerNameOrRoot(loggerName)}] in logback-test.xml") + case appender: CapturingAppender => appender + case other => + throw new IllegalStateException(s"Unexpected $CapturingAppender: $other") + } + } + +} + +/** + * INTERNAL API + * + * Logging from tests can be silenced by this appender. When there is a test failure + * the captured logging events are flushed to the appenders defined for the + * akka.actor.testkit.typed.internal.CapturingAppenderDelegate logger. + * + * The flushing on test failure is handled by [[akka.actor.testkit.typed.scaladsl.LogCapturing]] + * for ScalaTest and [[akka.actor.testkit.typed.javadsl.LogCapturing]] for JUnit. + * + * Use configuration like the following the logback-test.xml: + * + * {{{ + * + * + * + * + * + * + * + * + * + * }}} + */ +@InternalApi private[akka] class CapturingAppender extends AppenderBase[ILoggingEvent] { + import LogbackUtil._ + + private var buffer: Vector[ILoggingEvent] = Vector.empty + + // invocations are synchronized via doAppend in AppenderBase + override def append(event: ILoggingEvent): Unit = { + event.prepareForDeferredProcessing() + buffer :+= event + } + + /** + * Flush buffered logging events to the output appenders + * Also clears the buffer.. + */ + def flush(): Unit = synchronized { + import akka.util.ccompat.JavaConverters._ + val logbackLogger = getLogbackLogger(classOf[CapturingAppender].getName + "Delegate") + val appenders = logbackLogger.iteratorForAppenders().asScala.filterNot(_ == this).toList + for (event <- buffer; appender <- appenders) { + appender.doAppend(event) + } + clear() + } + + /** + * Discards the buffered logging events without output. + */ + def clear(): Unit = synchronized { + buffer = Vector.empty + } + +} diff --git a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/LogbackUtil.scala b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/LogbackUtil.scala new file mode 100644 index 0000000000..6f28238210 --- /dev/null +++ b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/LogbackUtil.scala @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2019 Lightbend Inc. + */ + +package akka.actor.testkit.typed.internal + +import akka.annotation.InternalApi +import org.slf4j.LoggerFactory +import org.slf4j.event.Level + +/** + * INTERNAL API + */ +@InternalApi private[akka] object LogbackUtil { + def loggerNameOrRoot(loggerName: String): String = + if (loggerName == "") org.slf4j.Logger.ROOT_LOGGER_NAME else loggerName + + def getLogbackLogger(loggerName: String): ch.qos.logback.classic.Logger = { + LoggerFactory.getLogger(loggerNameOrRoot(loggerName)) match { + case logger: ch.qos.logback.classic.Logger => logger + case null => + throw new IllegalArgumentException(s"Couldn't find logger for [$loggerName].") + case other => + throw new IllegalArgumentException( + s"Requires Logback logger for [$loggerName], it was a [${other.getClass.getName}]") + } + } + + def convertLevel(level: ch.qos.logback.classic.Level): Level = { + level.levelInt match { + case ch.qos.logback.classic.Level.TRACE_INT => Level.TRACE + case ch.qos.logback.classic.Level.DEBUG_INT => Level.DEBUG + case ch.qos.logback.classic.Level.INFO_INT => Level.INFO + case ch.qos.logback.classic.Level.WARN_INT => Level.WARN + case ch.qos.logback.classic.Level.ERROR_INT => Level.ERROR + case _ => + throw new IllegalArgumentException("Level " + level.levelStr + ", " + level.levelInt + " is unknown.") + } + } +} diff --git a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/LoggingEventFilterImpl.scala b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/LoggingEventFilterImpl.scala new file mode 100644 index 0000000000..4c2c96b9d2 --- /dev/null +++ b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/LoggingEventFilterImpl.scala @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2019 Lightbend Inc. + */ + +package akka.actor.testkit.typed.internal + +import java.util.function.Supplier + +import scala.concurrent.duration.Duration +import scala.reflect.ClassTag +import scala.util.matching.Regex + +import akka.actor.testkit.typed.LoggingEvent +import akka.actor.testkit.typed.TestKitSettings +import akka.actor.testkit.typed.javadsl +import akka.actor.testkit.typed.scaladsl +import akka.actor.typed.ActorSystem +import akka.annotation.InternalApi +import akka.testkit.TestKit +import org.slf4j.event.Level + +/** + * INTERNAL API + */ +@InternalApi private[akka] object LoggingEventFilterImpl { + def empty: LoggingEventFilterImpl = new LoggingEventFilterImpl(1, None, None, None, None, None, None, Map.empty, None) +} + +/** + * INTERNAL API + */ +@InternalApi private[akka] final case class LoggingEventFilterImpl( + occurrences: Int, + logLevel: Option[Level], + loggerName: Option[String], + source: Option[String], + messageContains: Option[String], + messageRegex: Option[Regex], + cause: Option[Class[_ <: Throwable]], + mdc: Map[String, String], + custom: Option[Function[LoggingEvent, Boolean]]) + extends javadsl.LoggingEventFilter + with scaladsl.LoggingEventFilter { + + @volatile // JMM does not guarantee visibility for non-final fields + private var todo = occurrences + + def matches(event: LoggingEvent): Boolean = { + logLevel.forall(_ == event.level) && + source.forall(_ == sourceOrEmpty(event)) && + messageContains.forall(messageOrEmpty(event).contains) && + messageRegex.forall(_.findFirstIn(messageOrEmpty(event)).isDefined) && + cause.forall(c => event.throwable.isDefined && c.isInstance(event.throwable.get)) && + mdc.forall { case (key, value) => event.mdc.contains(key) && event.mdc(key) == value } && + custom.forall(f => f(event)) + + // loggerName is handled when installing the filter, in `intercept` + } + + private def messageOrEmpty(event: LoggingEvent): String = + if (event.message == null) "" else event.message + + private def sourceOrEmpty(event: LoggingEvent): String = + event.mdc.getOrElse("akkaSource", "") + + def apply(event: LoggingEvent): Boolean = { + if (matches(event)) { + if (todo != Int.MaxValue) todo -= 1 + true + } else false + } + + private def awaitDone(max: Duration): Boolean = { + if (todo != Int.MaxValue && todo > 0) TestKit.awaitCond(todo <= 0, max, noThrow = true) + todo == Int.MaxValue || todo == 0 + } + + override def intercept[T](code: => T)(implicit system: ActorSystem[_]): T = { + val effectiveLoggerName = loggerName.getOrElse("") + checkLogback(system) + TestAppender.setupTestAppender(effectiveLoggerName) + TestAppender.addFilter(effectiveLoggerName, this) + val leeway = TestKitSettings(system).FilterLeeway + try { + val result = code + if (!awaitDone(leeway)) + if (todo > 0) + throw new AssertionError(s"Timeout ($leeway) waiting for $todo messages on $this.") + else + throw new AssertionError(s"Received ${-todo} excess messages on $this.") + result + } finally { + todo = occurrences + TestAppender.removeFilter(effectiveLoggerName, this) + } + } + + private def checkLogback(system: ActorSystem[_]): Unit = { + if (!system.dynamicAccess.classIsOnClasspath("ch.qos.logback.classic.spi.ILoggingEvent")) { + throw new IllegalStateException("LoggingEventFilter requires logback-classic dependency in classpath.") + } + } + + override def withOccurrences(newOccurrences: Int): LoggingEventFilterImpl = + copy(occurrences = newOccurrences) + + override def withLogLevel(newLogLevel: Level): LoggingEventFilterImpl = + copy(logLevel = Option(newLogLevel)) + + def withLoggerName(newLoggerName: String): LoggingEventFilterImpl = + copy(loggerName = Some(newLoggerName)) + + override def withSource(newSource: String): LoggingEventFilterImpl = + copy(source = Option(newSource)) + + override def withMessageContains(newMessageContains: String): LoggingEventFilterImpl = + copy(messageContains = Option(newMessageContains)) + + def withMessageRegex(newMessageRegex: String): LoggingEventFilterImpl = + copy(messageRegex = Option(new Regex(newMessageRegex))) + + override def withCause[A <: Throwable: ClassTag]: LoggingEventFilterImpl = { + val causeClass = implicitly[ClassTag[A]].runtimeClass.asInstanceOf[Class[Throwable]] + copy(cause = Option(causeClass)) + } + + override def withMdc(newMdc: Map[String, String]): LoggingEventFilterImpl = + copy(mdc = newMdc) + + override def withMdc(newMdc: java.util.Map[String, String]): javadsl.LoggingEventFilter = { + import akka.util.ccompat.JavaConverters._ + withMdc(newMdc.asScala.toMap) + } + + override def withCustom(newCustom: Function[LoggingEvent, Boolean]): LoggingEventFilterImpl = + copy(custom = Option(newCustom)) + + override def withCause(newCause: Class[_ <: Throwable]): javadsl.LoggingEventFilter = + copy(cause = Option(newCause)) + + override def intercept[T](system: ActorSystem[_], code: Supplier[T]): T = + intercept(code.get())(system) + +} diff --git a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/StubbedActorContext.scala b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/StubbedActorContext.scala index 9070928599..7316e013df 100644 --- a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/StubbedActorContext.scala +++ b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/StubbedActorContext.scala @@ -17,9 +17,11 @@ import java.util.concurrent.ThreadLocalRandom.{ current => rnd } import scala.collection.immutable.TreeMap import scala.concurrent.ExecutionContextExecutor import scala.concurrent.duration.FiniteDuration + import akka.actor.ActorRefProvider import org.slf4j.Logger -import org.slf4j.helpers.{ SubstituteLogger, SubstituteLoggerFactory } +import org.slf4j.helpers.MessageFormatter +import org.slf4j.helpers.SubstituteLoggerFactory /** * INTERNAL API @@ -76,8 +78,8 @@ private[akka] final class FunctionRef[-T](override val path: ActorPath, send: (T override val system = new ActorSystemStub("StubbedActorContext") private var _children = TreeMap.empty[String, BehaviorTestKitImpl[_]] private val childName = Iterator.from(0).map(Helpers.base64(_)) - private val loggingAdapter: SubstituteLogger = - new SubstituteLoggerFactory().getLogger("StubbedLoggingAdapter").asInstanceOf[SubstituteLogger] + private val substituteLoggerFactory = new SubstituteLoggerFactory + private val logger: Logger = substituteLoggerFactory.getLogger("StubbedLogger") private var unhandled: List[T] = Nil private[akka] def classicActorContext = @@ -183,20 +185,36 @@ private[akka] final class FunctionRef[-T](override val path: ActorPath, send: (T override def toString: String = s"Inbox($self)" - override def log: Logger = loggingAdapter + override def log: Logger = logger - override def setLoggerClass(clazz: Class[_]): Unit = () // nop as we dont track logger class + override def setLoggerName(name: String): Unit = () // nop as we don't track logger + + override def setLoggerName(clazz: Class[_]): Unit = () // nop as we don't track logger /** * The log entries logged through context.log.{debug, info, warn, error} are captured and can be inspected through * this method. */ - def logEntries: List[CapturedLogEvent] = ??? + def logEntries: List[CapturedLogEvent] = { + import akka.util.ccompat.JavaConverters._ + substituteLoggerFactory.getEventQueue + .iterator() + .asScala + .map { evt => + CapturedLogEvent( + level = evt.getLevel, + message = MessageFormatter.arrayFormat(evt.getMessage, evt.getArgumentArray).getMessage, + cause = Option(evt.getThrowable), + marker = Option(evt.getMarker)) + } + .toList + } /** * Clear the log entries. */ - def clearLog(): Unit = ??? + def clearLog(): Unit = + substituteLoggerFactory.getEventQueue.clear() override private[akka] def onUnhandled(msg: T): Unit = unhandled = msg :: unhandled diff --git a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/TestAppender.scala b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/TestAppender.scala new file mode 100644 index 0000000000..b589c9224f --- /dev/null +++ b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/TestAppender.scala @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2019 Lightbend Inc. + */ + +package akka.actor.testkit.typed.internal + +import akka.actor.testkit.typed.LoggingEvent +import akka.annotation.InternalApi +import ch.qos.logback.classic.spi.ILoggingEvent +import ch.qos.logback.classic.spi.ThrowableProxy +import ch.qos.logback.core.AppenderBase + +/** + * INTERNAL API + * + * The `TestAppender` emits the logging events to the registered [[LoggingEventFilterImpl]], which + * are added and removed to the appender dynamically from tests. + * + * `TestAppender` is currently requiring Logback as SLF4J implementation. + * Similar can probably be implemented with other backends, such as Log4j2. + */ +@InternalApi private[akka] object TestAppender { + import LogbackUtil._ + + private val TestAppenderName = "AkkaTestAppender" + + def setupTestAppender(loggerName: String): Unit = { + val logbackLogger = getLogbackLogger(loggerName) + logbackLogger.getAppender(TestAppenderName) match { + case null => + val testAppender = new TestAppender + testAppender.setName(TestAppenderName) + testAppender.setContext(logbackLogger.getLoggerContext) + testAppender.start() + logbackLogger.addAppender(testAppender) + case _: TestAppender => + // ok, already setup + case other => + throw new IllegalStateException(s"Unexpected $TestAppenderName already added: $other") + } + } + + def addFilter(loggerName: String, filter: LoggingEventFilterImpl): Unit = + getTestAppender(loggerName).addTestFilter(filter) + + def removeFilter(loggerName: String, filter: LoggingEventFilterImpl): Unit = + getTestAppender(loggerName).removeTestFilter(filter) + + private def getTestAppender(loggerName: String): TestAppender = { + val logger = getLogbackLogger(loggerName) + logger.getAppender(TestAppenderName) match { + case testAppender: TestAppender => testAppender + case null => + throw new IllegalStateException(s"No $TestAppenderName was setup for logger [${logger.getName}]") + case other => + throw new IllegalStateException( + s"Unexpected $TestAppenderName already added for logger [${logger.getName}]: $other") + } + } +} + +/** + * INTERNAL API + */ +@InternalApi private[akka] class TestAppender extends AppenderBase[ILoggingEvent] { + import LogbackUtil._ + + private var filters: List[LoggingEventFilterImpl] = Nil + + // invocations are synchronized via doAppend in AppenderBase + override def append(event: ILoggingEvent): Unit = { + import akka.util.ccompat.JavaConverters._ + + val throwable = event.getThrowableProxy match { + case p: ThrowableProxy => + Option(p.getThrowable) + case _ => None + } + + val loggingEvent = LoggingEvent( + level = convertLevel(event.getLevel), + message = event.getFormattedMessage, + loggerName = event.getLoggerName, + threadName = event.getThreadName, + timeStamp = event.getTimeStamp, + marker = Option(event.getMarker), + throwable = throwable, + mdc = event.getMDCPropertyMap.asScala.toMap) + + filter(loggingEvent) + } + + private def filter(event: LoggingEvent): Boolean = { + filters.exists(f => + try { + f.apply(event) + } catch { + case _: Exception => false + }) + } + + def addTestFilter(filter: LoggingEventFilterImpl): Unit = synchronized { + filters ::= filter + } + + def removeTestFilter(filter: LoggingEventFilterImpl): Unit = synchronized { + @scala.annotation.tailrec + def removeFirst( + list: List[LoggingEventFilterImpl], + zipped: List[LoggingEventFilterImpl] = Nil): List[LoggingEventFilterImpl] = + list match { + case head :: tail if head == filter => tail.reverse_:::(zipped) + case head :: tail => removeFirst(tail, head :: zipped) + case Nil => filters // filter not found, just return original list + } + filters = removeFirst(filters) + } + +} diff --git a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/javadsl/LogCapturing.scala b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/javadsl/LogCapturing.scala new file mode 100644 index 0000000000..c65ddbe562 --- /dev/null +++ b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/javadsl/LogCapturing.scala @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2019 Lightbend Inc. + */ + +package akka.actor.testkit.typed.javadsl + +import scala.util.control.NonFatal + +import akka.actor.testkit.typed.internal.CapturingAppender +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement +import org.slf4j.LoggerFactory + +/** + * JUnit `TestRule` to make log lines appear only when the test failed. + * + * Use this in test by adding a public field annotated with `@TestRule`: + * {{{ + * @Rule public final LogCapturing logCapturing = new LogCapturing(); + * }}} + * + * Requires Logback and configuration like the following the logback-test.xml: + * + * {{{ + * + * + * + * + * + * + * + * + * + * }}} + */ +final class LogCapturing extends TestRule { + // eager access of CapturingAppender to fail fast if misconfigured + private val capturingAppender = CapturingAppender.get("") + + private val myLogger = LoggerFactory.getLogger(classOf[LogCapturing]) + + override def apply(base: Statement, description: Description): Statement = { + new Statement { + override def evaluate(): Unit = { + try { + myLogger.info(s"Logging started for test [${description.getClassName}: ${description.getMethodName}]") + base.evaluate() + myLogger.info( + s"Logging finished for test [${description.getClassName}: ${description.getMethodName}] that was successful") + } catch { + case NonFatal(e) => + println( + s"--> [${Console.BLUE}${description.getClassName}: ${description.getMethodName}${Console.RESET}] " + + s"Start of log messages of test that failed with ${e.getMessage}") + capturingAppender.flush() + println( + s"<-- [${Console.BLUE}${description.getClassName}: ${description.getMethodName}${Console.RESET}] " + + s"End of log messages of test that failed with ${e.getMessage}") + throw e + } finally { + capturingAppender.clear() + } + } + } + } +} diff --git a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/javadsl/LoggingEventFilter.scala b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/javadsl/LoggingEventFilter.scala new file mode 100644 index 0000000000..462bb1807f --- /dev/null +++ b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/javadsl/LoggingEventFilter.scala @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2019 Lightbend Inc. + */ + +package akka.actor.testkit.typed.javadsl + +import java.util.function.Supplier + +import akka.actor.testkit.typed.LoggingEvent +import akka.actor.testkit.typed.internal.LoggingEventFilterImpl +import akka.actor.typed.ActorSystem +import akka.annotation.DoNotInherit +import org.slf4j.event.Level + +/** + * Facilities for selectively filtering out expected events from logging + * to verify that they were logged. + * + * Requires Logback. + * + * See the static factory methods as starting point for creating `LoggingEventFilter`. + * + * Not for user extension. + */ +@DoNotInherit abstract class LoggingEventFilter { + + /** + * Number of events the filter is supposed to match. By default 1. + */ + def withOccurrences(newOccurrences: Int): LoggingEventFilter + + /** + * Matching events with the given log level. + */ + def withLogLevel(newLogLevel: Level): LoggingEventFilter + + /** + * Matching events with the given logger name or sub-names in the same way + * as configuration loggers are configured in logback.xml. + * By default the root logger is used. + */ + def withLoggerName(newLoggerName: String): LoggingEventFilter + + /** + * Matching events that have "akkaSource" MDC value equal to the given value. + * "akkaSource" is typically the actor path. + */ + def withSource(newSource: String): LoggingEventFilter + + /** + * Matching events with a message that contains the given value. + */ + def withMessageContains(newMessageContains: String): LoggingEventFilter + + /** + * Matching events with a message that matches the given regular expression. + */ + def withMessageRegex(newMessageRegex: String): LoggingEventFilter + + /** + * Matching events with an included `throwable` that is a class or subclass of the given + * `Throwable` class. + */ + def withCause(newCause: Class[_ <: Throwable]): LoggingEventFilter + + /** + * Matching events with MDC containing all entries of the given `Map`. + * The event MDC may have more entries than the given `Map`. + */ + def withMdc(newMdc: java.util.Map[String, String]): LoggingEventFilter + + /** + * Matching events for which the supplied function returns `true`. + */ + def withCustom(newCustom: Function[LoggingEvent, Boolean]): LoggingEventFilter + // this is a Scala Function, ^ but that can be used with lambda from Java + + /** + * @return `true` if the event matches the conditions of the filter. + */ + def matches(event: LoggingEvent): Boolean + + /** + * Apply this filter while executing the given code block. + * Assert that this filter has matched within the configured `akka.actor.testkit.typed.filter-leeway` + * as often as requested by its `occurrences` parameter specifies. + * + * Care is taken to remove the filter when the block is finished or aborted. + */ + def intercept[T](system: ActorSystem[_], code: Supplier[T]): T + +} + +/** + * Facilities for selectively matching expected events from logging. + * + * Requires Logback. + */ +object LoggingEventFilter { + + /** + * An empty filter that doesn't match any events. + * + * More conditions can be added to the returned [LoggingEventFilter]. + */ + def empty: LoggingEventFilter = LoggingEventFilterImpl.empty + + /** + * Create a filter for events with a log message + * that contains the given `messageIncludes`. + * + * More conditions can be added to the returned [LoggingEventFilter]. + */ + def messageContains(str: String): LoggingEventFilter = + empty.withMessageContains(str) + + /** + * Create a filter for TRACE level events with a log message + * that contains the given `messageIncludes`. + * + * More conditions can be added to the returned [LoggingEventFilter]. + */ + def trace(messageIncludes: String): LoggingEventFilter = + messageContains(messageIncludes).withLogLevel(Level.TRACE) + + /** + * Create a filter for DEBUG level events with a log message + * that contains the given `messageIncludes`. + * + * More conditions can be added to the returned [LoggingEventFilter]. + */ + def debug(messageIncludes: String): LoggingEventFilter = + messageContains(messageIncludes).withLogLevel(Level.DEBUG) + + /** + * Create a filter for INFO level events with a log message + * that contains the given `messageIncludes`. + * + * More conditions can be added to the returned [LoggingEventFilter]. + */ + def info(messageIncludes: String): LoggingEventFilter = + messageContains(messageIncludes).withLogLevel(Level.INFO) + + /** + * Create a filter for WARN level events with a log message + * that contains the given `messageIncludes`. + * + * More conditions can be added to the returned [LoggingEventFilter]. + */ + def warn(messageIncludes: String): LoggingEventFilter = + messageContains(messageIncludes).withLogLevel(Level.WARN) + + /** + * Create a filter for WARN level events with a an included + * `throwable` that is a class or subclass of the given + * * `Throwable` class. + * + * More conditions can be added to the returned [LoggingEventFilter]. + */ + def warn(causeClass: Class[Throwable]): LoggingEventFilter = + empty.withLogLevel(Level.WARN).withCause(causeClass) + + /** + * Create a filter for ERROR level events with a log message + * that contains the given `messageIncludes`. + * + * More conditions can be added to the returned [LoggingEventFilter]. + */ + def error(messageIncludes: String): LoggingEventFilter = + messageContains(messageIncludes).withLogLevel(Level.ERROR) + + /** + * Create a filter for WARN level events with a an included + * `throwable` that is a class or subclass of the given + * * `Throwable` class. + * + * More conditions can be added to the returned [LoggingEventFilter]. + */ + def error(causeClass: Class[_ <: Throwable]): LoggingEventFilter = + empty.withLogLevel(Level.ERROR).withCause(causeClass) + + /** + * Create a custom event filter. The filter will match those events for + * which for which the supplied function returns `true`. + */ + def custom(test: Function[LoggingEvent, Boolean]): LoggingEventFilter = + empty.withCustom(test) // this is a Scala Function, but that can be used with lambda from Java + + /** + * Filter for the logging of dead letters. + */ + def deadLetters(): LoggingEventFilter = + empty.withLogLevel(Level.INFO).withMessageRegex(".*was not delivered.*dead letters encountered.*") +} diff --git a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/scaladsl/LogCapturing.scala b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/scaladsl/LogCapturing.scala new file mode 100644 index 0000000000..47a0a965a3 --- /dev/null +++ b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/scaladsl/LogCapturing.scala @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ + +package akka.actor.testkit.typed.scaladsl + +import scala.util.control.NonFatal + +import akka.actor.testkit.typed.internal.CapturingAppender +import org.scalatest.BeforeAndAfterAll +import org.scalatest.Outcome +import org.scalatest.TestSuite +import org.slf4j.LoggerFactory + +/** + * Mixin this trait to a ScalaTest test to make log lines appear only when the test failed. + * + * Requires Logback and configuration like the following the logback-test.xml: + * + * {{{ + * + * + * + * + * + * + * + * + * + * }}} + */ +trait LogCapturing extends BeforeAndAfterAll { self: TestSuite => + + // eager access of CapturingAppender to fail fast if misconfigured + private val capturingAppender = CapturingAppender.get("") + + private val myLogger = LoggerFactory.getLogger(classOf[LogCapturing]) + + override protected def afterAll(): Unit = { + try { + super.afterAll() + } catch { + case NonFatal(e) => + myLogger.error("Exception from afterAll", e) + capturingAppender.flush() + } finally { + capturingAppender.clear() + } + } + + abstract override def withFixture(test: NoArgTest): Outcome = { + myLogger.info(s"Logging started for test [${self.getClass.getName}: ${test.name}]") + val res = test() + myLogger.info(s"Logging finished for test [${self.getClass.getName}: ${test.name}] that [$res]") + + if (!(res.isSucceeded || res.isPending)) { + println( + s"--> [${Console.BLUE}${self.getClass.getName}: ${test.name}${Console.RESET}] Start of log messages of test that [$res]") + capturingAppender.flush() + println( + s"<-- [${Console.BLUE}${self.getClass.getName}: ${test.name}${Console.RESET}] End of log messages of test that [$res]") + } + + res + } +} diff --git a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/scaladsl/LoggingEventFilter.scala b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/scaladsl/LoggingEventFilter.scala new file mode 100644 index 0000000000..7eadc02da8 --- /dev/null +++ b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/scaladsl/LoggingEventFilter.scala @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2019 Lightbend Inc. + */ + +package akka.actor.testkit.typed.scaladsl + +import scala.reflect.ClassTag + +import akka.actor.testkit.typed.LoggingEvent +import akka.actor.testkit.typed.internal.LoggingEventFilterImpl +import akka.actor.typed.ActorSystem +import akka.annotation.DoNotInherit +import org.slf4j.event.Level + +/** + * Facilities for selectively filtering out expected events from logging + * to verify that they were logged. + * + * Requires Logback. + * + * See the companion object for convenient factory methods. + * + * Not for user extension. + */ +@DoNotInherit trait LoggingEventFilter { + + /** + * Number of events the filter is supposed to match. By default 1. + */ + def withOccurrences(newOccurrences: Int): LoggingEventFilter + + /** + * Matching events with the given log level. + */ + def withLogLevel(newLogLevel: Level): LoggingEventFilter + + /** + * Matching events with the given logger name or sub-names in the same way + * as configuration loggers are configured in logback.xml. + * By default the root logger is used. + */ + def withLoggerName(newLoggerName: String): LoggingEventFilter + + /** + * Matching events that have "akkaSource" MDC value equal to the given value. + * "akkaSource" is typically the actor path. + */ + def withSource(newSource: String): LoggingEventFilter + + /** + * Matching events with a message that contains the given value. + */ + def withMessageContains(newMessageContains: String): LoggingEventFilter + + /** + * Matching events with a message that matches the given regular expression. + */ + def withMessageRegex(newMessageRegex: String): LoggingEventFilter + + /** + * Matching events with an included `throwable` that is a class or subclass of the given + * `Throwable` `ClassTag`. + */ + def withCause[A <: Throwable: ClassTag]: LoggingEventFilter + + /** + * Matching events with MDC containing all entries of the given `Map`. + * The event MDC may have more entries than the given `Map`. + */ + def withMdc(newMdc: Map[String, String]): LoggingEventFilter + + /** + * Matching events for which the supplied function returns`true`. + */ + def withCustom(newCustom: Function[LoggingEvent, Boolean]): LoggingEventFilter + + /** + * @return `true` if the event matches the conditions of the filter. + */ + def matches(event: LoggingEvent): Boolean + + /** + * Apply this filter while executing the given code block. + * Assert that this filter has matched as often as requested by its + * `occurrences` parameter specifies. + * + * Care is taken to remove the filter when the block is finished or aborted. + */ + def intercept[T](code: => T)(implicit system: ActorSystem[_]): T + +} + +/** + * Facilities for selectively matching expected events from logging. + * + * Requires Logback. + */ +object LoggingEventFilter { + + /** + * An empty filter that doesn't match any events. + * + * More conditions can be added to the returned [LoggingEventFilter]. + */ + def empty: LoggingEventFilter = LoggingEventFilterImpl.empty + + /** + * Create a filter for events with a log message + * that contains the given `messageIncludes`. + * + * More conditions can be added to the returned [LoggingEventFilter]. + */ + def messageContains(str: String): LoggingEventFilter = + empty.withMessageContains(str) + + /** + * Create a filter for TRACE level events with a log message + * that contains the given `messageIncludes`. + * + * More conditions can be added to the returned [LoggingEventFilter]. + */ + def trace(messageIncludes: String): LoggingEventFilter = + messageContains(messageIncludes).withLogLevel(Level.TRACE) + + /** + * Create a filter for DEBUG level events with a log message + * that contains the given `messageIncludes`. + * + * More conditions can be added to the returned [LoggingEventFilter]. + */ + def debug(messageIncludes: String): LoggingEventFilter = + messageContains(messageIncludes).withLogLevel(Level.DEBUG) + + /** + * Create a filter for INFO level events with a log message + * that contains the given `messageIncludes`. + * + * More conditions can be added to the returned [LoggingEventFilter]. + */ + def info(messageIncludes: String): LoggingEventFilter = + messageContains(messageIncludes).withLogLevel(Level.INFO) + + /** + * Create a filter for WARN level events with a log message + * that contains the given `messageIncludes`. + * + * More conditions can be added to the returned [LoggingEventFilter]. + */ + def warn(messageIncludes: String): LoggingEventFilter = + messageContains(messageIncludes).withLogLevel(Level.WARN) + + /** + * Create a filter for WARN level events with a an included + * `throwable` that is a class or subclass of the given + * `Throwable` `ClassTag`. + * + * More conditions can be added to the returned [LoggingEventFilter]. + */ + def warn[A <: Throwable: ClassTag]: LoggingEventFilter = + empty.withLogLevel(Level.WARN).withCause[A] + + /** + * Create a filter for ERROR level events with a log message + * that contains the given `messageIncludes`. + * + * More conditions can be added to the returned [LoggingEventFilter]. + */ + def error(messageIncludes: String): LoggingEventFilter = + messageContains(messageIncludes).withLogLevel(Level.ERROR) + + /** + * Create a filter for WARN level events with a an included + * `throwable` that is a class or subclass of the given + * `Throwable` `ClassTag`. + * + * More conditions can be added to the returned [LoggingEventFilter]. + */ + def error[A <: Throwable: ClassTag]: LoggingEventFilter = + empty.withLogLevel(Level.ERROR).withCause[A] + + /** + * Create a custom event filter. The filter will match those events for + * which the supplied function returns `true`. + */ + def custom(test: Function[LoggingEvent, Boolean]): LoggingEventFilter = + empty.withCustom(test) + + /** + * Filter for the logging of dead letters. + */ + def deadLetters(): LoggingEventFilter = + empty.withLogLevel(Level.INFO).withMessageRegex(".*was not delivered.*dead letters encountered.*") +} diff --git a/akka-actor-testkit-typed/src/test/java/akka/actor/testkit/typed/javadsl/ActorTestKitTest.java b/akka-actor-testkit-typed/src/test/java/akka/actor/testkit/typed/javadsl/ActorTestKitTest.java index 14aa41ef6e..0550e0c628 100644 --- a/akka-actor-testkit-typed/src/test/java/akka/actor/testkit/typed/javadsl/ActorTestKitTest.java +++ b/akka-actor-testkit-typed/src/test/java/akka/actor/testkit/typed/javadsl/ActorTestKitTest.java @@ -7,6 +7,7 @@ package akka.actor.testkit.typed.javadsl; import akka.Done; import akka.actor.typed.javadsl.Behaviors; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.scalatest.junit.JUnitSuite; @@ -21,6 +22,8 @@ public class ActorTestKitTest extends JUnitSuite { @ClassRule public static TestKitJunitResource testKit = new TestKitJunitResource(); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + @Test public void systemNameShouldComeFromTestClassViaJunitResource() { assertEquals("ActorTestKitTest", testKit.system().name()); diff --git a/akka-actor-testkit-typed/src/test/java/akka/actor/testkit/typed/javadsl/BehaviorTestKitTest.java b/akka-actor-testkit-typed/src/test/java/akka/actor/testkit/typed/javadsl/BehaviorTestKitTest.java index f3a9f6659a..578d968895 100644 --- a/akka-actor-testkit-typed/src/test/java/akka/actor/testkit/typed/javadsl/BehaviorTestKitTest.java +++ b/akka-actor-testkit-typed/src/test/java/akka/actor/testkit/typed/javadsl/BehaviorTestKitTest.java @@ -11,10 +11,10 @@ import akka.actor.typed.ActorRef; import akka.actor.typed.Behavior; import akka.actor.typed.Props; import akka.actor.typed.javadsl.Behaviors; -import akka.event.Logging; import org.junit.Ignore; import org.junit.Test; import org.scalatest.junit.JUnitSuite; +import org.slf4j.event.Level; import java.util.Arrays; import java.util.Collections; @@ -250,7 +250,7 @@ public class BehaviorTestKitTest extends JUnitSuite { test.run(new Log(what)); final List allLogEntries = test.getAllLogEntries(); assertEquals(1, allLogEntries.size()); - assertEquals(new CapturedLogEvent(Logging.InfoLevel(), what), allLogEntries.get(0)); + assertEquals(new CapturedLogEvent(Level.INFO, what), allLogEntries.get(0)); } @Test diff --git a/akka-actor-testkit-typed/src/test/java/akka/actor/testkit/typed/javadsl/LoggingEventFilterTest.java b/akka-actor-testkit-typed/src/test/java/akka/actor/testkit/typed/javadsl/LoggingEventFilterTest.java new file mode 100644 index 0000000000..144b64aa56 --- /dev/null +++ b/akka-actor-testkit-typed/src/test/java/akka/actor/testkit/typed/javadsl/LoggingEventFilterTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2018-2019 Lightbend Inc. + */ + +package akka.actor.testkit.typed.javadsl; + +import akka.actor.testkit.typed.LoggingEvent; +import akka.actor.testkit.typed.TestException; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.scalatest.junit.JUnitSuite; +import org.slf4j.event.Level; + +import java.util.Collections; +import java.util.Optional; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class LoggingEventFilterTest extends JUnitSuite { + + @ClassRule public static TestKitJunitResource testKit = new TestKitJunitResource(); + + @Rule public final LogCapturing logCapturing = new LogCapturing(); + + private LoggingEvent errorNoCause() { + return LoggingEvent.create( + Level.ERROR, + getClass().getName(), + Thread.currentThread().getName(), + "this is an error", + System.currentTimeMillis(), + Optional.empty(), + Optional.empty(), + Collections.emptyMap()); + } + + private LoggingEvent errorWithCause(Throwable cause) { + return LoggingEvent.create( + Level.ERROR, + getClass().getName(), + Thread.currentThread().getName(), + "this is an error", + System.currentTimeMillis(), + Optional.empty(), + Optional.of(cause), + Collections.emptyMap()); + } + + @Test + public void filterErrorsWithMatchingMessage() { + assertTrue( + LoggingEventFilter.error("an error").matches(errorWithCause(new TestException("exc")))); + assertTrue(LoggingEventFilter.error("an error").matches(errorNoCause())); + assertFalse(LoggingEventFilter.error("another error").matches(errorNoCause())); + } + + @Test + public void filterErrorsWithMatchingCause() { + assertTrue( + LoggingEventFilter.error(TestException.class) + .matches(errorWithCause(new TestException("exc")))); + assertFalse( + LoggingEventFilter.error(TestException.class) + .matches(errorWithCause(new RuntimeException("exc")))); + assertTrue( + LoggingEventFilter.error("an error") + .withCause(TestException.class) + .matches(errorWithCause(new TestException("exc")))); + assertFalse( + LoggingEventFilter.error("another error") + .withCause(TestException.class) + .matches(errorWithCause(new TestException("exc")))); + } + + @Test + public void filterErrorsWithMatchingCustomFunction() { + assertTrue(LoggingEventFilter.custom(event -> true).matches(errorNoCause())); + assertFalse( + LoggingEventFilter.custom(event -> event.getMdc().containsKey("aKey")) + .matches(errorNoCause())); + } +} diff --git a/akka-actor-testkit-typed/src/test/java/akka/actor/testkit/typed/javadsl/TestProbeTest.java b/akka-actor-testkit-typed/src/test/java/akka/actor/testkit/typed/javadsl/TestProbeTest.java index f1e3edf0b3..0bda428b48 100644 --- a/akka-actor-testkit-typed/src/test/java/akka/actor/testkit/typed/javadsl/TestProbeTest.java +++ b/akka-actor-testkit-typed/src/test/java/akka/actor/testkit/typed/javadsl/TestProbeTest.java @@ -11,6 +11,7 @@ import java.util.List; import akka.actor.testkit.typed.scaladsl.TestProbeSpec; import akka.actor.testkit.typed.scaladsl.TestProbeSpec.*; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.scalatest.junit.JUnitSuite; @@ -20,6 +21,8 @@ public class TestProbeTest extends JUnitSuite { @ClassRule public static TestKitJunitResource testKit = new TestKitJunitResource(); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + @Test public void testReceiveMessage() { TestProbe probe = TestProbe.create(testKit.system()); diff --git a/akka-actor-testkit-typed/src/test/java/jdocs/akka/actor/testkit/typed/javadsl/JunitIntegrationExampleTest.java b/akka-actor-testkit-typed/src/test/java/jdocs/akka/actor/testkit/typed/javadsl/JunitIntegrationExampleTest.java index ce7c380c07..707ee7ddde 100644 --- a/akka-actor-testkit-typed/src/test/java/jdocs/akka/actor/testkit/typed/javadsl/JunitIntegrationExampleTest.java +++ b/akka-actor-testkit-typed/src/test/java/jdocs/akka/actor/testkit/typed/javadsl/JunitIntegrationExampleTest.java @@ -9,16 +9,20 @@ import static jdocs.akka.actor.testkit.typed.javadsl.AsyncTestingExampleTest.Pon import static jdocs.akka.actor.testkit.typed.javadsl.AsyncTestingExampleTest.echoActor; // #junit-integration +import akka.actor.testkit.typed.javadsl.LogCapturing; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import akka.actor.testkit.typed.javadsl.TestProbe; import akka.actor.typed.ActorRef; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; public class JunitIntegrationExampleTest { @ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource(); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + @Test public void testSomething() { ActorRef pinger = testKit.spawn(echoActor(), "ping"); diff --git a/akka-actor-testkit-typed/src/test/java/jdocs/akka/actor/testkit/typed/javadsl/ManualTimerExampleTest.java b/akka-actor-testkit-typed/src/test/java/jdocs/akka/actor/testkit/typed/javadsl/ManualTimerExampleTest.java index 9313065591..33eae704ca 100644 --- a/akka-actor-testkit-typed/src/test/java/jdocs/akka/actor/testkit/typed/javadsl/ManualTimerExampleTest.java +++ b/akka-actor-testkit-typed/src/test/java/jdocs/akka/actor/testkit/typed/javadsl/ManualTimerExampleTest.java @@ -6,10 +6,12 @@ package jdocs.akka.actor.testkit.typed.javadsl; // #manual-scheduling-simple +import akka.actor.testkit.typed.javadsl.LogCapturing; import akka.actor.typed.Behavior; import akka.actor.testkit.typed.javadsl.ManualTime; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import org.junit.ClassRule; +import org.junit.Rule; import org.scalatest.junit.JUnitSuite; import java.time.Duration; @@ -24,6 +26,8 @@ public class ManualTimerExampleTest extends JUnitSuite { @ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource(ManualTime.config()); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + private final ManualTime manualTime = ManualTime.get(testKit.system()); static final class Tick {} diff --git a/akka-actor-testkit-typed/src/test/java/jdocs/akka/actor/testkit/typed/javadsl/SyncTestingExampleTest.java b/akka-actor-testkit-typed/src/test/java/jdocs/akka/actor/testkit/typed/javadsl/SyncTestingExampleTest.java index e3fa19e74c..7ea37ff1d9 100644 --- a/akka-actor-testkit-typed/src/test/java/jdocs/akka/actor/testkit/typed/javadsl/SyncTestingExampleTest.java +++ b/akka-actor-testkit-typed/src/test/java/jdocs/akka/actor/testkit/typed/javadsl/SyncTestingExampleTest.java @@ -11,15 +11,14 @@ import akka.actor.testkit.typed.javadsl.Effects; import akka.actor.testkit.typed.javadsl.TestInbox; import akka.actor.typed.*; import akka.actor.typed.javadsl.*; -import akka.event.Logging; import java.util.HashMap; import java.util.List; import java.util.Optional; +import org.slf4j.event.Level; // #imports import org.junit.Test; import static org.junit.Assert.assertEquals; import org.scalatest.junit.JUnitSuite; -import org.slf4j.event.LoggingEvent; public class SyncTestingExampleTest extends JUnitSuite { @@ -173,7 +172,7 @@ public class SyncTestingExampleTest extends JUnitSuite { assertEquals(1, allLogEntries.size()); CapturedLogEvent expectedLogEvent = new CapturedLogEvent( - Logging.InfoLevel(), + Level.INFO, "Saying hello to Inboxer", Optional.empty(), Optional.empty(), diff --git a/akka-actor-testkit-typed/src/test/resources/logback-test.xml b/akka-actor-testkit-typed/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..e90cc999d2 --- /dev/null +++ b/akka-actor-testkit-typed/src/test/resources/logback-test.xml @@ -0,0 +1,35 @@ + + + + + + + + DEBUG + DENY + + + %date{ISO8601} %-5level %logger %X{akkaSource} %X{sourceThread} - %msg%n + + + + + + + + + + + + + + + diff --git a/akka-actor-testkit-typed/src/test/scala/akka/actor/testkit/typed/scaladsl/ActorTestKitSpec.scala b/akka-actor-testkit-typed/src/test/scala/akka/actor/testkit/typed/scaladsl/ActorTestKitSpec.scala index 78fd001a4b..4db9c9affc 100644 --- a/akka-actor-testkit-typed/src/test/scala/akka/actor/testkit/typed/scaladsl/ActorTestKitSpec.scala +++ b/akka-actor-testkit-typed/src/test/scala/akka/actor/testkit/typed/scaladsl/ActorTestKitSpec.scala @@ -14,7 +14,7 @@ import org.scalatest.Matchers import org.scalatest.WordSpec import org.scalatest.WordSpecLike -class ActorTestKitSpec extends ScalaTestWithActorTestKit with WordSpecLike { +class ActorTestKitSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { "the Scala testkit" should { @@ -91,7 +91,7 @@ class ActorTestKitSpec extends ScalaTestWithActorTestKit with WordSpecLike { } // derivative classes should also work fine (esp the naming part -abstract class MyBaseSpec extends ScalaTestWithActorTestKit with Matchers with WordSpecLike +abstract class MyBaseSpec extends ScalaTestWithActorTestKit with Matchers with WordSpecLike with LogCapturing class MyConcreteDerivateSpec extends MyBaseSpec { "A derivative test" should { @@ -116,7 +116,7 @@ class MyConcreteDerivateSpec extends MyBaseSpec { } -class CompositionSpec extends WordSpec with Matchers with BeforeAndAfterAll { +class CompositionSpec extends WordSpec with Matchers with BeforeAndAfterAll with LogCapturing { val testKit = ActorTestKit() override def afterAll(): Unit = { diff --git a/akka-actor-testkit-typed/src/test/scala/akka/actor/testkit/typed/scaladsl/BehaviorTestKitSpec.scala b/akka-actor-testkit-typed/src/test/scala/akka/actor/testkit/typed/scaladsl/BehaviorTestKitSpec.scala index b11c988d93..d7b94f7474 100644 --- a/akka-actor-testkit-typed/src/test/scala/akka/actor/testkit/typed/scaladsl/BehaviorTestKitSpec.scala +++ b/akka-actor-testkit-typed/src/test/scala/akka/actor/testkit/typed/scaladsl/BehaviorTestKitSpec.scala @@ -11,11 +11,11 @@ import akka.actor.testkit.typed.{ CapturedLogEvent, Effect } import akka.actor.testkit.typed.Effect._ import akka.actor.testkit.typed.scaladsl.BehaviorTestKitSpec.{ Child, Father } import akka.actor.testkit.typed.scaladsl.BehaviorTestKitSpec.Father._ -import akka.event.Logging import org.scalatest.{ Matchers, WordSpec } - import scala.reflect.ClassTag +import org.slf4j.event.Level + object BehaviorTestKitSpec { object Father { @@ -121,7 +121,7 @@ object BehaviorTestKitSpec { } -class BehaviorTestKitSpec extends WordSpec with Matchers { +class BehaviorTestKitSpec extends WordSpec with Matchers with LogCapturing { private val props = Props.empty.withDispatcherFromConfig("cat") @@ -183,14 +183,14 @@ class BehaviorTestKitSpec extends WordSpec with Matchers { val what = "Hello!" val testkit = BehaviorTestKit[Father.Command](Father.init) testkit.run(Log(what)) - testkit.logEntries() shouldBe Seq(CapturedLogEvent(Logging.InfoLevel, what)) + testkit.logEntries() shouldBe Seq(CapturedLogEvent(Level.INFO, what)) } "allow clearing log messages issued by behavior" in { - val what = "Hello!" + val what = "Hi!" val testkit = BehaviorTestKit[Father.Command](Father.init) testkit.run(Log(what)) - testkit.logEntries() shouldBe Seq(CapturedLogEvent(Logging.InfoLevel, what)) + testkit.logEntries() shouldBe Seq(CapturedLogEvent(Level.INFO, what)) testkit.clearLog() testkit.logEntries() shouldBe Seq.empty } diff --git a/akka-actor-testkit-typed/src/test/scala/akka/actor/testkit/typed/scaladsl/LoggingEventFilterSpec.scala b/akka-actor-testkit-typed/src/test/scala/akka/actor/testkit/typed/scaladsl/LoggingEventFilterSpec.scala new file mode 100644 index 0000000000..0660335368 --- /dev/null +++ b/akka-actor-testkit-typed/src/test/scala/akka/actor/testkit/typed/scaladsl/LoggingEventFilterSpec.scala @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ + +package akka.actor.testkit.typed.scaladsl + +import akka.actor.testkit.typed.LoggingEvent +import org.scalatest.WordSpecLike +import org.slf4j.event.Level + +class LoggingEventFilterSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { + + private class AnError extends Exception + private def errorNoCause = + LoggingEvent( + level = Level.ERROR, + loggerName = getClass.getName, + message = "this is an error", + threadName = Thread.currentThread().getName, + timeStamp = System.currentTimeMillis()) + private def errorWithCause(cause: Throwable) = + LoggingEvent( + level = Level.ERROR, + loggerName = getClass.getName, + message = "this is an error", + threadName = Thread.currentThread().getName, + timeStamp = System.currentTimeMillis(), + marker = None, + throwable = Option(cause), + mdc = Map.empty) + private def warningNoCause = + LoggingEvent( + level = Level.WARN, + loggerName = getClass.getName, + message = "this is a warning", + threadName = Thread.currentThread().getName, + timeStamp = System.currentTimeMillis()) + private def warningWithCause(cause: Throwable) = + LoggingEvent( + level = Level.WARN, + loggerName = getClass.getName, + message = "this is a warning", + threadName = Thread.currentThread().getName, + timeStamp = System.currentTimeMillis(), + marker = None, + throwable = Option(cause), + mdc = Map.empty) + private def warningWithSource(source: String) = + LoggingEvent( + level = Level.WARN, + loggerName = getClass.getName, + message = "this is a warning", + threadName = Thread.currentThread().getName, + timeStamp = System.currentTimeMillis(), + marker = None, + throwable = None, + mdc = Map("akkaSource" -> source)) + + "The LoggingEventFilter.error" must { + "filter errors without cause" in { + val filter = LoggingEventFilter.empty.withLogLevel(Level.ERROR) + filter.matches(errorNoCause) should ===(true) + } + + "filter errors with cause" in { + val filter = LoggingEventFilter.empty.withLogLevel(Level.ERROR) + filter.matches(errorWithCause(new AnError)) should ===(true) + } + + "filter error with matching message" in { + LoggingEventFilter.error("an error").matches(errorWithCause(new AnError)) should ===(true) + LoggingEventFilter.error("an error").matches(errorNoCause) should ===(true) + LoggingEventFilter.error("another error").matches(errorNoCause) should ===(false) + } + + "filter with matching MDC" in { + LoggingEventFilter.empty.withMdc(Map("a" -> "A")).matches(errorNoCause.copy(mdc = Map("a" -> "A"))) should ===( + true) + LoggingEventFilter.empty + .withMdc(Map("a" -> "A", "b" -> "B")) + .matches(errorNoCause.copy(mdc = Map("a" -> "A", "b" -> "B"))) should ===(true) + LoggingEventFilter.empty + .withMdc(Map("a" -> "A")) + .matches(errorNoCause.copy(mdc = Map("a" -> "A", "b" -> "B"))) should ===(true) + LoggingEventFilter.empty + .withMdc(Map("a" -> "A", "b" -> "B")) + .matches(errorNoCause.copy(mdc = Map("a" -> "A"))) should ===(false) + LoggingEventFilter.empty.withMdc(Map("a" -> "A", "b" -> "B")).matches(errorNoCause) should ===(false) + } + } + + "The LoggingEventFilter with cause" must { + "not filter errors without cause" in { + val filter = LoggingEventFilter.error[AnError] + filter.matches(errorNoCause) should ===(false) + } + + "not filter errors with an unrelated cause" in { + object AnotherError extends Exception + val filter = LoggingEventFilter.error[AnError] + filter.matches(errorWithCause(AnotherError)) should ===(false) + } + + "filter errors with a matching cause" in { + val filter = LoggingEventFilter.error[AnError] + filter.matches(errorWithCause(new AnError)) should ===(true) + } + "filter errors with a matching cause and message" in { + val filter = LoggingEventFilter.error("this is an error").withCause[AnError] + filter.matches(errorWithCause(new AnError)) should ===(true) + } + } + + "The LoggingEventFilter.warn" must { + "filter warnings without cause" in { + val filter = LoggingEventFilter.empty.withLogLevel(Level.WARN) + filter.matches(warningNoCause) should ===(true) + } + "filter warning with cause" in { + val filter = LoggingEventFilter.empty.withLogLevel(Level.WARN) + filter.matches(warningWithCause(new AnError)) should ===(true) + } + "filter warning with matching message" in { + LoggingEventFilter.warn("this is a warning").matches(warningWithCause(new AnError)) should ===(true) + LoggingEventFilter.warn("this is another warning").matches(warningWithCause(new AnError)) should ===(false) + } + "filter warning with matching source" in { + val source = "akka://Sys/user/foo" + LoggingEventFilter.empty + .withLogLevel(Level.WARN) + .withSource(source) + .matches(warningWithSource(source)) should ===(true) + LoggingEventFilter.empty + .withLogLevel(Level.WARN) + .withSource("akka://Sys/user/bar") + .matches(warningWithSource(source)) should ===(false) + } + + } + +} diff --git a/akka-actor-testkit-typed/src/test/scala/akka/actor/testkit/typed/scaladsl/TestAppenderSpec.scala b/akka-actor-testkit-typed/src/test/scala/akka/actor/testkit/typed/scaladsl/TestAppenderSpec.scala new file mode 100644 index 0000000000..a0c704a5a3 --- /dev/null +++ b/akka-actor-testkit-typed/src/test/scala/akka/actor/testkit/typed/scaladsl/TestAppenderSpec.scala @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ + +package akka.actor.testkit.typed.scaladsl + +import java.util.concurrent.atomic.AtomicInteger + +import akka.actor.testkit.typed.TestException +import org.scalatest.WordSpecLike +import org.slf4j.LoggerFactory + +class TestAppenderSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { + + class AnotherLoggerClass + + private val log = LoggerFactory.getLogger(getClass) + + "TestAppender and LoggingEventFilter" must { + "filter errors without cause" in { + LoggingEventFilter.error("an error").withOccurrences(2).intercept { + log.error("an error") + log.error("an error") + } + } + + "filter errors with cause" in { + LoggingEventFilter.error("err").withCause[TestException].intercept { + log.error("err", TestException("an error")) + } + } + + "filter warnings" in { + LoggingEventFilter.warn("a warning").withOccurrences(2).intercept { + log.error("an error") + log.warn("a warning") + log.error("an error") + log.warn("a warning") + } + } + + "only filter events for given logger name" in { + val count = new AtomicInteger + LoggingEventFilter + .custom({ + case logEvent => + count.incrementAndGet() + logEvent.message == "Hello from right logger" && logEvent.loggerName == classOf[AnotherLoggerClass].getName + }) + .withOccurrences(2) + .withLoggerName(classOf[AnotherLoggerClass].getName) + .intercept { + LoggerFactory.getLogger(classOf[AnotherLoggerClass]).info("Hello from right logger") + log.info("Hello wrong logger") + LoggerFactory.getLogger(classOf[AnotherLoggerClass]).info("Hello from right logger") + } + count.get should ===(2) + } + + "find unexpected events withOccurences(0)" in { + LoggingEventFilter.warn("a warning").withOccurrences(0).intercept { + log.error("an error") + log.warn("another warning") + } + + intercept[AssertionError] { + LoggingEventFilter.warn("a warning").withOccurrences(0).intercept { + log.error("an error") + log.warn("a warning") + log.warn("another warning") + } + }.getMessage should include("Received 1 excess messages") + + intercept[AssertionError] { + LoggingEventFilter.warn("a warning").withOccurrences(0).intercept { + log.warn("a warning") + log.warn("a warning") + } + }.getMessage should include("Received 2 excess messages") + } + + } + +} diff --git a/akka-actor-testkit-typed/src/test/scala/akka/actor/testkit/typed/scaladsl/TestProbeSpec.scala b/akka-actor-testkit-typed/src/test/scala/akka/actor/testkit/typed/scaladsl/TestProbeSpec.scala index b41041b20f..1909feae3e 100644 --- a/akka-actor-testkit-typed/src/test/scala/akka/actor/testkit/typed/scaladsl/TestProbeSpec.scala +++ b/akka-actor-testkit-typed/src/test/scala/akka/actor/testkit/typed/scaladsl/TestProbeSpec.scala @@ -10,7 +10,7 @@ import com.typesafe.config.ConfigFactory import scala.concurrent.duration._ import org.scalatest.WordSpecLike -class TestProbeSpec extends ScalaTestWithActorTestKit with WordSpecLike { +class TestProbeSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { import TestProbeSpec._ @@ -181,7 +181,10 @@ object TestProbeSpec { for (n <- 1 to expected) yield EventT(n) } -class TestProbeTimeoutSpec extends ScalaTestWithActorTestKit(TestProbeSpec.timeoutConfig) with WordSpecLike { +class TestProbeTimeoutSpec + extends ScalaTestWithActorTestKit(TestProbeSpec.timeoutConfig) + with WordSpecLike + with LogCapturing { import TestProbeSpec._ diff --git a/akka-actor-testkit-typed/src/test/scala/docs/akka/actor/testkit/typed/scaladsl/AsyncTestingExampleSpec.scala b/akka-actor-testkit-typed/src/test/scala/docs/akka/actor/testkit/typed/scaladsl/AsyncTestingExampleSpec.scala index 29d759bd8f..f9347ed7a6 100644 --- a/akka-actor-testkit-typed/src/test/scala/docs/akka/actor/testkit/typed/scaladsl/AsyncTestingExampleSpec.scala +++ b/akka-actor-testkit-typed/src/test/scala/docs/akka/actor/testkit/typed/scaladsl/AsyncTestingExampleSpec.scala @@ -4,6 +4,7 @@ package docs.akka.actor.testkit.typed.scaladsl +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.Scheduler //#test-header import akka.actor.testkit.typed.scaladsl.ActorTestKit @@ -58,7 +59,7 @@ object AsyncTestingExampleSpec { } //#test-header -class AsyncTestingExampleSpec extends WordSpec with BeforeAndAfterAll with Matchers { +class AsyncTestingExampleSpec extends WordSpec with BeforeAndAfterAll with Matchers with LogCapturing { val testKit = ActorTestKit() //#test-header diff --git a/akka-actor-testkit-typed/src/test/scala/docs/akka/actor/testkit/typed/scaladsl/ManualTimerExampleSpec.scala b/akka-actor-testkit-typed/src/test/scala/docs/akka/actor/testkit/typed/scaladsl/ManualTimerExampleSpec.scala index 67f39c59f6..adc78fdfc1 100644 --- a/akka-actor-testkit-typed/src/test/scala/docs/akka/actor/testkit/typed/scaladsl/ManualTimerExampleSpec.scala +++ b/akka-actor-testkit-typed/src/test/scala/docs/akka/actor/testkit/typed/scaladsl/ManualTimerExampleSpec.scala @@ -6,13 +6,15 @@ package docs.akka.actor.testkit.typed.scaladsl //#manual-scheduling-simple import scala.concurrent.duration._ + import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.testkit.typed.scaladsl.ManualTime import akka.actor.testkit.typed.scaladsl.TestProbe +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.scaladsl.Behaviors import org.scalatest.WordSpecLike -class ManualTimerExampleSpec extends ScalaTestWithActorTestKit(ManualTime.config) with WordSpecLike { +class ManualTimerExampleSpec extends ScalaTestWithActorTestKit(ManualTime.config) with WordSpecLike with LogCapturing { val manualTime: ManualTime = ManualTime() diff --git a/akka-actor-testkit-typed/src/test/scala/docs/akka/actor/testkit/typed/scaladsl/SyncTestingExampleSpec.scala b/akka-actor-testkit-typed/src/test/scala/docs/akka/actor/testkit/typed/scaladsl/SyncTestingExampleSpec.scala index 3865760835..e006523cef 100644 --- a/akka-actor-testkit-typed/src/test/scala/docs/akka/actor/testkit/typed/scaladsl/SyncTestingExampleSpec.scala +++ b/akka-actor-testkit-typed/src/test/scala/docs/akka/actor/testkit/typed/scaladsl/SyncTestingExampleSpec.scala @@ -11,7 +11,7 @@ import akka.actor.testkit.typed.scaladsl.BehaviorTestKit import akka.actor.testkit.typed.scaladsl.TestInbox import akka.actor.typed._ import akka.actor.typed.scaladsl._ -import akka.event.Logging +import org.slf4j.event.Level //#imports import org.scalatest.Matchers import org.scalatest.WordSpec @@ -115,7 +115,7 @@ class SyncTestingExampleSpec extends WordSpec with Matchers { val testKit = BehaviorTestKit(myBehavior) val inbox = TestInbox[String]("Inboxer") testKit.run(LogAndSayHello(inbox.ref)) - testKit.logEntries() shouldBe Seq(CapturedLogEvent(Logging.InfoLevel, "Saying hello to Inboxer")) + testKit.logEntries() shouldBe Seq(CapturedLogEvent(Level.INFO, "Saying hello to Inboxer")) //#test-check-logging } } diff --git a/akka-actor-tests/src/test/scala/akka/config/ConfigSpec.scala b/akka-actor-tests/src/test/scala/akka/config/ConfigSpec.scala index 34446cf044..b56c08b8e8 100644 --- a/akka-actor-tests/src/test/scala/akka/config/ConfigSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/config/ConfigSpec.scala @@ -12,9 +12,10 @@ import akka.event.Logging.DefaultLogger import akka.testkit.AkkaSpec import com.typesafe.config.ConfigFactory import org.scalatest.Assertions - import scala.concurrent.duration._ +import akka.actor.ExtendedActorSystem + class ConfigSpec extends AkkaSpec(ConfigFactory.defaultReference(ActorSystem.findClassLoader())) with Assertions { "The default configuration file (i.e. reference.conf)" must { @@ -164,4 +165,24 @@ class ConfigSpec extends AkkaSpec(ConfigFactory.defaultReference(ActorSystem.fin } } } + + "SLF4J Settings" must { + "not be amended for default reference in akka-actor" in { + val dynamicAccess = system.asInstanceOf[ExtendedActorSystem].dynamicAccess + val config = ActorSystem.Settings.amendSlf4jConfig(ConfigFactory.defaultReference(), dynamicAccess) + config.getStringList("akka.loggers").size() should ===(1) + config.getStringList("akka.loggers").get(0) should ===(classOf[DefaultLogger].getName) + config.getString("akka.logging-filter") should ===(classOf[DefaultLoggingFilter].getName) + } + + "not be amended when akka-slf4j is not in classpath" in { + val dynamicAccess = system.asInstanceOf[ExtendedActorSystem].dynamicAccess + val config = ActorSystem.Settings.amendSlf4jConfig( + ConfigFactory.parseString("akka.use-slf4j = on").withFallback(ConfigFactory.defaultReference()), + dynamicAccess) + config.getStringList("akka.loggers").size() should ===(1) + config.getStringList("akka.loggers").get(0) should ===(classOf[DefaultLogger].getName) + config.getString("akka.logging-filter") should ===(classOf[DefaultLoggingFilter].getName) + } + } } diff --git a/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/ActorContextAskTest.java b/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/ActorContextAskTest.java index e50f92f3f3..0cf99e3a99 100644 --- a/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/ActorContextAskTest.java +++ b/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/ActorContextAskTest.java @@ -4,6 +4,7 @@ package akka.actor.typed.javadsl; +import akka.actor.testkit.typed.javadsl.LogCapturing; import akka.actor.typed.ActorRef; import akka.actor.typed.Behavior; import akka.testkit.AkkaSpec; @@ -11,6 +12,7 @@ import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import akka.actor.testkit.typed.javadsl.TestProbe; import akka.util.Timeout; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.scalatest.junit.JUnitSuite; @@ -22,6 +24,8 @@ public class ActorContextAskTest extends JUnitSuite { @ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource(AkkaSpec.testConf()); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + static class Ping { final ActorRef respondTo; diff --git a/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/ActorContextPipeToSelfTest.java b/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/ActorContextPipeToSelfTest.java index 6242d84bfc..67793117cc 100644 --- a/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/ActorContextPipeToSelfTest.java +++ b/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/ActorContextPipeToSelfTest.java @@ -4,12 +4,14 @@ package akka.actor.typed.javadsl; +import akka.actor.testkit.typed.javadsl.LogCapturing; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import akka.actor.testkit.typed.javadsl.TestProbe; import akka.actor.typed.Behavior; import akka.actor.typed.Props; import com.typesafe.config.ConfigFactory; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.scalatest.junit.JUnitSuite; @@ -28,6 +30,8 @@ public final class ActorContextPipeToSelfTest extends JUnitSuite { "pipe-to-self-spec-dispatcher.executor = thread-pool-executor\n" + "pipe-to-self-spec-dispatcher.type = PinnedDispatcher\n")); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + static final class Msg { final String response; final String selfName; diff --git a/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/ActorLoggingTest.java b/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/ActorLoggingTest.java index c359ecd911..7eef5e7acc 100644 --- a/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/ActorLoggingTest.java +++ b/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/ActorLoggingTest.java @@ -4,6 +4,7 @@ package akka.actor.typed.javadsl; +import akka.actor.testkit.typed.javadsl.LogCapturing; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import akka.actor.typed.ActorRef; import akka.actor.typed.Behavior; @@ -12,6 +13,7 @@ import akka.japi.pf.PFBuilder; import akka.testkit.CustomEventFilter; import com.typesafe.config.ConfigFactory; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.scalatest.junit.JUnitSuite; import scala.concurrent.duration.FiniteDuration; @@ -44,6 +46,8 @@ public class ActorLoggingTest extends JUnitSuite { } } + @Rule public final LogCapturing logCapturing = new LogCapturing(); + @Test public void loggingProvidesClassWhereLogWasCalled() { CustomEventFilter eventFilter = @@ -72,7 +76,7 @@ public class ActorLoggingTest extends JUnitSuite { Behaviors.setup( context -> Behaviors.withMdc( - null, + Protocol.class, (message) -> { Map mdc = new HashMap<>(); mdc.put("txId", message.getTransactionId()); diff --git a/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/AdapterTest.java b/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/AdapterTest.java index b87ccdc7b3..945ab96bcc 100644 --- a/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/AdapterTest.java +++ b/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/AdapterTest.java @@ -4,7 +4,9 @@ package akka.actor.typed.javadsl; +import akka.actor.testkit.typed.javadsl.LogCapturing; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.scalatest.junit.JUnitSuite; @@ -240,6 +242,8 @@ public class AdapterTest extends JUnitSuite { public static AkkaJUnitActorSystemResource actorSystemResource = new AkkaJUnitActorSystemResource("ActorSelectionTest", AkkaSpec.testConf()); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + private final ActorSystem system = actorSystemResource.getSystem(); @Test diff --git a/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/BehaviorBuilderTest.java b/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/BehaviorBuilderTest.java index 6d0f6828fa..288664df70 100644 --- a/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/BehaviorBuilderTest.java +++ b/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/BehaviorBuilderTest.java @@ -4,9 +4,11 @@ package akka.actor.typed.javadsl; +import akka.actor.testkit.typed.javadsl.LogCapturing; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import akka.actor.testkit.typed.javadsl.TestProbe; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.scalatest.junit.JUnitSuite; @@ -25,6 +27,8 @@ public class BehaviorBuilderTest extends JUnitSuite { @ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource(); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + interface Message {} static final class One implements Message { diff --git a/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/InterceptTest.java b/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/InterceptTest.java index 5031871439..dfcb075622 100644 --- a/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/InterceptTest.java +++ b/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/InterceptTest.java @@ -4,11 +4,13 @@ package akka.actor.typed.javadsl; +import akka.actor.testkit.typed.javadsl.LogCapturing; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import akka.actor.testkit.typed.javadsl.TestProbe; import akka.actor.typed.*; import akka.testkit.AkkaSpec; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.scalatest.junit.JUnitSuite; @@ -17,6 +19,8 @@ public class InterceptTest extends JUnitSuite { @ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource(AkkaSpec.testConf()); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + @Test public void interceptMessage() { final TestProbe interceptProbe = testKit.createTestProbe(); diff --git a/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/ReceiveBuilderTest.java b/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/ReceiveBuilderTest.java index 71c302870d..21dc6385a4 100644 --- a/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/ReceiveBuilderTest.java +++ b/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/ReceiveBuilderTest.java @@ -4,10 +4,12 @@ package akka.actor.typed.javadsl; +import akka.actor.testkit.typed.javadsl.LogCapturing; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import akka.actor.testkit.typed.javadsl.TestProbe; import akka.actor.typed.ActorRef; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.scalatest.junit.JUnitSuite; @@ -21,6 +23,8 @@ public class ReceiveBuilderTest extends JUnitSuite { @ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource(); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + @Test public void testMutableCounter() { Behavior mutable = diff --git a/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/WatchTest.java b/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/WatchTest.java index 8c284ad836..b89067e3b2 100644 --- a/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/WatchTest.java +++ b/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/WatchTest.java @@ -8,8 +8,10 @@ import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; import akka.Done; +import akka.actor.testkit.typed.javadsl.LogCapturing; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import org.junit.ClassRule; +import org.junit.Rule; import org.scalatest.junit.JUnitSuite; import org.junit.Test; @@ -24,6 +26,8 @@ public class WatchTest extends JUnitSuite { @ClassRule public static TestKitJunitResource testKit = new TestKitJunitResource(); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + interface Message {} static final class RunTest implements Message { diff --git a/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/BubblingSampleTest.java b/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/BubblingSampleTest.java index daf4efc255..61d4c871ba 100644 --- a/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/BubblingSampleTest.java +++ b/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/BubblingSampleTest.java @@ -4,10 +4,12 @@ package jdocs.akka.typed; +import akka.actor.testkit.typed.javadsl.LogCapturing; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import akka.actor.testkit.typed.javadsl.TestProbe; import akka.actor.typed.ActorRef; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.scalatest.junit.JUnitSuite; @@ -22,6 +24,8 @@ public class BubblingSampleTest extends JUnitSuite { public static final TestKitJunitResource testKit = new TestKitJunitResource("akka.loglevel = off"); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + @Test public void testBubblingSample() throws Exception { ActorRef boss = testKit.spawn(Boss.create(), "upper-management"); diff --git a/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/InteractionPatternsTest.java b/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/InteractionPatternsTest.java index f784cf5d37..e2f84b74e1 100644 --- a/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/InteractionPatternsTest.java +++ b/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/InteractionPatternsTest.java @@ -4,6 +4,7 @@ package jdocs.akka.typed; +import akka.actor.testkit.typed.javadsl.LogCapturing; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import akka.actor.typed.ActorRef; import akka.actor.typed.ActorSystem; @@ -11,6 +12,7 @@ import akka.actor.typed.Behavior; import akka.actor.typed.javadsl.*; import akka.actor.testkit.typed.javadsl.TestProbe; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.scalatest.junit.JUnitSuite; @@ -647,6 +649,8 @@ public class InteractionPatternsTest extends JUnitSuite { @ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource(); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + @Test public void fireAndForgetSample() throws Exception { // #fire-and-forget-doit diff --git a/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/MailboxDocTest.java b/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/MailboxDocTest.java index 6d37e5ddcb..cfd661be5d 100644 --- a/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/MailboxDocTest.java +++ b/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/MailboxDocTest.java @@ -5,6 +5,7 @@ package jdocs.akka.typed; import akka.Done; +import akka.actor.testkit.typed.javadsl.LogCapturing; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import akka.actor.testkit.typed.javadsl.TestProbe; import akka.actor.typed.ActorRef; @@ -13,6 +14,7 @@ import akka.actor.typed.MailboxSelector; import akka.actor.typed.javadsl.Behaviors; import com.typesafe.config.ConfigFactory; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.scalatest.junit.JUnitSuite; @@ -22,6 +24,8 @@ public class MailboxDocTest extends JUnitSuite { public static final TestKitJunitResource testKit = new TestKitJunitResource(ConfigFactory.load("mailbox-config-sample.conf")); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + @Test public void startSomeActorsWithDifferentMailboxes() { TestProbe testProbe = testKit.createTestProbe(); diff --git a/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/StashDocTest.java b/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/StashDocTest.java index ec1669340b..465db02db5 100644 --- a/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/StashDocTest.java +++ b/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/StashDocTest.java @@ -5,10 +5,12 @@ package jdocs.akka.typed; import akka.Done; +import akka.actor.testkit.typed.javadsl.LogCapturing; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import akka.actor.testkit.typed.javadsl.TestProbe; import akka.actor.typed.ActorRef; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.scalatest.junit.JUnitSuite; @@ -22,6 +24,8 @@ public class StashDocTest extends JUnitSuite { @ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource(); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + @Test public void stashingExample() throws Exception { final DB db = diff --git a/akka-actor-typed-tests/src/test/resources/logback-test.xml b/akka-actor-typed-tests/src/test/resources/logback-test.xml index 04b2292ea4..9e4d05a8dd 100644 --- a/akka-actor-typed-tests/src/test/resources/logback-test.xml +++ b/akka-actor-typed-tests/src/test/resources/logback-test.xml @@ -1,27 +1,35 @@ + + + + + DEBUG + DENY + %date{ISO8601} %-5level %logger %X{akkaSource} %X{sourceThread} - %msg%n - - - %date{ISO8601} %-5level %logger %X{akkaSource} %X{sourceThread} - %msg%n - - - - log-${byDay}.txt - true - - %-4relative [%thread] %-5level %logger{35} - %msg%n - - - - + + + + + + + + - - + diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/ActorContextSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/ActorContextSpec.scala index 4b2b93083c..d7489ecc81 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/ActorContextSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/ActorContextSpec.scala @@ -7,12 +7,15 @@ package akka.actor.typed import akka.actor.InvalidMessageException import akka.actor.typed.scaladsl.Behaviors import akka.actor.testkit.typed.scaladsl.TestProbe - import scala.concurrent.duration._ import scala.reflect.ClassTag + +import akka.actor.UnhandledMessage import akka.actor.testkit.typed.TestException +import akka.actor.testkit.typed.scaladsl.LoggingEventFilter import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit -import akka.testkit.EventFilter +import akka.actor.testkit.typed.scaladsl.LogCapturing +import akka.actor.typed.eventstream.EventStream import org.scalatest.WordSpecLike object ActorSpecMessages { @@ -63,13 +66,7 @@ object ActorSpecMessages { } -abstract class ActorContextSpec extends ScalaTestWithActorTestKit(""" - akka.loggers = [akka.testkit.TestEventListener] - """) with WordSpecLike { - - // FIXME #24348: eventfilter support in typed testkit - import scaladsl.adapter._ - implicit val classicSystem = system.toClassic +abstract class ActorContextSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { import ActorSpecMessages._ @@ -89,6 +86,8 @@ abstract class ActorContextSpec extends ScalaTestWithActorTestKit(""" } "canonicalize behaviors" in { + val unhandledProbe = createTestProbe[UnhandledMessage]() + system.eventStream ! EventStream.Subscribe(unhandledProbe.ref) val probe = TestProbe[Event]() lazy val behavior: Behavior[Command] = Behaviors @@ -112,11 +111,11 @@ abstract class ActorContextSpec extends ScalaTestWithActorTestKit(""" val actor = spawn(behavior) actor ! Ping probe.expectMessage(Pong) - // unhandled gives warning from EventFilter - EventFilter.warning(occurrences = 1).intercept { - actor ! Miss - probe.expectMessage(Missed) - } + + actor ! Miss + probe.expectMessage(Missed) + unhandledProbe.receiveMessage() + actor ! Renew(probe.ref) probe.expectMessage(Renewed) actor ! Ping @@ -140,7 +139,7 @@ abstract class ActorContextSpec extends ScalaTestWithActorTestKit(""" val behavior = Behaviors.supervise(internal).onFailure(SupervisorStrategy.restart) val actor = spawn(behavior) - EventFilter[TestException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { actor ! Fail } probe.expectMessage(ReceivedSignal(PreRestart)) @@ -204,7 +203,7 @@ abstract class ActorContextSpec extends ScalaTestWithActorTestKit(""" val parentRef = spawn(parent) val childRef = probe.expectMessageType[ChildMade].ref - EventFilter[TestException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { childRef ! Fail } probe.expectMessage(GotChildSignal(PreRestart)) @@ -261,7 +260,7 @@ abstract class ActorContextSpec extends ScalaTestWithActorTestKit(""" val actor = spawn(behavior) actor ! Ping probe.expectMessage(1) - EventFilter[TestException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { actor ! Fail } actor ! Ping @@ -287,7 +286,7 @@ abstract class ActorContextSpec extends ScalaTestWithActorTestKit(""" val actor = spawn(behavior) actor ! Ping probe.expectMessage(1) - EventFilter[TestException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { actor ! Fail } actor ! Ping @@ -329,7 +328,7 @@ abstract class ActorContextSpec extends ScalaTestWithActorTestKit(""" probe.expectMessage(Pong) watcher ! Ping probe.expectMessage(Pong) - EventFilter[TestException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { actorToWatch ! Fail } probe.expectMessage(ReceivedSignal(PostStop)) @@ -514,7 +513,7 @@ abstract class ActorContextSpec extends ScalaTestWithActorTestKit(""" val childRef = probe.expectMessageType[ChildMade].ref actor ! Inert probe.expectMessage(InertEvent) - EventFilter[DeathPactException](occurrences = 1).intercept { + LoggingEventFilter.error[DeathPactException].intercept { childRef ! Stop probe.expectMessage(GotChildSignal(PostStop)) probe.expectMessage(ReceivedSignal(PostStop)) diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/AskSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/AskSpec.scala index 7cb0512cbf..06a01fd447 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/AskSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/AskSpec.scala @@ -8,36 +8,32 @@ import akka.actor.typed.internal.adapter.ActorSystemAdapter import akka.actor.typed.scaladsl.AskPattern._ import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.Behaviors._ -import akka.testkit.EventFilter import akka.actor.testkit.typed.scaladsl.TestProbe import akka.util.Timeout - import scala.concurrent.duration._ import scala.concurrent.{ ExecutionContext, TimeoutException } import scala.util.Success + import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import org.scalatest.WordSpecLike - import scala.concurrent.Future +import akka.actor.DeadLetter +import akka.actor.UnhandledMessage +import akka.actor.testkit.typed.scaladsl.LoggingEventFilter +import akka.actor.testkit.typed.scaladsl.LogCapturing +import akka.actor.typed.eventstream.EventStream + object AskSpec { sealed trait Msg final case class Foo(s: String, replyTo: ActorRef[String]) extends Msg final case class Stop(replyTo: ActorRef[Unit]) extends Msg } -class AskSpec extends ScalaTestWithActorTestKit(""" - akka.loglevel=warning - akka.loggers = [ akka.testkit.TestEventListener ] - """) with WordSpecLike { +class AskSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { - // FIXME #24348: eventfilter support in typed testkit import AskSpec._ - // FIXME #24348: eventfilter support in typed testkit - import scaladsl.adapter._ - implicit val classicSystem = system.toClassic - implicit def executor: ExecutionContext = system.executionContext @@ -58,10 +54,7 @@ class AskSpec extends ScalaTestWithActorTestKit(""" val probe = createTestProbe() probe.expectTerminated(ref, probe.remainingOrDefault) - val answer: Future[String] = - EventFilter.warning(pattern = ".*received dead letter.*", occurrences = 1).intercept { - ref.ask(Foo("bar", _)) - } + val answer: Future[String] = ref.ask(Foo("bar", _)) val result = answer.failed.futureValue result shouldBe a[TimeoutException] result.getMessage should include("had already been terminated.") @@ -80,17 +73,19 @@ class AskSpec extends ScalaTestWithActorTestKit(""" } "fail the future if the actor doesn't reply in time" in { + val unhandledProbe = createTestProbe[UnhandledMessage]() + system.eventStream ! EventStream.Subscribe(unhandledProbe.ref) + val actor = spawn(Behaviors.empty[Foo]) implicit val timeout: Timeout = 10.millis - EventFilter.warning(pattern = ".*unhandled message.*", occurrences = 1).intercept { - val answer: Future[String] = actor.ask(Foo("bar", _)) - val result = answer.failed.futureValue - result shouldBe a[TimeoutException] - result.getMessage should startWith("Ask timed out on") - } + + val answer: Future[String] = actor.ask(Foo("bar", _)) + unhandledProbe.receiveMessage() + val result = answer.failed.futureValue + result shouldBe a[TimeoutException] + result.getMessage should startWith("Ask timed out on") } - /** See issue #19947 (MatchError with adapted ActorRef) */ "fail the future if the actor doesn't exist" in { val noSuchActor: ActorRef[Msg] = system match { case adaptedSys: ActorSystemAdapter[_] => @@ -100,13 +95,19 @@ class AskSpec extends ScalaTestWithActorTestKit(""" fail("this test must only run in an adapted actor system") } - val answer: Future[String] = - EventFilter.warning(pattern = ".*received dead letter.*", occurrences = 1).intercept { - noSuchActor.ask(Foo("bar", _)) - } + val deadLetterProbe = createTestProbe[DeadLetter]() + system.eventStream ! EventStream.Subscribe(deadLetterProbe.ref) + + val answer: Future[String] = noSuchActor.ask(Foo("bar", _)) val result = answer.failed.futureValue result shouldBe a[TimeoutException] result.getMessage should include("had already been terminated") + + val deadLetter = deadLetterProbe.receiveMessage() + deadLetter.message match { + case Foo(s, _) => s should ===("bar") + case _ => fail(s"unexpected DeadLetter: $deadLetter") + } } "transform a replied akka.actor.Status.Failure to a failed future" in { @@ -127,6 +128,7 @@ class AskSpec extends ScalaTestWithActorTestKit(""" val legacyActor = classicSystem.actorOf(akka.actor.Props(new LegacyActor)) import scaladsl.AskPattern._ + import akka.actor.typed.scaladsl.adapter._ implicit val timeout: Timeout = 3.seconds implicit val scheduler = classicSystem.toTyped.scheduler val typedLegacy: ActorRef[AnyRef] = legacyActor @@ -172,10 +174,11 @@ class AskSpec extends ScalaTestWithActorTestKit(""" ref ! "start-ask" val Question(replyRef2) = probe.expectMessageType[Question] - EventFilter[RuntimeException](message = "Exception thrown out of adapter. Stopping myself.", occurrences = 1) + LoggingEventFilter + .error("Exception thrown out of adapter. Stopping myself.") .intercept { replyRef2 ! 42L - }(system.toClassic) + }(system) probe.expectTerminated(ref, probe.remainingOrDefault) } diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/BehaviorSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/BehaviorSpec.scala index c93018c000..e0b775cbdd 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/BehaviorSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/BehaviorSpec.scala @@ -12,6 +12,7 @@ import java.util.function.{ Function => F1 } import akka.Done import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.testkit.typed.scaladsl.{ BehaviorTestKit, TestInbox } import org.scalactic.TypeCheckedTripleEquals import org.scalatest.Matchers @@ -68,7 +69,7 @@ object BehaviorSpec { override def next = StateA } - trait Common extends WordSpecLike with Matchers with TypeCheckedTripleEquals { + trait Common extends WordSpecLike with Matchers with TypeCheckedTripleEquals with LogCapturing { type Aux >: Null <: AnyRef def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) @silent("never used") diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/DeferredSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/DeferredSpec.scala index 61fffbecf6..346ade880d 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/DeferredSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/DeferredSpec.scala @@ -4,13 +4,13 @@ package akka.actor.typed -import akka.testkit.EventFilter import akka.actor.typed.scaladsl.Behaviors import akka.actor.testkit.typed.TestKitSettings import akka.actor.testkit.typed.scaladsl._ - import scala.util.control.NoStackTrace + import akka.actor.ActorInitializationException +import akka.actor.testkit.typed.scaladsl.LoggingEventFilter import org.scalatest.{ Matchers, WordSpec, WordSpecLike } object DeferredSpec { @@ -30,17 +30,11 @@ object DeferredSpec { }) } -class DeferredSpec extends ScalaTestWithActorTestKit(""" - akka.loggers = [akka.testkit.TestEventListener] - """) with WordSpecLike { +class DeferredSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { import DeferredSpec._ implicit val testSettings = TestKitSettings(system) - // FIXME #24348: eventfilter support in typed testkit - import scaladsl.adapter._ - implicit val classicSystem = system.toClassic - "Deferred behavior" must { "must create underlying" in { val probe = TestProbe[Event]("evt") @@ -68,7 +62,7 @@ class DeferredSpec extends ScalaTestWithActorTestKit(""" Behaviors.stopped } } - EventFilter[ActorInitializationException](occurrences = 1).intercept { + LoggingEventFilter.error[ActorInitializationException].intercept { spawn(behv) probe.expectMessage(Started) probe.expectMessage(Pong) @@ -144,7 +138,7 @@ class DeferredSpec extends ScalaTestWithActorTestKit(""" Behaviors.same } } - EventFilter[ActorInitializationException](occurrences = 1).intercept { + LoggingEventFilter.error[ActorInitializationException].intercept { val ref = spawn(behv) probe.expectTerminated(ref, probe.remainingOrDefault) } @@ -152,7 +146,7 @@ class DeferredSpec extends ScalaTestWithActorTestKit(""" } } -class DeferredStubbedSpec extends WordSpec with Matchers { +class DeferredStubbedSpec extends WordSpec with Matchers with LogCapturing { import DeferredSpec._ diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/ExtensionsSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/ExtensionsSpec.scala index 32512aca5a..ecb15a3818 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/ExtensionsSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/ExtensionsSpec.scala @@ -12,6 +12,7 @@ import scala.concurrent.Future import akka.actor.BootstrapSetup import akka.actor.setup.ActorSystemSetup import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.receptionist.Receptionist import akka.actor.typed.receptionist.ServiceKey import akka.actor.typed.scaladsl.Behaviors @@ -72,7 +73,7 @@ akka.actor.typed { """).resolve() } -class ExtensionsSpec extends ScalaTestWithActorTestKit with WordSpecLike { +class ExtensionsSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { "The extensions subsystem" must { "return the same instance for the same id" in diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/InterceptSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/InterceptSpec.scala index 521a571770..03c9089206 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/InterceptSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/InterceptSpec.scala @@ -6,14 +6,15 @@ package akka.actor.typed import java.util.concurrent.atomic.AtomicBoolean -import akka.testkit.EventFilter import akka.actor.testkit.typed.scaladsl.TestProbe import akka.actor.typed.scaladsl.Behaviors import org.scalatest.WordSpecLike import scala.concurrent.duration._ import akka.actor.ActorInitializationException +import akka.actor.testkit.typed.scaladsl.LoggingEventFilter import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.internal.PoisonPill import akka.actor.typed.internal.PoisonPillInterceptor @@ -74,16 +75,10 @@ object InterceptSpec { } } -class InterceptSpec extends ScalaTestWithActorTestKit(""" - akka.loggers = [akka.testkit.TestEventListener] - """) with WordSpecLike { +class InterceptSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { import BehaviorInterceptor._ import InterceptSpec._ - // FIXME #24348: eventfilter support in typed testkit - import scaladsl.adapter._ - implicit val classicSystem = system.toClassic - private def snitchingInterceptor(probe: ActorRef[String]) = new BehaviorInterceptor[String, String] { override def aroundReceive( context: TypedActorContext[String], @@ -287,7 +282,7 @@ class InterceptSpec extends ScalaTestWithActorTestKit(""" val probe = TestProbe[String]() val interceptor = snitchingInterceptor(probe.ref) - EventFilter[ActorInitializationException](occurrences = 1).intercept { + LoggingEventFilter.error[ActorInitializationException].intercept { val ref = spawn(Behaviors.intercept(() => interceptor)(Behaviors.setup[String] { _ => Behaviors.same[String] })) diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/LogMessagesSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/LogMessagesSpec.scala index 578ac273d0..1bb981c72c 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/LogMessagesSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/LogMessagesSpec.scala @@ -5,176 +5,134 @@ package akka.actor.typed import akka.actor +import akka.actor.testkit.typed.scaladsl.LoggingEventFilter import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.adapter._ -import akka.actor.typed.testkit.LoggingEventFilter import org.scalatest.WordSpecLike -import org.slf4j.{ Logger, LoggerFactory, MDC } import org.slf4j.event.Level -import org.slf4j.helpers.{ SubstituteLogger, SubstituteLoggerFactory } - -//TODO review akka.testkit.TestEventListener as config. Has quite important implications class LogMessagesSpec extends ScalaTestWithActorTestKit(""" akka.loglevel = DEBUG # test verifies debug - akka.loggers = ["akka.actor.typed.testkit.TestEventListener"] - """) with WordSpecLike { + """) with WordSpecLike with LogCapturing { implicit val classic: actor.ActorSystem = system.toClassic "The log messages behavior" should { "log messages and signals" in { + val behavior: Behavior[String] = Behaviors.logMessages(Behaviors.ignore) - val factory = new SubstituteLoggerFactory() - val substituteLogger: SubstituteLogger = factory.getLogger("substitute").asInstanceOf[SubstituteLogger] - val opts = LogOptions().withLevel(Level.DEBUG).withLogger(substituteLogger) - - val behavior: Behavior[String] = Behaviors.logMessages(opts, Behaviors.ignore) val ref: ActorRef[String] = spawn(behavior) - LoggingEventFilter - .debug(s"actor ${ref.path.toString} received message Hello", source = ref.path.toString, occurrences = 1) - .intercept(ref ! "Hello", factory.getEventQueue) + LoggingEventFilter.debug(s"actor [${ref.path.toString}] received message: Hello").intercept { + ref ! "Hello" + } - LoggingEventFilter - .debug(s"actor ${ref.path.toString} received signal PostStop", source = ref.path.toString, occurrences = 1) - .intercept(testKit.stop(ref), factory.getEventQueue) + LoggingEventFilter.debug(s"actor [${ref.path}] received signal: PostStop").intercept { + testKit.stop(ref) + } } "log messages with provided log level" in { - val factory = new SubstituteLoggerFactory() - val substituteLogger: SubstituteLogger = factory.getLogger("substitute").asInstanceOf[SubstituteLogger] - val opts = LogOptions().withLevel(Level.INFO).withLogger(substituteLogger) + val opts = LogOptions().withLevel(Level.INFO) val behavior: Behavior[String] = Behaviors.logMessages(opts, Behaviors.ignore) val ref: ActorRef[String] = spawn(behavior) - LoggingEventFilter - .info(s"actor ${ref.path.toString} received message Hello", source = ref.path.toString, occurrences = 1) - .intercept(ref ! "Hello", factory.getEventQueue) + LoggingEventFilter.info(s"actor [${ref.path}] received message: Hello").intercept { + ref ! "Hello" + } - LoggingEventFilter - .info(s"actor ${ref.path.toString} received signal PostStop", source = ref.path.toString, occurrences = 1) - .intercept(testKit.stop(ref), factory.getEventQueue) + LoggingEventFilter.info(s"actor [${ref.path}] received signal: PostStop").intercept { + testKit.stop(ref) + } } "log messages with provided logger" in { - - val factory = new SubstituteLoggerFactory() - val substituteLogger: SubstituteLogger = factory.getLogger("substitute").asInstanceOf[SubstituteLogger] - val opts = LogOptions().withLevel(Level.DEBUG).withLogger(substituteLogger) + val logger = system.log + val opts = LogOptions().withLogger(logger) val behavior: Behavior[String] = Behaviors.logMessages(opts, Behaviors.ignore) val ref: ActorRef[String] = spawn(behavior) - LoggingEventFilter - .debug(s"actor ${ref.path.toString} received message Hello", source = ref.path.toString, occurrences = 1) - .intercept(ref ! "Hello", factory.getEventQueue) + LoggingEventFilter.debug(s"actor [${ref.path}] received message: Hello").intercept { + ref ! "Hello" + } - LoggingEventFilter - .debug(s"actor ${ref.path.toString} received signal PostStop", source = ref.path.toString, occurrences = 1) - .intercept(testKit.stop(ref), factory.getEventQueue) + LoggingEventFilter.debug(s"actor [${ref.path}] received signal: PostStop").intercept { + testKit.stop(ref) + } } "not log messages when not enabled" in { - val factory = new SubstituteLoggerFactory() - val substituteLogger: SubstituteLogger = factory.getLogger("substitute").asInstanceOf[SubstituteLogger] - substituteLogger.setDelegate(LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)) - - val opts = LogOptions().withLevel(Level.DEBUG).withLogger(substituteLogger).withEnabled(false) + val opts = LogOptions().withEnabled(false) val behavior: Behavior[String] = Behaviors.logMessages(opts, Behaviors.ignore) val ref: ActorRef[String] = spawn(behavior) - LoggingEventFilter - .debug(s"actor ${ref.path.toString} received message Hello", source = ref.path.toString, occurrences = 0) - .intercept(ref ! "Hello", factory.getEventQueue) + LoggingEventFilter.debug(s"actor [${ref.path}] received message: Hello").withOccurrences(0).intercept { + ref ! "Hello" + } - LoggingEventFilter - .debug(s"actor ${ref.path.toString} received signal PostStop", source = ref.path.toString, occurrences = 0) - .intercept(testKit.stop(ref), factory.getEventQueue) + LoggingEventFilter.debug(s"actor [${ref.path}] received signal: PostStop").withOccurrences(0).intercept { + testKit.stop(ref) + } } "log messages with decorated MDC values" in { - val factory = new SubstituteLoggerFactory() - val substituteLogger: SubstituteLogger = factory.getLogger("substitute").asInstanceOf[SubstituteLogger] - - val opts = LogOptions().withLevel(Level.DEBUG).withLogger(substituteLogger) + val opts = LogOptions().withLevel(Level.DEBUG) val mdc = Map("mdc" -> "true") val behavior = Behaviors.withMdc[String](mdc)(Behaviors.logMessages(opts, Behaviors.ignore)) val ref = spawn(behavior) - LoggingEventFilter - .debug( - s"actor ${ref.path.toString} received message Hello MDC is $mdc", - source = ref.path.toString, - occurrences = 1) - .intercept(ref ! "Hello", factory.getEventQueue) - LoggingEventFilter - .debug( - s"actor ${ref.path.toString} received signal PostStop MDC is $mdc", - source = ref.path.toString, - occurrences = 1) - .intercept(testKit.stop(ref), factory.getEventQueue) + LoggingEventFilter.debug(s"actor [${ref.path}] received message: Hello").withMdc(mdc).intercept { + ref ! "Hello" + } + + LoggingEventFilter.debug(s"actor [${ref.path}] received signal: PostStop").withMdc(mdc).intercept { + testKit.stop(ref) + } } "log messages with different decorated MDC values in different actors" in { - val factory = new SubstituteLoggerFactory() - val substituteLogger: SubstituteLogger = factory.getLogger("substitute").asInstanceOf[SubstituteLogger] - - val opts = LogOptions().withLevel(Level.DEBUG).withLogger(substituteLogger) + val opts = LogOptions().withLevel(Level.DEBUG) val mdc1 = Map("mdc" -> "true") val behavior1 = Behaviors.withMdc[String](mdc1)(Behaviors.logMessages(opts, Behaviors.ignore)) val mdc2 = Map("mdc" -> "false") val behavior2 = Behaviors.withMdc[String](mdc2)(Behaviors.logMessages(opts, Behaviors.ignore)) val ref2 = spawn(behavior2) - LoggingEventFilter - .debug( - s"actor ${ref2.path.toString} received message Hello MDC is $mdc2", - source = ref2.path.toString, - occurrences = 1) - .intercept(ref2 ! "Hello", factory.getEventQueue) + + LoggingEventFilter.debug(s"actor [${ref2.path}] received message: Hello").withMdc(mdc2).intercept { + ref2 ! "Hello" + } val ref1 = spawn(behavior1) - LoggingEventFilter - .debug( - s"actor ${ref1.path.toString} received message Hello MDC is $mdc1", - source = ref1.path.toString, - occurrences = 1) - .intercept(ref1 ! "Hello", factory.getEventQueue) - LoggingEventFilter - .debug( - s"actor ${ref2.path.toString} received signal PostStop MDC is $mdc2", - source = ref2.path.toString, - occurrences = 1) - .intercept(testKit.stop(ref2), factory.getEventQueue) + LoggingEventFilter.debug(s"actor [${ref1.path}] received message: Hello").withMdc(mdc1).intercept { + ref1 ! "Hello" + } - LoggingEventFilter - .debug( - s"actor ${ref1.path.toString} received signal PostStop MDC is $mdc1", - source = ref1.path.toString, - occurrences = 1) - .intercept(testKit.stop(ref1), factory.getEventQueue) + LoggingEventFilter.debug(s"actor [${ref2.path}] received signal: PostStop").withMdc(mdc2).intercept { + testKit.stop(ref2) + } + LoggingEventFilter.debug(s"actor [${ref1.path}] received signal: PostStop").withMdc(mdc1).intercept { + testKit.stop(ref1) + } } "log messages of different type" in { - val factory = new SubstituteLoggerFactory() - val substituteLogger: Logger = factory.getLogger("substitute").asInstanceOf[SubstituteLogger] - val opts = LogOptions().withLevel(Level.DEBUG).withLogger(substituteLogger) - - val behavior: Behavior[String] = Behaviors.logMessages(opts, Behaviors.ignore[String]) + val behavior: Behavior[String] = Behaviors.logMessages(Behaviors.ignore[String]) val ref = spawn(behavior) - LoggingEventFilter - .debug(s"actor ${ref.path.toString} received message 13", source = ref.path.toString, occurrences = 1) - .intercept(ref.unsafeUpcast[Any] ! 13, factory.getEventQueue) + LoggingEventFilter.debug(s"actor [${ref.path}] received message: 13").intercept { + ref.unsafeUpcast[Any] ! 13 + } } } diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/MailboxSelectorSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/MailboxSelectorSpec.scala index a3c28b014d..bd0886af10 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/MailboxSelectorSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/MailboxSelectorSpec.scala @@ -4,8 +4,13 @@ package akka.actor.typed +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + import akka.actor.ActorCell +import akka.actor.testkit.typed.scaladsl.LoggingEventFilter import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.internal.adapter.ActorContextAdapter import akka.actor.typed.scaladsl.AskPattern._ import akka.actor.typed.scaladsl.Behaviors @@ -13,24 +18,14 @@ import akka.dispatch.BoundedMessageQueueSemantics import akka.dispatch.BoundedNodeMessageQueue import akka.dispatch.MessageQueue import akka.dispatch.UnboundedMessageQueueSemantics -import akka.testkit.EventFilter -import akka.testkit.TestLatch import org.scalatest.WordSpecLike -import scala.concurrent.Await -import scala.concurrent.duration._ - class MailboxSelectorSpec extends ScalaTestWithActorTestKit(""" specific-mailbox { mailbox-type = "akka.dispatch.NonBlockingBoundedMailbox" mailbox-capacity = 4 } - akka.loggers = [ akka.testkit.TestEventListener ] - """) with WordSpecLike { - - // FIXME #24348: eventfilter support in typed testkit - import scaladsl.adapter._ - implicit val classicSystem = system.toClassic + """) with WordSpecLike with LogCapturing { case class WhatsYourMailbox(replyTo: ActorRef[MessageQueue]) private def behavior: Behavior[WhatsYourMailbox] = @@ -65,24 +60,24 @@ class MailboxSelectorSpec extends ScalaTestWithActorTestKit(""" } "set capacity on a bounded mailbox" in { - val latch = TestLatch(1) + val latch = new CountDownLatch(1) val actor = spawn(Behaviors.receiveMessage[String] { case "one" => // block here so we can fill mailbox up - Await.ready(latch, 10.seconds) + latch.await(10, TimeUnit.SECONDS) Behaviors.same case _ => Behaviors.same }, MailboxSelector.bounded(2)) actor ! "one" // actor will block here actor ! "two" - EventFilter.warning(start = "received dead letter:", occurrences = 1).intercept { + LoggingEventFilter.deadLetters().intercept { // one or both of these doesn't fit in mailbox // depending on race with how fast actor consumes actor ! "three" actor ! "four" } - latch.open() + latch.countDown() } "select an arbitrary mailbox from config" in { diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/MonitorSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/MonitorSpec.scala index be0678b667..69a65f2b6a 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/MonitorSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/MonitorSpec.scala @@ -6,10 +6,11 @@ package akka.actor.typed import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.testkit.typed.scaladsl.TestProbe +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.scaladsl.Behaviors import org.scalatest.WordSpecLike -class MonitorSpec extends ScalaTestWithActorTestKit with WordSpecLike { +class MonitorSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { "The monitor behavior" should { diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/OrElseSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/OrElseSpec.scala index 15539144a5..cb7a95f4e5 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/OrElseSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/OrElseSpec.scala @@ -183,7 +183,7 @@ object OrElseSpec { } -class OrElseSpec extends WordSpec with Matchers { +class OrElseSpec extends WordSpec with Matchers with LogCapturing { import OrElseSpec._ diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/PropsSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/PropsSpec.scala index 7946e20a67..6ca9e3b81d 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/PropsSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/PropsSpec.scala @@ -4,10 +4,11 @@ package akka.actor.typed +import akka.actor.testkit.typed.scaladsl.LogCapturing import org.scalatest.Matchers import org.scalatest.WordSpec -class PropsSpec extends WordSpec with Matchers { +class PropsSpec extends WordSpec with Matchers with LogCapturing { val dispatcherFirst = Props.empty.withDispatcherFromConfig("pool").withDispatcherDefault diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/SpawnProtocolSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/SpawnProtocolSpec.scala index 92e53d1ebc..1133ed03d7 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/SpawnProtocolSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/SpawnProtocolSpec.scala @@ -26,7 +26,7 @@ object SpawnProtocolSpec { } } -class SpawnProtocolSpec extends ScalaTestWithActorTestKit with WordSpecLike { +class SpawnProtocolSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { import SpawnProtocolSpec._ implicit val testSettings = TestKitSettings(system) @@ -96,7 +96,7 @@ class SpawnProtocolSpec extends ScalaTestWithActorTestKit with WordSpecLike { } } -class StubbedSpawnProtocolSpec extends WordSpec with Matchers { +class StubbedSpawnProtocolSpec extends WordSpec with Matchers with LogCapturing { import SpawnProtocolSpec._ diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/SupervisionSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/SupervisionSpec.scala index 8316998978..03ee41154f 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/SupervisionSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/SupervisionSpec.scala @@ -7,21 +7,25 @@ package akka.actor.typed import java.io.IOException import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.{ AtomicBoolean, AtomicInteger } +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger + +import scala.concurrent.duration._ +import scala.util.control.NoStackTrace import akka.actor.ActorInitializationException -import akka.actor.typed.scaladsl.{ AbstractBehavior, Behaviors } -import akka.actor.typed.scaladsl.Behaviors._ -import akka.testkit.EventFilter -import akka.actor.testkit.typed.scaladsl._ -import akka.actor.testkit.typed._ -import org.scalatest.{ Matchers, WordSpec, WordSpecLike } - -import scala.util.control.NoStackTrace -import scala.concurrent.duration._ import akka.actor.Dropped +import akka.actor.testkit.typed._ +import akka.actor.testkit.typed.scaladsl.LoggingEventFilter +import akka.actor.testkit.typed.scaladsl._ import akka.actor.typed.SupervisorStrategy.Resume -import akka.event.Logging +import akka.actor.typed.eventstream.EventStream +import akka.actor.typed.scaladsl.Behaviors._ +import akka.actor.typed.scaladsl.AbstractBehavior +import akka.actor.typed.scaladsl.Behaviors +import org.scalatest.Matchers +import org.scalatest.WordSpec +import org.scalatest.WordSpecLike import org.slf4j.event.Level object SupervisionSpec { @@ -88,7 +92,7 @@ object SupervisionSpec { } } -class StubbedSupervisionSpec extends WordSpec with Matchers { +class StubbedSupervisionSpec extends WordSpec with Matchers with LogCapturing { import SupervisionSpec._ @@ -248,21 +252,15 @@ class StubbedSupervisionSpec extends WordSpec with Matchers { } class SupervisionSpec extends ScalaTestWithActorTestKit(""" - akka.loggers = [akka.testkit.TestEventListener] akka.log-dead-letters = off - """) with WordSpecLike { + """) with WordSpecLike with LogCapturing { - import SupervisionSpec._ import BehaviorInterceptor._ + import SupervisionSpec._ private val nameCounter = Iterator.from(0) private def nextName(prefix: String = "a"): String = s"$prefix-${nameCounter.next()}" - // FIXME #24348: eventfilter support in typed testkit - import akka.actor.typed.scaladsl.adapter._ - - implicit val classicSystem = system.toClassic - class FailingConstructorTestSetup(failCount: Int) { val failCounter = new AtomicInteger(0) class FailingConstructor(monitor: ActorRef[Event]) extends AbstractBehavior[Command] { @@ -315,7 +313,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" val probe = TestProbe[Event]("evt") val behv = targetBehavior(probe.ref) val ref = spawn(behv) - EventFilter[Exc3](occurrences = 1).intercept { + LoggingEventFilter.error[Exc3].intercept { ref ! Throw(new Exc3) probe.expectMessage(ReceivedSignal(PostStop)) probe.expectTerminated(ref) @@ -325,7 +323,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" val probe = TestProbe[Event]("evt") val behv = Behaviors.supervise(targetBehavior(probe.ref)).onFailure[Throwable](SupervisorStrategy.stop) val ref = spawn(behv) - EventFilter[Exc3](occurrences = 1).intercept { + LoggingEventFilter.error[Exc3].intercept { ref ! Throw(new Exc3) probe.expectMessage(ReceivedSignal(PostStop)) probe.expectTerminated(ref) @@ -339,7 +337,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" targetBehavior(probe.ref) }) val behv = Behaviors.supervise(failedSetup).onFailure[Throwable](SupervisorStrategy.stop) - EventFilter[Exc3](occurrences = 1).intercept { + LoggingEventFilter.error[Exc3].intercept { spawn(behv) } } @@ -352,12 +350,12 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" val ref = spawn(behv) - EventFilter[IOException](occurrences = 1).intercept { + LoggingEventFilter.error[IOException].intercept { ref ! Throw(new IOException()) probe.expectMessage(ReceivedSignal(PreRestart)) } - EventFilter[IllegalArgumentException](occurrences = 1).intercept { + LoggingEventFilter.error[IllegalArgumentException].intercept { ref ! Throw(new IllegalArgumentException("cat")) probe.expectMessage(ReceivedSignal(PostStop)) } @@ -373,7 +371,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" val ref = spawn(behv) - EventFilter[Exception](occurrences = 1).intercept { + LoggingEventFilter.error[Exception].intercept { ref ! Throw(new IOException()) probe.expectMessage(ReceivedSignal(PreRestart)) } @@ -381,7 +379,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" ref ! Ping(1) probe.expectMessage(Pong(1)) - EventFilter[IllegalArgumentException](occurrences = 1).intercept { + LoggingEventFilter.error[IllegalArgumentException].intercept { ref ! Throw(new IllegalArgumentException("cat")) probe.expectMessage(ReceivedSignal(PreRestart)) } @@ -399,7 +397,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" val ref = spawn(behv) - EventFilter[Exception](occurrences = 1).intercept { + LoggingEventFilter.error[Exception].intercept { ref ! Throw(new IOException()) probe.expectMessage(ReceivedSignal(PreRestart)) } @@ -407,7 +405,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" ref ! Ping(1) probe.expectMessage(Pong(1)) - EventFilter[IllegalArgumentException](occurrences = 1).intercept { + LoggingEventFilter.error[IllegalArgumentException].intercept { ref ! Throw(new IllegalArgumentException("cat")) probe.expectMessage(ReceivedSignal(PreRestart)) } @@ -421,7 +419,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" val probe = TestProbe[Event]("evt") val behv = targetBehavior(probe.ref) val ref = spawn(behv) - EventFilter[Exc3](occurrences = 1).intercept { + LoggingEventFilter.error[Exc3].intercept { ref ! Throw(new Exc3) probe.expectMessage(ReceivedSignal(PostStop)) } @@ -431,7 +429,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" val probe = TestProbe[Event]("evt") val behv = Behaviors.supervise(targetBehavior(probe.ref)).onFailure[Exc1](SupervisorStrategy.restart) val ref = spawn(behv) - EventFilter[Exc3](occurrences = 1).intercept { + LoggingEventFilter.error[Exc3].intercept { ref ! Throw(new Exc3) probe.expectMessage(ReceivedSignal(PostStop)) } @@ -445,7 +443,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" ref ! GetState probe.expectMessage(State(1, Map.empty)) - EventFilter[Exc2](occurrences = 1).intercept { + LoggingEventFilter.error[Exc2].intercept { ref ! Throw(new Exc2) probe.expectMessage(ReceivedSignal(PreRestart)) } @@ -464,7 +462,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" ref ! GetState probe.expectMessage(State(1, Map.empty)) - EventFilter[Exc2](occurrences = 3).intercept { + LoggingEventFilter.error[Exc2].withOccurrences(3).intercept { ref ! Throw(new Exc2) probe.expectMessage(ReceivedSignal(PreRestart)) ref ! Throw(new Exc2) @@ -472,10 +470,8 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" ref ! Throw(new Exc2) probe.expectMessage(ReceivedSignal(PostStop)) } - EventFilter.warning(start = "received dead letter", occurrences = 1).intercept { - ref ! GetState - probe.expectNoMessage() - } + + probe.expectTerminated(ref) } "reset fixed limit after timeout" in { @@ -489,7 +485,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" ref ! GetState probe.expectMessage(State(1, Map.empty)) - EventFilter[Exc2](occurrences = 3).intercept { + LoggingEventFilter.error[Exc2].withOccurrences(3).intercept { ref ! Throw(new Exc2) probe.expectMessage(ReceivedSignal(PreRestart)) ref ! Throw(new Exc2) @@ -529,7 +525,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" ref ! GetState parentProbe.expectMessageType[State].children.keySet should ===(Set(child1Name, child2Name)) - EventFilter[Exc1](occurrences = 1).intercept { + LoggingEventFilter.error[Exc1].intercept { ref ! Throw(new Exc1) parentProbe.expectMessage(ReceivedSignal(PreRestart)) ref ! GetState @@ -567,7 +563,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" ref ! GetState parentProbe.expectMessageType[State].children.keySet should contain(childName) - EventFilter[Exc1](occurrences = 1).intercept { + LoggingEventFilter.error[Exc1].intercept { ref ! Throw(new Exc1) parentProbe.expectMessage(ReceivedSignal(PreRestart)) ref ! GetState @@ -598,7 +594,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" val child2Name = nextName() - EventFilter[Exc1](occurrences = 1).intercept { + LoggingEventFilter.error[Exc1].intercept { ref ! Throw(new Exc1) parentProbe.expectMessage(ReceivedSignal(PreRestart)) ref ! GetState @@ -607,7 +603,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" ref ! Throw(new Exc1) } - EventFilter[Exc1](occurrences = 1).intercept { + LoggingEventFilter.error[Exc1].intercept { slowStop.countDown() childProbe.expectMessage(ReceivedSignal(PostStop)) // child1 parentProbe.expectMessageType[State].children.keySet should ===(Set.empty) @@ -652,7 +648,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" } .onFailure[RuntimeException](strategy) - EventFilter[TestException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { val ref = spawn(behv) slowStop1.countDown() child1Probe.expectMessage(ReceivedSignal(PostStop)) @@ -703,12 +699,12 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" throwFromSetup.set(true) - EventFilter[Exc1](occurrences = 1).intercept { + LoggingEventFilter.error[Exc1].intercept { ref ! Throw(new Exc1) parentProbe.expectMessage(ReceivedSignal(PreRestart)) } - EventFilter[TestException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { slowStop1.countDown() child1Probe.expectMessage(ReceivedSignal(PostStop)) child1Probe.expectMessage(ReceivedSignal(PostStop)) @@ -729,7 +725,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" ref ! GetState probe.expectMessage(State(1, Map.empty)) - EventFilter[Exc2](occurrences = 1).intercept { + LoggingEventFilter.error[Exc2].intercept { ref ! Throw(new Exc2) ref ! GetState probe.expectMessage(State(1, Map.empty)) @@ -747,7 +743,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" probe.expectMessage(State(1, Map.empty)) // resume - EventFilter[Exc2](occurrences = 1).intercept { + LoggingEventFilter.error[Exc2].intercept { ref ! Throw(new Exc2) probe.expectNoMessage() ref ! GetState @@ -755,7 +751,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" } // restart - EventFilter[Exc3](occurrences = 1).intercept { + LoggingEventFilter.error[Exc3].intercept { ref ! Throw(new Exc3) probe.expectMessage(ReceivedSignal(PreRestart)) ref ! GetState @@ -763,13 +759,16 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" } // stop - EventFilter[Exc1](occurrences = 1).intercept { + LoggingEventFilter.error[Exc1].intercept { ref ! Throw(new Exc1) probe.expectMessage(ReceivedSignal(PostStop)) } } "publish dropped messages while backing off and stash is full" in { + import akka.actor.typed.scaladsl.adapter._ + val droppedMessagesProbe = TestProbe[Dropped]() + system.eventStream ! EventStream.Subscribe(droppedMessagesProbe.ref) val probe = TestProbe[Event]("evt") val startedProbe = TestProbe[Event]("started") val minBackoff = 1.seconds @@ -781,27 +780,27 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" }) .onFailure[Exception](strategy) - val droppedMessagesProbe = TestProbe[Dropped]() - system.toClassic.eventStream.subscribe(droppedMessagesProbe.ref.toClassic, classOf[Dropped]) val ref = spawn(behv) - EventFilter[Exc1](occurrences = 1).intercept { + LoggingEventFilter.error[Exc1].intercept { startedProbe.expectMessage(Started) ref ! Throw(new Exc1) probe.expectMessage(ReceivedSignal(PreRestart)) } - EventFilter.warning(start = "dropped message", occurrences = 2).intercept { - ref ! Ping(1) - ref ! Ping(2) - ref ! Ping(3) - ref ! Ping(4) - probe.expectMessage(Pong(1)) - probe.expectMessage(Pong(2)) - droppedMessagesProbe.expectMessage(Dropped(Ping(3), "Stash is full in [RestartSupervisor]", ref.toClassic)) - droppedMessagesProbe.expectMessage(Dropped(Ping(4), "Stash is full in [RestartSupervisor]", ref.toClassic)) - } + + ref ! Ping(1) + ref ! Ping(2) + ref ! Ping(3) + ref ! Ping(4) + probe.expectMessage(Pong(1)) + probe.expectMessage(Pong(2)) + droppedMessagesProbe.expectMessage(Dropped(Ping(3), "Stash is full in [RestartSupervisor]", ref.toClassic)) + droppedMessagesProbe.expectMessage(Dropped(Ping(4), "Stash is full in [RestartSupervisor]", ref.toClassic)) } "restart after exponential backoff" in { + import akka.actor.typed.scaladsl.adapter._ + val droppedMessagesProbe = TestProbe[Dropped]() + system.eventStream ! EventStream.Subscribe(droppedMessagesProbe.ref) val probe = TestProbe[Event]("evt") val startedProbe = TestProbe[Event]("started") val minBackoff = 1.seconds @@ -817,14 +816,13 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" .onFailure[Exception](strategy) val ref = spawn(behv) - EventFilter[Exc1](occurrences = 1).intercept { + LoggingEventFilter.error[Exc1].intercept { startedProbe.expectMessage(Started) ref ! IncrementState ref ! Throw(new Exc1) probe.expectMessage(ReceivedSignal(PreRestart)) - EventFilter.warning(start = "dropped message", occurrences = 1).intercept { - ref ! Ping(1) // dropped due to backoff, no stashing - } + ref ! Ping(1) // dropped due to backoff, no stashing + droppedMessagesProbe.expectMessage(Dropped(Ping(1), "Stash is full in [RestartSupervisor]", ref.toClassic)) } startedProbe.expectNoMessage(minBackoff - 100.millis) @@ -834,13 +832,12 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" probe.expectMessage(State(0, Map.empty)) // one more time - EventFilter[Exc1](occurrences = 1).intercept { + LoggingEventFilter.error[Exc1].intercept { ref ! IncrementState ref ! Throw(new Exc1) probe.expectMessage(ReceivedSignal(PreRestart)) - EventFilter.warning(start = "dropped message", occurrences = 1).intercept { - ref ! Ping(2) // dropped due to backoff, no stashing - } + ref ! Ping(2) // dropped due to backoff, no stashing + droppedMessagesProbe.expectMessage(Dropped(Ping(2), "Stash is full in [RestartSupervisor]", ref.toClassic)) } startedProbe.expectNoMessage((minBackoff * 2) - 100.millis) @@ -873,8 +870,8 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" .onFailure[Exception](strategy) val ref = spawn(behv) - EventFilter[Exc1](occurrences = 1).intercept { - EventFilter[TestException](occurrences = 2).intercept { + LoggingEventFilter.error[Exc1].intercept { + LoggingEventFilter.error[TestException].withOccurrences(2).intercept { startedProbe.expectMessage(Started) ref ! Throw(new Exc1) probe.expectTerminated(ref, 3.seconds) @@ -883,6 +880,9 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" } "reset exponential backoff count after reset timeout" in { + import akka.actor.typed.scaladsl.adapter._ + val droppedMessagesProbe = TestProbe[Dropped]() + system.eventStream ! EventStream.Subscribe(droppedMessagesProbe.ref) val probe = TestProbe[Event]("evt") val minBackoff = 1.seconds val strategy = SupervisorStrategy @@ -892,13 +892,12 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" val behv = supervise(targetBehavior(probe.ref)).onFailure[Exc1](strategy) val ref = spawn(behv) - EventFilter[Exc1](occurrences = 1).intercept { + LoggingEventFilter.error[Exc1].intercept { ref ! IncrementState ref ! Throw(new Exc1) probe.expectMessage(ReceivedSignal(PreRestart)) - EventFilter.warning(start = "dropped message", occurrences = 1).intercept { - ref ! Ping(1) // dropped due to backoff, no stash - } + ref ! Ping(1) // dropped due to backoff, no stash + droppedMessagesProbe.expectMessage(Dropped(Ping(1), "Stash is full in [RestartSupervisor]", ref.toClassic)) } probe.expectNoMessage(minBackoff + 100.millis.dilated) @@ -906,14 +905,13 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" probe.expectMessage(State(0, Map.empty)) // one more time after the reset timeout - EventFilter[Exc1](occurrences = 1).intercept { + LoggingEventFilter.error[Exc1].intercept { probe.expectNoMessage(strategy.resetBackoffAfter + 100.millis.dilated) ref ! IncrementState ref ! Throw(new Exc1) probe.expectMessage(ReceivedSignal(PreRestart)) - EventFilter.warning(start = "dropped message", occurrences = 1).intercept { - ref ! Ping(2) // dropped due to backoff - } + ref ! Ping(2) // dropped due to backoff + droppedMessagesProbe.expectMessage(Dropped(Ping(2), "Stash is full in [RestartSupervisor]", ref.toClassic)) } // backoff was reset, so restarted after the minBackoff @@ -938,7 +936,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" failCount = 1, strategy = SupervisorStrategy.restart) { - EventFilter[ActorInitializationException](occurrences = 1).intercept { + LoggingEventFilter.error[ActorInitializationException].intercept { spawn(behv) } } @@ -946,7 +944,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" "fail to restart when deferred factory throws unhandled" in new FailingUnhandledTestSetup( strategy = SupervisorStrategy.restart) { - EventFilter[ActorInitializationException](occurrences = 1).intercept { + LoggingEventFilter.error[ActorInitializationException].intercept { spawn(behv) } } @@ -954,8 +952,8 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" "fail to resume when deferred factory throws" in new FailingDeferredTestSetup( failCount = 1, strategy = SupervisorStrategy.resume) { - EventFilter[TestException](occurrences = 1).intercept { - EventFilter[ActorInitializationException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { + LoggingEventFilter.error[ActorInitializationException].intercept { spawn(behv) } } @@ -965,7 +963,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" failCount = 1, strategy = SupervisorStrategy.restartWithBackoff(minBackoff = 100.millis.dilated, maxBackoff = 1.second, 0)) { - EventFilter[TestException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { spawn(behv) probe.expectMessage(StartFailed) @@ -978,7 +976,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" "fail instead of restart with exponential backoff when deferred factory throws unhandled" in new FailingUnhandledTestSetup( strategy = SupervisorStrategy.restartWithBackoff(minBackoff = 100.millis.dilated, maxBackoff = 1.second, 0)) { - EventFilter[ActorInitializationException](occurrences = 1).intercept { + LoggingEventFilter.error[ActorInitializationException].intercept { spawn(behv) probe.expectMessage(StartFailed) } @@ -988,7 +986,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" failCount = 1, strategy = SupervisorStrategy.restart.withLimit(3, 1.second)) { - EventFilter[TestException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { spawn(behv) probe.expectMessage(StartFailed) @@ -1000,8 +998,8 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" failCount = 20, strategy = SupervisorStrategy.restart.withLimit(2, 1.second)) { - EventFilter[ActorInitializationException](occurrences = 1).intercept { - EventFilter[TestException](occurrences = 2).intercept { + LoggingEventFilter.error[ActorInitializationException].intercept { + LoggingEventFilter.error[TestException].withOccurrences(2).intercept { spawn(behv) // first one from initial setup @@ -1017,7 +1015,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" "fail instead of restart with limit when deferred factory throws unhandled" in new FailingUnhandledTestSetup( strategy = SupervisorStrategy.restart.withLimit(3, 1.second)) { - EventFilter[ActorInitializationException](occurrences = 1).intercept { + LoggingEventFilter.error[ActorInitializationException].intercept { spawn(behv) probe.expectMessage(StartFailed) } @@ -1028,7 +1026,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" val behv = supervise(setup[Command](_ => new FailingConstructor(probe.ref))) .onFailure[Exception](SupervisorStrategy.restart) - EventFilter[ActorInitializationException](occurrences = 1).intercept { + LoggingEventFilter.error[ActorInitializationException].intercept { spawn(behv) probe.expectMessage(Started) // first one before failure } @@ -1073,7 +1071,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" actor ! "ping" probe.expectMessage("pong") - EventFilter[RuntimeException](occurrences = 1).intercept { + LoggingEventFilter.error[RuntimeException].intercept { // Should be supervised as resume actor ! "boom" } @@ -1121,7 +1119,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" actor ! "ping" probe.expectMessage("pong") - EventFilter[RuntimeException](occurrences = 1).intercept { + LoggingEventFilter.error[RuntimeException].intercept { // Should be supervised as resume actor ! "boom" } @@ -1165,7 +1163,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" probe.expectMessage("started 1") ref ! "ping" probe.expectMessage("pong 1") - EventFilter[TestException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { ref ! "boom" probe.expectMessage("crashing 1") ref ! "ping" @@ -1175,10 +1173,12 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" probe.expectMessage("pong 2") // from "ping" that was stashed ref ! "ping" probe.expectMessage("pong 2") - EventFilter[TestException](occurrences = 1).intercept { - ref ! "boom" // now we should have replaced supervision with the resuming one - probe.expectMessage("crashing 2") - } + LoggingEventFilter + .error[TestException] + .intercept { + ref ! "boom" // now we should have replaced supervision with the resuming one + probe.expectMessage("crashing 2") + }(system) ref ! "ping" probe.expectMessage("pong 2") } @@ -1208,7 +1208,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" }) .onFailure[DeathPactException](SupervisorStrategy.restart)) - EventFilter[DeathPactException](occurrences = 1).intercept { + LoggingEventFilter.error[DeathPactException].intercept { actor ! "boom" val child = probe.expectMessageType[ActorRef[_]] probe.expectTerminated(child, 3.seconds) @@ -1223,7 +1223,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" .supervise(targetBehavior(probe.ref)) .onFailure[Exc1](SupervisorStrategy.restart.withLoggingEnabled(true).withLogLevel(Level.INFO)) val ref = spawn(behv) - EventFilter.info(pattern = "exc-1", source = ref.path.toString, occurrences = 1).intercept { + LoggingEventFilter.info("exc-1").intercept { ref ! Throw(new Exc1) probe.expectMessage(ReceivedSignal(PreRestart)) } @@ -1235,7 +1235,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" .supervise(targetBehavior(probe.ref)) .onFailure[Exc1](SupervisorStrategy.restart.withLoggingEnabled(true).withLogLevel(Level.DEBUG)) val ref = spawn(behv) - EventFilter.info(pattern = "exc-1", source = ref.path.toString, occurrences = 0).intercept { + LoggingEventFilter.info("exc-1").withSource(ref.path.toString).withOccurrences(0).intercept { ref ! Throw(new Exc1) probe.expectMessage(ReceivedSignal(PreRestart)) } @@ -1263,7 +1263,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" ref ! Ping(1) probe.expectMessage(Pong(1)) - EventFilter[Exc1](occurrences = 1).intercept { + LoggingEventFilter.error[Exc1].intercept { ref.unsafeUpcast ! "boom" probe.expectMessage(ReceivedSignal(PreRestart)) } @@ -1311,7 +1311,7 @@ class SupervisionSpec extends ScalaTestWithActorTestKit(""" }) .onFailure[TestException](strategy)) - EventFilter[TestException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { actor ! "boom" } createTestProbe().expectTerminated(actor, 3.second) diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/TerminatedSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/TerminatedSpec.scala index d5370e6e1a..e954407f76 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/TerminatedSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/TerminatedSpec.scala @@ -5,9 +5,10 @@ package akka.actor.typed import akka.actor.testkit.typed.scaladsl.TestInbox +import akka.actor.testkit.typed.scaladsl.LogCapturing import org.scalatest.{ Matchers, WordSpec } -class TerminatedSpec extends WordSpec with Matchers { +class TerminatedSpec extends WordSpec with Matchers with LogCapturing { "Child Failed" must { "should be pattern matchable" in { diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/TimerSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/TimerSpec.scala index cc2bc4b4c3..221b8be3d7 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/TimerSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/TimerSpec.scala @@ -13,19 +13,15 @@ import scala.util.control.NoStackTrace import akka.actor.DeadLetter import akka.actor.testkit.typed.TestException +import akka.actor.testkit.typed.scaladsl.LoggingEventFilter import akka.actor.testkit.typed.scaladsl._ +import akka.actor.typed.eventstream.EventStream import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.TimerScheduler -import akka.testkit.{ EventFilter, TimingTest } +import akka.testkit.TimingTest import org.scalatest.WordSpecLike -class TimerSpec extends ScalaTestWithActorTestKit(""" - akka.loggers = [ akka.testkit.TestEventListener ] - """) with WordSpecLike { - - // FIXME #24348: eventfilter support in typed testkit - import scaladsl.adapter._ - implicit val classicSystem = system.toClassic +class TimerSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { sealed trait Command case class Tick(n: Int) extends Command @@ -179,7 +175,7 @@ class TimerSpec extends ScalaTestWithActorTestKit(""" probe.expectMessage(Tock(1)) val latch = new CountDownLatch(1) - EventFilter[Exc](occurrences = 1).intercept { + LoggingEventFilter.error[Exc].intercept { // next Tock(1) is enqueued in mailbox, but should be discarded by new incarnation ref ! SlowThenThrow(latch, new Exc) @@ -209,7 +205,7 @@ class TimerSpec extends ScalaTestWithActorTestKit(""" probe.expectMessage(Tock(2)) - EventFilter[Exc](occurrences = 1).intercept { + LoggingEventFilter.error[Exc].intercept { val latch = new CountDownLatch(1) // next Tock(2) is enqueued in mailbox, but should be discarded by new incarnation ref ! SlowThenThrow(latch, new Exc) @@ -230,7 +226,7 @@ class TimerSpec extends ScalaTestWithActorTestKit(""" target(probe.ref, timer, 1) } val ref = spawn(behv) - EventFilter[Exc](occurrences = 1).intercept { + LoggingEventFilter.error[Exc].intercept { ref ! Throw(new Exc) probe.expectMessage(GotPostStop(false)) } @@ -300,7 +296,7 @@ class TimerSpec extends ScalaTestWithActorTestKit(""" } "not leak timers when PostStop is used" in { - val probe = TestProbe[Any]() + val probe = TestProbe[DeadLetter]() val ref = spawn(Behaviors.withTimers[String] { timers => Behaviors.setup { _ => timers.startTimerWithFixedDelay("test", "test", 250.millis) @@ -309,11 +305,11 @@ class TimerSpec extends ScalaTestWithActorTestKit(""" } } }) - EventFilter.info("stopping").intercept { + LoggingEventFilter.info("stopping").intercept { ref ! "stop" } probe.expectTerminated(ref) - system.toClassic.eventStream.subscribe(probe.ref.toClassic, classOf[DeadLetter]) + system.eventStream ! EventStream.Subscribe(probe.ref) probe.expectNoMessage(1.second) } } @@ -348,7 +344,7 @@ class TimerSpec extends ScalaTestWithActorTestKit(""" Behaviors.unhandled } - EventFilter[TestException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { val ref = spawn(Behaviors.supervise(behv).onFailure[TestException](SupervisorStrategy.restart)) ref ! Tick(-1) probe.expectMessage(Tock(-1)) @@ -383,7 +379,7 @@ class TimerSpec extends ScalaTestWithActorTestKit(""" Behaviors.unhandled } - EventFilter[TestException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { val ref = spawn(Behaviors.supervise(behv).onFailure[TestException](SupervisorStrategy.restart)) ref ! Tick(-1) probe.expectMessage(Tock(-1)) diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/TransformMessagesSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/TransformMessagesSpec.scala index b48fda98b4..8e6eed4e5a 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/TransformMessagesSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/TransformMessagesSpec.scala @@ -11,10 +11,12 @@ import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.testkit.typed.scaladsl.TestProbe import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.adapter._ -import akka.testkit.EventFilter import org.scalatest.WordSpecLike import scala.concurrent.duration._ +import akka.actor.testkit.typed.scaladsl.LoggingEventFilter +import akka.actor.testkit.typed.scaladsl.LogCapturing + object TransformMessagesSpec { // this is the sample from the Scaladoc @@ -31,9 +33,7 @@ object TransformMessagesSpec { } } -class TransformMessagesSpec extends ScalaTestWithActorTestKit(""" - akka.loggers = [akka.testkit.TestEventListener] - """) with WordSpecLike { +class TransformMessagesSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { implicit val classicSystem = system.toClassic @@ -62,14 +62,11 @@ class TransformMessagesSpec extends ScalaTestWithActorTestKit(""" val probe = TestProbe[String]() val ref = spawn(intToString(probe.ref)) - // TestEventListener logs unhandled as warnings, silence that - EventFilter.warning(occurrences = 1).intercept { - ref ! 42 - ref ! 13 - ref ! 43 - probe.expectMessage("42") - probe.expectMessage("43") - } + ref ! 42 + ref ! 13 + ref ! 43 + probe.expectMessage("42") + probe.expectMessage("43") } "not build up when the same transformMessages is used many times (initially)" in { @@ -137,7 +134,7 @@ class TransformMessagesSpec extends ScalaTestWithActorTestKit(""" case s => s.toLowerCase } - EventFilter[ActorInitializationException](occurrences = 1).intercept { + LoggingEventFilter.error[ActorInitializationException].intercept { val ref = spawn(transform(transform(Behaviors.receiveMessage[String] { _ => Behaviors.same }))) diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/WatchSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/WatchSpec.scala index ef26168619..e866d00ac6 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/WatchSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/WatchSpec.scala @@ -8,20 +8,17 @@ import akka.Done import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.AbstractBehavior import akka.actor.typed.scaladsl.adapter._ -import akka.testkit.EventFilter import akka.actor.testkit.typed.scaladsl.TestProbe - import scala.concurrent._ import scala.concurrent.duration._ + import akka.actor.testkit.typed.TestException +import akka.actor.testkit.typed.scaladsl.LoggingEventFilter import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit -import com.typesafe.config.ConfigFactory +import akka.actor.testkit.typed.scaladsl.LogCapturing import org.scalatest.WordSpecLike object WatchSpec { - val config = ConfigFactory.parseString(""" - akka.loggers = ["akka.testkit.TestEventListener"] - """.stripMargin) case object Stop @@ -44,7 +41,7 @@ object WatchSpec { case class StartWatchingWith(watchee: ActorRef[Stop.type], message: CustomTerminationMessage) extends Message } -class WatchSpec extends ScalaTestWithActorTestKit(WatchSpec.config) with WordSpecLike { +class WatchSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { implicit def classicSystem = system.toClassic @@ -110,7 +107,7 @@ class WatchSpec extends ScalaTestWithActorTestKit(WatchSpec.config) with WordSpe }, "supervised-child-parent") - EventFilter[TestException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { parent ! "boom" } probe.expectMessageType[ChildHasFailed].t.cause shouldEqual ex @@ -145,7 +142,7 @@ class WatchSpec extends ScalaTestWithActorTestKit(WatchSpec.config) with WordSpe } val parent = spawn(behavior, "parent") - EventFilter[TestException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { parent ! "boom" } probe.expectMessageType[ChildHasFailed].t.cause shouldEqual ex @@ -184,8 +181,8 @@ class WatchSpec extends ScalaTestWithActorTestKit(WatchSpec.config) with WordSpe }, "grosso-bosso") - EventFilter[TestException](occurrences = 1).intercept { - EventFilter[DeathPactException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { + LoggingEventFilter.error[DeathPactException].intercept { grossoBosso ! "boom" } } @@ -325,7 +322,9 @@ class WatchSpec extends ScalaTestWithActorTestKit(WatchSpec.config) with WordSpe "fail when watch is used after watchWith on same subject" in new ErrorTestSetup { watcher ! StartWatchingWith(terminator, CustomTerminationMessage) - EventFilter[IllegalStateException](pattern = ".*termination message was not overwritten.*", occurrences = 1) + LoggingEventFilter + .error[IllegalStateException] + .withMessageContains("termination message was not overwritten") .intercept { watcher ! StartWatching(terminator) } @@ -336,7 +335,9 @@ class WatchSpec extends ScalaTestWithActorTestKit(WatchSpec.config) with WordSpe "fail when watchWitch is used after watchWith with different termination message" in new ErrorTestSetup { watcher ! StartWatchingWith(terminator, CustomTerminationMessage) - EventFilter[IllegalStateException](pattern = ".*termination message was not overwritten.*", occurrences = 1) + LoggingEventFilter + .error[IllegalStateException] + .withMessageContains("termination message was not overwritten") .intercept { watcher ! StartWatchingWith(terminator, CustomTerminationMessage2) } @@ -346,7 +347,9 @@ class WatchSpec extends ScalaTestWithActorTestKit(WatchSpec.config) with WordSpe "fail when watchWith is used after watch on same subject" in new ErrorTestSetup { watcher ! StartWatching(terminator) - EventFilter[IllegalStateException](pattern = ".*termination message was not overwritten.*", occurrences = 1) + LoggingEventFilter + .error[IllegalStateException] + .withMessageContains("termination message was not overwritten") .intercept { watcher ! StartWatchingWith(terminator, CustomTerminationMessage) } diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/coexistence/TypedSupervisingClassicSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/coexistence/TypedSupervisingClassicSpec.scala index 7f82a9d1fa..e0b6321221 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/coexistence/TypedSupervisingClassicSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/coexistence/TypedSupervisingClassicSpec.scala @@ -5,6 +5,7 @@ package akka.actor.typed.coexistence import akka.actor.Actor import akka.actor.testkit.typed.TestException +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.testkit.typed.scaladsl.{ ScalaTestWithActorTestKit, TestProbe } import akka.actor.typed.ActorRef import akka.actor.typed.scaladsl.Behaviors @@ -42,7 +43,7 @@ object TypedSupervisingClassicSpec { class TypedSupervisingClassicSpec extends ScalaTestWithActorTestKit(""" akka.loglevel = INFO - """.stripMargin) with WordSpecLike { + """.stripMargin) with WordSpecLike with LogCapturing { import TypedSupervisingClassicSpec._ "Typed supervising classic" should { diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/eventstream/EventStreamSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/eventstream/EventStreamSpec.scala index 0065ad389f..f404a3b028 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/eventstream/EventStreamSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/eventstream/EventStreamSpec.scala @@ -6,10 +6,11 @@ package akka.actor.typed.eventstream import scala.concurrent.duration._ +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.testkit.typed.scaladsl.{ ScalaTestWithActorTestKit, TestProbe } import org.scalatest.WordSpecLike -class EventStreamSpec extends ScalaTestWithActorTestKit with WordSpecLike { +class EventStreamSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { import EventStreamSpec._ import EventStream._ diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/internal/ActorRefSerializationSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/internal/ActorRefSerializationSpec.scala index 27f79fed56..8f3596c359 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/internal/ActorRefSerializationSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/internal/ActorRefSerializationSpec.scala @@ -9,6 +9,7 @@ import akka.actor.typed.scaladsl.adapter._ import akka.actor.typed.ActorRef import akka.serialization.{ JavaSerializer, SerializationExtension } import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import com.typesafe.config.ConfigFactory import org.scalatest.WordSpecLike @@ -27,7 +28,10 @@ object ActorRefSerializationSpec { case class MessageWrappingActorRef(s: String, ref: ActorRef[Unit]) extends java.io.Serializable } -class ActorRefSerializationSpec extends ScalaTestWithActorTestKit(ActorRefSerializationSpec.config) with WordSpecLike { +class ActorRefSerializationSpec + extends ScalaTestWithActorTestKit(ActorRefSerializationSpec.config) + with WordSpecLike + with LogCapturing { val serialization = SerializationExtension(system.toClassic) diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/internal/ActorSystemSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/internal/ActorSystemSpec.scala index 2515b420e5..d40045c4d6 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/internal/ActorSystemSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/internal/ActorSystemSpec.scala @@ -14,13 +14,20 @@ import akka.Done import akka.actor.CoordinatedShutdown import akka.actor.InvalidMessageException import akka.actor.testkit.typed.scaladsl.TestInbox +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.adapter._ import org.scalatest._ import org.scalatest.concurrent.Eventually import org.scalatest.concurrent.ScalaFutures -class ActorSystemSpec extends WordSpec with Matchers with BeforeAndAfterAll with ScalaFutures with Eventually { +class ActorSystemSpec + extends WordSpec + with Matchers + with BeforeAndAfterAll + with ScalaFutures + with Eventually + with LogCapturing { override implicit val patienceConfig = PatienceConfig(1.second) def system[T](behavior: Behavior[T], name: String) = ActorSystem(behavior, name) diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/internal/receptionist/LocalReceptionistSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/internal/receptionist/LocalReceptionistSpec.scala index b644c31bc1..16d9c24d7d 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/internal/receptionist/LocalReceptionistSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/internal/receptionist/LocalReceptionistSpec.scala @@ -5,10 +5,12 @@ package akka.actor.typed.internal.receptionist import scala.concurrent.Future + import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.testkit.typed.scaladsl.BehaviorTestKit import akka.actor.testkit.typed.scaladsl.TestInbox import akka.actor.testkit.typed.scaladsl.TestProbe +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed._ import akka.actor.typed.receptionist.Receptionist import akka.actor.typed.receptionist.Receptionist._ @@ -36,7 +38,7 @@ object LocalReceptionistSpec { } -class LocalReceptionistSpec extends ScalaTestWithActorTestKit with WordSpecLike { +class LocalReceptionistSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { import LocalReceptionistSpec._ abstract class TestSetup { @@ -128,7 +130,7 @@ class LocalReceptionistSpec extends ScalaTestWithActorTestKit with WordSpecLike } } -class LocalReceptionistBehaviorSpec extends WordSpec with Matchers { +class LocalReceptionistBehaviorSpec extends WordSpec with Matchers with LogCapturing { import LocalReceptionistSpec._ def assertEmpty(inboxes: TestInbox[_]*): Unit = { diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/internal/receptionist/ServiceKeySerializationSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/internal/receptionist/ServiceKeySerializationSpec.scala index 9736978dc7..5fd7908902 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/internal/receptionist/ServiceKeySerializationSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/internal/receptionist/ServiceKeySerializationSpec.scala @@ -9,11 +9,13 @@ import akka.actor.typed.receptionist.ServiceKey import akka.actor.typed.scaladsl.adapter._ import akka.serialization.SerializationExtension import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import org.scalatest.WordSpecLike class ServiceKeySerializationSpec extends ScalaTestWithActorTestKit(ActorRefSerializationSpec.config) - with WordSpecLike { + with WordSpecLike + with LogCapturing { val serialization = SerializationExtension(system.toClassic) diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/internal/routing/RoutingLogicSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/internal/routing/RoutingLogicSpec.scala index fd4867cc32..cc5d7e9de7 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/internal/routing/RoutingLogicSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/internal/routing/RoutingLogicSpec.scala @@ -5,10 +5,11 @@ package akka.actor.typed.internal.routing import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.testkit.typed.scaladsl.TestProbe +import akka.actor.testkit.typed.scaladsl.LogCapturing import org.scalatest.Matchers import org.scalatest.WordSpecLike -class RoutingLogicSpec extends ScalaTestWithActorTestKit with WordSpecLike with Matchers { +class RoutingLogicSpec extends ScalaTestWithActorTestKit with WordSpecLike with Matchers with LogCapturing { "The round robin routing logic" must { diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/ActorContextAskSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/ActorContextAskSpec.scala index 26d66ed8fd..7978b56014 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/ActorContextAskSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/ActorContextAskSpec.scala @@ -4,22 +4,21 @@ package akka.actor.typed.scaladsl -import akka.actor.typed.scaladsl.adapter._ import akka.actor.typed.{ ActorRef, PostStop, Props } -import akka.testkit.EventFilter import akka.actor.testkit.typed.scaladsl.TestProbe import com.typesafe.config.ConfigFactory - import scala.concurrent.TimeoutException import scala.concurrent.duration._ import scala.reflect.ClassTag import scala.util.{ Failure, Success } + +import akka.actor.testkit.typed.scaladsl.LoggingEventFilter import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import org.scalatest.WordSpecLike object ActorContextAskSpec { val config = ConfigFactory.parseString(""" - akka.loggers = ["akka.testkit.TestEventListener"] ping-pong-dispatcher { executor = thread-pool-executor type = PinnedDispatcher @@ -31,9 +30,10 @@ object ActorContextAskSpec { """) } -class ActorContextAskSpec extends ScalaTestWithActorTestKit(ActorContextAskSpec.config) with WordSpecLike { - - implicit val classic = system.toClassic // FIXME #24348: eventfilter support in testkit +class ActorContextAskSpec + extends ScalaTestWithActorTestKit(ActorContextAskSpec.config) + with WordSpecLike + with LogCapturing { "The Scala DSL ActorContext" must { @@ -104,7 +104,7 @@ class ActorContextAskSpec extends ScalaTestWithActorTestKit(ActorContextAskSpec. } } - EventFilter[NotImplementedError](occurrences = 1, start = "Pong").intercept { + LoggingEventFilter.error[NotImplementedError].withMessageContains("Pong").intercept { spawn(snitch) } @@ -126,11 +126,7 @@ class ActorContextAskSpec extends ScalaTestWithActorTestKit(ActorContextAskSpec. } } - EventFilter.warning(occurrences = 1, message = "received dead letter: boo").intercept { - EventFilter.info(occurrences = 1, start = "Message [java.lang.String]").intercept { - spawn(snitch) - } - } + spawn(snitch) val exc = probe.expectMessageType[TimeoutException] exc.getMessage should include("had already been terminated") diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/ActorContextPipeToSelfSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/ActorContextPipeToSelfSpec.scala index 76809c4d5c..495e26bb0f 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/ActorContextPipeToSelfSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/ActorContextPipeToSelfSpec.scala @@ -8,6 +8,7 @@ import scala.concurrent.Future import scala.util.control.NoStackTrace import scala.util.{ Failure, Success } +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.testkit.typed.scaladsl.{ ScalaTestWithActorTestKit, TestProbe } import akka.actor.typed.Props import com.typesafe.config.ConfigFactory @@ -24,7 +25,8 @@ object ActorContextPipeToSelfSpec { final class ActorContextPipeToSelfSpec extends ScalaTestWithActorTestKit(ActorContextPipeToSelfSpec.config) - with WordSpecLike { + with WordSpecLike + with LogCapturing { "The Scala DSL ActorContext pipeToSelf" must { "handle success" in { responseFrom(Future.successful("hi")) should ===("ok: hi") } diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/ActorLoggingSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/ActorLoggingSpec.scala index b2fe0851c4..833f662bed 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/ActorLoggingSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/ActorLoggingSpec.scala @@ -5,20 +5,24 @@ package akka.actor.typed.scaladsl import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicReference import akka.actor.testkit.typed.TestException +import akka.actor.testkit.typed.scaladsl.ActorTestKit +import akka.actor.testkit.typed.scaladsl.LoggingEventFilter import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing +import akka.actor.typed.Behavior import akka.actor.typed.scaladsl.adapter._ -import akka.actor.typed.testkit.LoggingEventFilter._ -import akka.actor.typed.testkit.{ AppenderInterceptor, LoggingEventFilter } -import akka.actor.typed.{ ActorRef, Behavior, LogOptions } -import akka.event.Logging -import akka.event.Logging.{ LogEvent, LogEventWithCause, LogEventWithMarker } -import akka.testkit.EventFilter -import ch.qos.logback.classic.spi.ILoggingEvent +import akka.event.DefaultLoggingFilter +import akka.event.Logging.DefaultLogger +import akka.event.slf4j.Slf4jLogger +import akka.event.slf4j.Slf4jLoggingFilter +import com.typesafe.config.ConfigFactory import org.scalatest.WordSpecLike -import org.slf4j.event.{ Level, LoggingEvent } -import org.slf4j.helpers.{ BasicMarkerFactory, SubstituteLogger, SubstituteLoggerFactory } +import org.slf4j.LoggerFactory +import org.slf4j.MDC +import org.slf4j.helpers.BasicMarkerFactory class SomeClass @@ -43,128 +47,120 @@ class BehaviorWhereTheLoggerIsUsed(context: ActorContext[String]) extends Abstra class ActorLoggingSpec extends ScalaTestWithActorTestKit(""" akka.loglevel = DEBUG # test verifies debug - akka.loggers = ["akka.actor.typed.testkit.TestEventListener"] - """) with WordSpecLike { + """) with WordSpecLike with LogCapturing { val marker = new BasicMarkerFactory().getMarker("marker") - val cause = new TestException("böö") + val cause = TestException("böö") implicit val classic = system.toClassic + class AnotherLoggerClass + "Logging in an actor" must { - "log messages and signals" in { - - val factory = new SubstituteLoggerFactory() - val substituteLogger: SubstituteLogger = factory.getLogger("substitute").asInstanceOf[SubstituteLogger] - val opts = LogOptions().withLevel(Level.DEBUG).withLogger(substituteLogger) - - val behavior: Behavior[String] = Behaviors.logMessages(opts, Behaviors.ignore) - val ref: ActorRef[String] = spawn(behavior) - - debug(s"actor ${ref.path.toString} received message Hello", source = ref.path.toString, occurrences = 1) - .intercept(ref ! "Hello", factory.getEventQueue) - - debug(s"actor ${ref.path.toString} received signal PostStop", source = ref.path.toString, occurrences = 1) - .intercept(testKit.stop(ref), factory.getEventQueue) - } - - //TODO be aware of context.log.xyz logging are very different as they aren't created by the InterceptorImpl! @see LogMessagesInterceptor.aroundReceive - // That's why AppenderInterceptor.events approach has been taken "be conveniently available from the context" in { val behavior: Behavior[String] = Behaviors.setup[String] { context => - println(s"context out ${context.executionContext.hashCode()}") context.log.info("Started") Behaviors.receive { (context, message) => - println(s"context in ${context.executionContext.hashCode()}") - context.log.info("got message {}", message) Behaviors.same } } - val actor = LoggingEventFilter - .info("Started", occurrences = 1) - .interceptIt(spawn(behavior, "the-actor"), AppenderInterceptor.events) + val actor = LoggingEventFilter.info("Started").intercept(spawn(behavior, "the-actor")) + LoggingEventFilter.info("got message Hello").intercept(actor ! "Hello") + + } + + "log with custom Logger class" in { + val behavior: Behavior[String] = Behaviors.setup[String] { context => + context.setLoggerName(classOf[AnotherLoggerClass]) + context.log.info("Started") + + Behaviors.receive { (context, message) => + context.log.info("got message {}", message) + Behaviors.same + } + } + + val actor = + LoggingEventFilter.info("Started").withLoggerName(classOf[AnotherLoggerClass].getName).intercept { + spawn(behavior, "the-other-actor") + } + + // verify that it's logged with `AnotherLoggerClass` + // verify that it's only capturing log events for that logger and not any other logger when interceptLogger + // is used + val count = new AtomicInteger LoggingEventFilter - .info("got message Hello", occurrences = 1) - .interceptIt(actor ! "Hello", AppenderInterceptor.events) + .custom { logEvent => + count.incrementAndGet() + logEvent.message == "got message Hello" && logEvent.loggerName == classOf[AnotherLoggerClass].getName + } + .withLoggerName(classOf[AnotherLoggerClass].getName) + .withOccurrences(2) + .intercept { + actor ! "Hello" + LoggerFactory.getLogger(classOf[ActorLoggingSpec]).debug("Hello from other logger") + actor ! "Hello" + } + count.get should ===(2) } "contain the class name where the first log was called" in { - val eventFilter = custom({ - case l: LoggingEvent if l.getLoggerName == classOf[ActorLoggingSpec].getName => + val eventFilter = LoggingEventFilter.custom({ + case event if event.loggerName == classOf[ActorLoggingSpec].getName => true - case l: LoggingEvent => - println(l.getLoggerName) + case event => + println(event.loggerName) false - }, occurrences = 1) + }) - eventFilter.interceptIt(spawn(Behaviors.setup[String] { context => - context.log.info("Started") + eventFilter.intercept(spawn(Behaviors.setup[String] { context => + context.log.info("Started") - Behaviors.receive { (context, message) => - context.log.info("got message {}", message) - Behaviors.same - } - }, "the-actor-with-class"), AppenderInterceptor.events) + Behaviors.receive { (context, message) => + context.log.info("got message {}", message) + Behaviors.same + } + }, "the-actor-with-class")) } - //TODO all below "contain the object class name where the first log was called" in { - val eventFilter = EventFilter.custom({ - case l: LogEvent if l.logClass == WhereTheBehaviorIsDefined.getClass => true - case l: LogEvent => - println(l.logClass) + val eventFilter = LoggingEventFilter.custom({ + case event if event.loggerName == WhereTheBehaviorIsDefined.getClass.getName => true + case other => + println(other.loggerName) false - }, occurrences = 1) + }) - eventFilter.intercept { - spawn(WhereTheBehaviorIsDefined.behavior, "the-actor-with-object") - } + eventFilter.intercept(spawn(WhereTheBehaviorIsDefined.behavior, "the-actor-with-object")) } "contain the abstract behavior class name where the first log was called" in { - val eventFilter = EventFilter.custom({ - case l: LogEvent if l.logClass == classOf[BehaviorWhereTheLoggerIsUsed] => true - case l: LogEvent => - println(l.logClass) + val eventFilter = LoggingEventFilter.custom({ + case event if event.loggerName == classOf[BehaviorWhereTheLoggerIsUsed].getName => true + case other => + println(other.loggerName) false - }, occurrences = 1) + }) eventFilter.intercept { - spawn(BehaviorWhereTheLoggerIsUsed.behavior, "the-actor-with-behavior") + spawn(Behaviors.setup[String](context => new BehaviorWhereTheLoggerIsUsed(context)), "the-actor-with-behavior") } } - "allow for adapting log source and class" in { - //TODO WIP... - val eventFilter = custom({ - case l: ILoggingEvent => - l.getLoggerName == classOf[SomeClass] && - l.getCallerData == "who-knows-where-it-came-from" && - l.getMDCPropertyMap == Map("mdc" -> true) // mdc should be kept - }, occurrences = 1) - - spawn(Behaviors.setup[String] { context => - context.log.info("Started") - Behaviors.empty - }, "the-actor-with-custom-class") - Thread.sleep(1) - eventFilter.interceptIt(println(""), AppenderInterceptor.events) - - } - "pass markers to the log" in { - EventFilter - .custom({ - case event: LogEventWithMarker if event.marker.name == marker.getName => true - }, occurrences = 9) + LoggingEventFilter + .custom { event => + event.marker.map(_.getName) == Option(marker.getName) + } + .withOccurrences(5) .intercept(spawn(Behaviors.setup[Any] { context => context.log.debug(marker, "whatever") context.log.info(marker, "whatever") @@ -176,10 +172,11 @@ class ActorLoggingSpec extends ScalaTestWithActorTestKit(""" } "pass cause with warn" in { - EventFilter - .custom({ - case event: LogEventWithCause if event.cause == cause => true - }, occurrences = 2) + LoggingEventFilter + .custom { event => + event.throwable == Option(cause) + } + .withOccurrences(2) .intercept(spawn(Behaviors.setup[Any] { context => context.log.warn("whatever", cause) context.log.warn(marker, "whatever", cause) @@ -191,56 +188,108 @@ class ActorLoggingSpec extends ScalaTestWithActorTestKit(""" // Not the best test but at least it exercises every log overload ;) - EventFilter - .custom({ - case _ => true // any is fine, we're just after the right count of statements reaching the listener - }, occurrences = 120) - .intercept { - spawn(Behaviors.setup[String] { context => - context.log.debug("message") - context.log.debug("{}", "arg1") - context.log - .debug("{} {}", "arg1", "arg2": Any) //using Int to avoid ambiguous reference to overloaded definition - context.log.debug("{} {} {}", "arg1", "arg2", "arg3") - context.log.debug(marker, "message") - context.log.debug(marker, "{}", "arg1") - context.log.debug(marker, "{} {}", "arg1", "arg2": Any) //using Int to avoid ambiguous reference to overloaded definition - context.log.debug(marker, "{} {} {}", "arg1", "arg2", "arg3") - - context.log.info("message") - context.log.info("{}", "arg1") - context.log.info("{} {}", "arg1", "arg2": Any) - context.log.info("{} {} {}", "arg1", "arg2", "arg3") - context.log.info(marker, "message") - context.log.info(marker, "{}", "arg1") - context.log.info(marker, "{} {}", "arg1", "arg2": Any) - context.log.info(marker, "{} {} {}", "arg1", "arg2", "arg3") - - context.log.warn("message") - context.log.warn("{}", "arg1") - context.log.warn("{} {}", "arg1", "arg2": Any) - context.log.warn("{} {} {}", "arg1", "arg2", "arg3") - context.log.warn(marker, "message") - context.log.warn(marker, "{}", "arg1") - context.log.warn(marker, "{} {}", "arg1", "arg2": Any) - context.log.warn(marker, "{} {} {}", "arg1", "arg2", "arg3") - context.log.warn("message", cause) - - context.log.error("message") - context.log.error("{}", "arg1") - context.log.error("{} {}", "arg1", "arg2": Any) - context.log.error("{} {} {}", "arg1", "arg2", "arg3") - context.log.error(marker, "message") - context.log.error(marker, "{}", "arg1") - context.log.error(marker, "{} {}", "arg1", "arg2": Any) - context.log.error(marker, "{} {} {}", "arg1", "arg2", "arg3") - context.log.error("message", cause) - - Behaviors.stopped - }) + LoggingEventFilter + .custom { _ => + true // any is fine, we're just after the right count of statements reaching the listener } + .withOccurrences(36) + .intercept({ + spawn(Behaviors.setup[String] { + context => + context.log.debug("message") + context.log.debug("{}", "arg1") + // using `: Any` to avoid "ambiguous reference to overloaded definition", see also LoggerOpsSpec + context.log.debug("{} {}", "arg1", "arg2": Any) + context.log.debug("{} {} {}", "arg1", "arg2", "arg3") + context.log.debug(marker, "message") + context.log.debug(marker, "{}", "arg1") + context.log.debug(marker, "{} {}", "arg1", "arg2": Any) + context.log.debug(marker, "{} {} {}", "arg1", "arg2", "arg3") + + context.log.info("message") + context.log.info("{}", "arg1") + context.log.info("{} {}", "arg1", "arg2": Any) + context.log.info("{} {} {}", "arg1", "arg2", "arg3") + context.log.info(marker, "message") + context.log.info(marker, "{}", "arg1") + context.log.info(marker, "{} {}", "arg1", "arg2": Any) + context.log.info(marker, "{} {} {}", "arg1", "arg2", "arg3") + + context.log.warn("message") + context.log.warn("{}", "arg1") + context.log.warn("{} {}", "arg1", "arg2": Any) + context.log.warn("{} {} {}", "arg1", "arg2", "arg3") + context.log.warn(marker, "message") + context.log.warn(marker, "{}", "arg1") + context.log.warn(marker, "{} {}", "arg1", "arg2": Any) + context.log.warn(marker, "{} {} {}", "arg1", "arg2", "arg3") + context.log.warn("message", cause) + + context.log.error("message") + context.log.error("{}", "arg1") + context.log.error("{} {}", "arg1", "arg2": Any) + context.log.error("{} {} {}", "arg1", "arg2", "arg3") + // using to avoid vararg problem for primitive type, see also LoggerOpsSpec + context.log.error("{} {} {}", "arg1", "arg2", 3.asInstanceOf[AnyRef]) + context.log.error(marker, "message") + context.log.error(marker, "{}", "arg1") + context.log.error(marker, "{} {}", "arg1", "arg2": Any) + context.log.error(marker, "{} {} {}", "arg1", "arg2", "arg3") + context.log.error(marker, "{} {} {}", "arg1", "arg2", 3.asInstanceOf[AnyRef]) + context.log.error("message", cause) + + Behaviors.stopped + }) + }) } + "use Slf4jLogger from akka-slf4j automatically" in { + LoggingEventFilter.info("via Slf4jLogger").intercept { + // this will log via classic eventStream + system.toClassic.log.info("via Slf4jLogger") + } + } + + } + + "SLF4J Settings" must { + import akka.actor.typed.scaladsl.adapter._ + import akka.actor.ExtendedActorSystem + import akka.actor.{ ActorSystem => ClassicActorSystem } + + "by default be amended to use Slf4jLogger" in { + system.settings.config.getStringList("akka.loggers").size() should ===(1) + system.settings.config.getStringList("akka.loggers").get(0) should ===(classOf[Slf4jLogger].getName) + system.settings.config.getString("akka.logging-filter") should ===(classOf[Slf4jLoggingFilter].getName) + + system.toClassic.settings.Loggers should ===(List(classOf[Slf4jLogger].getName)) + system.toClassic.settings.LoggingFilter should ===(classOf[Slf4jLoggingFilter].getName) + } + + "by default be amended to use Slf4jLogger when starting classic ActorSystem" in { + val classicSys = akka.actor.ActorSystem(system.name) + try { + classicSys.settings.config.getStringList("akka.loggers").size() should ===(1) + classicSys.settings.config.getStringList("akka.loggers").get(0) should ===(classOf[Slf4jLogger].getName) + classicSys.settings.config.getString("akka.logging-filter") should ===(classOf[Slf4jLoggingFilter].getName) + + classicSys.settings.Loggers should ===(List(classOf[Slf4jLogger].getName)) + classicSys.settings.LoggingFilter should ===(classOf[Slf4jLoggingFilter].getName) + + } finally { + ActorTestKit.shutdown(classicSys.toTyped) + } + } + + "not be amended when use-slf4j=off" in { + val dynamicAccess = system.toClassic.asInstanceOf[ExtendedActorSystem].dynamicAccess + val config = ClassicActorSystem.Settings.amendSlf4jConfig( + ConfigFactory.parseString("akka.use-slf4j = off").withFallback(ConfigFactory.defaultReference()), + dynamicAccess) + config.getStringList("akka.loggers").size() should ===(1) + config.getStringList("akka.loggers").get(0) should ===(classOf[DefaultLogger].getName) + config.getString("akka.logging-filter") should ===(classOf[DefaultLoggingFilter].getName) + } } trait Protocol { @@ -254,7 +303,6 @@ class ActorLoggingSpec extends ScalaTestWithActorTestKit(""" val behaviors = Behaviors.withMdc[Protocol]( Map("static" -> "1"), // FIXME why u no infer the type here Scala?? - //TODO review that change from Map[String,Any] to Map[String,String] is viable (message: Protocol) => if (message.transactionId == 1) Map("txId" -> message.transactionId.toString, "first" -> "true") @@ -268,47 +316,28 @@ class ActorLoggingSpec extends ScalaTestWithActorTestKit(""" } } - // mdc on defer is empty (thread and timestamp MDC is added by logger backend) - val ref = EventFilter - .custom( - { - case logEvent if logEvent.level == Logging.InfoLevel => - logEvent.message should ===("Starting") - logEvent.mdc shouldBe empty - true - case other => system.log.error(s"Unexpected log event: {}", other); false - }, - occurrences = 1) + // mdc on defer is empty + val ref = LoggingEventFilter + .info("Starting") + // not counting for example "akkaSource", but it shouldn't have any other entries + .withCustom(logEvent => logEvent.mdc.keysIterator.forall(_.startsWith("akka"))) .intercept { spawn(behaviors) } // mdc on message - EventFilter - .custom( - { - case logEvent if logEvent.level == Logging.InfoLevel => - logEvent.message should ===("Got message!") - logEvent.mdc should ===(Map("static" -> 1, "txId" -> 1L, "first" -> true)) - true - case other => system.log.error(s"Unexpected log event: {}", other); false - }, - occurrences = 1) + LoggingEventFilter + .info("Got message!") + .withMdc(Map("static" -> "1", "txId" -> "1", "first" -> "true")) .intercept { ref ! Message(1, "first") } // mdc does not leak between messages - EventFilter - .custom( - { - case logEvent if logEvent.level == Logging.InfoLevel => - logEvent.message should ===("Got message!") - logEvent.mdc should ===(Map("static" -> 1, "txId" -> 2L)) - true - case other => system.log.error(s"Unexpected log event: {}", other); false - }, - occurrences = 1) + LoggingEventFilter + .info("Got message!") + .withMdc(Map("static" -> "1", "txId" -> "2")) + .withCustom(event => !event.mdc.contains("first")) .intercept { ref ! Message(2, "second") } @@ -327,16 +356,10 @@ class ActorLoggingSpec extends ScalaTestWithActorTestKit(""" } val ref = spawn(behavior) - EventFilter - .custom( - { - case logEvent if logEvent.level == Logging.InfoLevel => - logEvent.message should ===("message") - logEvent.mdc should ===(Map("outermost" -> true)) - true - case other => system.log.error(s"Unexpected log event: {}", other); false - }, - occurrences = 1) + LoggingEventFilter + .info("message") + .withMdc(Map("outermost" -> "true")) + .withCustom(event => !event.mdc.contains("innermost")) .intercept { ref ! "message" } @@ -355,32 +378,15 @@ class ActorLoggingSpec extends ScalaTestWithActorTestKit(""" } val ref = spawn(Behaviors.withMdc(Map("hasMdc" -> "true"))(behavior)) - EventFilter - .custom( - { - case logEvent if logEvent.level == Logging.InfoLevel => - logEvent.message should ===("message") - logEvent.mdc should ===(Map("hasMdc" -> true)) - true - case other => system.log.error(s"Unexpected log event: {}", other); false - }, - occurrences = 1) - .intercept { - ref ! "message" - } + LoggingEventFilter.info("message").withMdc(Map("hasMdc" -> "true")).intercept { + ref ! "message" + } ref ! "new-behavior" - EventFilter - .custom( - { - case logEvent if logEvent.level == Logging.InfoLevel => - logEvent.message should ===("message") - logEvent.mdc should ===(Map("hasMdc" -> true)) // original mdc should stay - true - case other => system.log.error(s"Unexpected log event: {}", other); false - }, - occurrences = 1) + LoggingEventFilter + .info("message") + .withMdc(Map("hasMdc" -> "true")) // original mdc should stay .intercept { ref ! "message" } @@ -404,30 +410,13 @@ class ActorLoggingSpec extends ScalaTestWithActorTestKit(""" } val ref = spawn(behavior) - EventFilter - .custom( - { - case logEvent if logEvent.level == Logging.InfoLevel => - logEvent.message should ===("message") - logEvent.mdc should ===(Map("mdc-version" -> 1)) - true - case other => system.log.error(s"Unexpected log event: {}", other); false - }, - occurrences = 1) - .intercept { - ref ! "message" - } + LoggingEventFilter.info("message").withMdc(Map("mdc-version" -> "1")).intercept { + ref ! "message" + } ref ! "new-mdc" - EventFilter - .custom( - { - case logEvent if logEvent.level == Logging.InfoLevel => - logEvent.message should ===("message") - logEvent.mdc should ===(Map("mdc-version" -> 2)) // mdc should have been replaced - true - case other => system.log.error(s"Unexpected log event: {}", other); false - }, - occurrences = 1) + LoggingEventFilter + .info("message") + .withMdc(Map("mdc-version" -> "2")) // mdc should have been replaced .intercept { ref ! "message" } @@ -437,41 +426,55 @@ class ActorLoggingSpec extends ScalaTestWithActorTestKit(""" "provide a withMdc decorator" in { val behavior = Behaviors.withMdc[Protocol](Map("mdc" -> "outer"))(Behaviors.setup { context => Behaviors.receiveMessage { _ => - org.slf4j.MDC.put("mdc", "inner") - context.log.info("Got message log.withMDC!") - // after log.withMdc so we know it didn't change the outer mdc - context.log.info("Got message behavior.withMdc!") + context.log.info("first") + org.slf4j.MDC.put("mdc", "inner-" + org.slf4j.MDC.get("mdc")) + context.log.info("second") Behaviors.same } }) // mdc on message val ref = spawn(behavior) - EventFilter - .custom( - { - case logEvent if logEvent.level == Logging.InfoLevel => - logEvent.message should ===("Got message behavior.withMdc!") - logEvent.mdc should ===(Map("mdc" -> "outer")) - true - case other => system.log.error(s"Unexpected log event: {}", other); false - }, - occurrences = 1) - .intercept { - EventFilter - .custom( - { - case logEvent if logEvent.level == Logging.InfoLevel => - logEvent.message should ===("Got message log.withMDC!") - logEvent.mdc should ===(Map("mdc" -> "inner")) - true - case other => system.log.error(s"Unexpected log event: {}", other); false - }, - occurrences = 1) - .intercept { - ref ! Message(1, "first") - } + LoggingEventFilter.info("first").withMdc(Map("mdc" -> "outer")).intercept { + LoggingEventFilter.info("second").withMdc(Map("mdc" -> "inner-outer")).intercept { + ref ! Message(1, "first") } + } + } + + "always include some MDC values in the log" in { + // need AtomicReference because LoggingFilter defined before actor is created and ActorTestKit names are dynamic + val actorPathStr = new AtomicReference[String] + val behavior = + Behaviors.setup[Message] { context => + actorPathStr.set(context.self.path.toString) + context.log.info("Starting") + Behaviors.receiveMessage { _ => + if (MDC.get("logSource") != null) + throw new IllegalStateException("MDC wasn't cleared. logSource has value before context.log is accessed.") + context.log.info("Got message!") + Behaviors.same + } + } + + // log from setup + // can't use LoggingEventFilter.withMdc here because the actorPathStr isn't know yet + val ref = + LoggingEventFilter.info("Starting").withCustom(event => event.mdc("akkaSource") == actorPathStr.get).intercept { + spawn(behavior) + } + + // on message + LoggingEventFilter + .info("Got message!") + .withMdc(Map("akkaSource" -> actorPathStr.get)) + .withOccurrences(10) + .intercept { + (1 to 10).foreach { n => + ref ! Message(n, s"msg-$n") + } + } + } } diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/DispatcherSelectorSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/DispatcherSelectorSpec.scala index fe2124e7eb..efd965951c 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/DispatcherSelectorSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/DispatcherSelectorSpec.scala @@ -9,6 +9,7 @@ import akka.actor.setup.ActorSystemSetup import akka.actor.testkit.typed.scaladsl.ActorTestKit import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.testkit.typed.scaladsl.TestProbe +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.ActorRef import akka.actor.typed.ActorSystem import akka.actor.typed.Behavior @@ -39,7 +40,10 @@ object DispatcherSelectorSpec { } -class DispatcherSelectorSpec extends ScalaTestWithActorTestKit(DispatcherSelectorSpec.config) with WordSpecLike { +class DispatcherSelectorSpec + extends ScalaTestWithActorTestKit(DispatcherSelectorSpec.config) + with WordSpecLike + with LogCapturing { import DispatcherSelectorSpec.PingPong import DispatcherSelectorSpec.PingPong._ diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/GracefulStopSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/GracefulStopSpec.scala index 8376401d4f..0740bbda58 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/GracefulStopSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/GracefulStopSpec.scala @@ -9,9 +9,10 @@ import akka.Done import akka.NotUsed import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.testkit.typed.scaladsl.TestProbe +import akka.actor.testkit.typed.scaladsl.LogCapturing import org.scalatest.WordSpecLike -final class GracefulStopSpec extends ScalaTestWithActorTestKit with WordSpecLike { +final class GracefulStopSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { "Graceful stop" must { diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/LoggerOpsSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/LoggerOpsSpec.scala new file mode 100644 index 0000000000..e9aa74b4df --- /dev/null +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/LoggerOpsSpec.scala @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2009-2019 Lightbend Inc. + */ + +package akka.actor.typed.scaladsl + +import akka.actor.testkit.typed.scaladsl.LoggingEventFilter +import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing +import org.scalatest.WordSpecLike +import org.slf4j.LoggerFactory + +object LoggerOpsSpec { + case class Value1(i: Int) + case class Value2(i: Int) + case class Value3(i: Int) +} + +class LoggerOpsSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { + import LoggerOpsSpec._ + + val log = LoggerFactory.getLogger(getClass) + + "LoggerOps" must { + + "provide extension method for 2 arguments" in { + LoggingEventFilter.info("[template a b]").intercept { + log.info2("[template {} {}]", "a", "b") + } + LoggingEventFilter.info("[template a 2]").intercept { + log.info2("[template {} {}]", "a", 2) + } + LoggingEventFilter.info("[template 1 2]").intercept { + log.info2("[template {} {}]", 1, 2) + } + LoggingEventFilter.info("[template 1 b]").intercept { + log.info2("[template {} {}]", 1, "b") + } + LoggingEventFilter.info("[template a Value2(2)]").intercept { + log.info2("[template {} {}]", "a", Value2(2)) + } + LoggingEventFilter.info("[template Value1(1) Value1(1)]").intercept { + log.info2("[template {} {}]", Value1(1), Value1(1)) + } + LoggingEventFilter.info("[template Value1(1) Value2(2)]").intercept { + log.info2("[template {} {}]", Value1(1), Value2(2)) + } + } + + "provide extension method for vararg arguments" in { + LoggingEventFilter.info("[template a b c]").intercept { + log.infoN("[template {} {} {}]", "a", "b", "c") + } + LoggingEventFilter.info("[template a b 3]").intercept { + log.infoN("[template {} {} {}]", "a", "b", 3) + } + LoggingEventFilter.info("[template a 2 c]").intercept { + log.infoN("[template {} {} {}]", "a", 2, "c") + } + LoggingEventFilter.info("[template 1 2 3]").intercept { + log.infoN("[template {} {} {}]", 1, 2, 3) + } + LoggingEventFilter.info("[template 1 b c]").intercept { + log.infoN("[template {} {} {}]", 1, "b", "c") + } + LoggingEventFilter.info("[template a Value2(2) Value3(3)]").intercept { + log.infoN("[template {} {} {}]", "a", Value2(2), Value3(3)) + } + LoggingEventFilter.info("[template Value1(1) Value1(1) Value1(1)]").intercept { + log.infoN("[template {} {} {}]", Value1(1), Value1(1), Value1(1)) + } + LoggingEventFilter.info("[template Value1(1) Value2(2) Value3(3)]").intercept { + log.infoN("[template {} {} {}]", Value1(1), Value2(2), Value3(3)) + } + } + } +} diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/MessageAdapterSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/MessageAdapterSpec.scala index ae90d30315..4e2f8839df 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/MessageAdapterSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/MessageAdapterSpec.scala @@ -4,21 +4,22 @@ package akka.actor.typed.scaladsl +import akka.actor.UnhandledMessage import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.testkit.typed.TestException -import akka.actor.typed.scaladsl.adapter._ +import akka.actor.testkit.typed.scaladsl.LoggingEventFilter import akka.actor.typed.ActorRef import akka.actor.typed.Behavior import akka.actor.typed.PostStop import akka.actor.typed.Props -import akka.testkit.EventFilter import akka.actor.testkit.typed.scaladsl.TestProbe +import akka.actor.testkit.typed.scaladsl.LogCapturing +import akka.actor.typed.eventstream.EventStream import com.typesafe.config.ConfigFactory import org.scalatest.WordSpecLike object MessageAdapterSpec { val config = ConfigFactory.parseString(""" - akka.loggers = ["akka.testkit.TestEventListener"] akka.log-dead-letters = off ping-pong-dispatcher { executor = thread-pool-executor @@ -31,9 +32,10 @@ object MessageAdapterSpec { """) } -class MessageAdapterSpec extends ScalaTestWithActorTestKit(MessageAdapterSpec.config) with WordSpecLike { - - implicit val classic = system.toClassic // FIXME #24348: eventfilter support in testkit +class MessageAdapterSpec + extends ScalaTestWithActorTestKit(MessageAdapterSpec.config) + with WordSpecLike + with LogCapturing { "Message adapters" must { @@ -139,6 +141,8 @@ class MessageAdapterSpec extends ScalaTestWithActorTestKit(MessageAdapterSpec.co Behaviors.same }) + val unhandledProbe = createTestProbe[UnhandledMessage]() + system.eventStream ! EventStream.Subscribe(unhandledProbe.ref) val probe = TestProbe[Wrapped]() val snitch = Behaviors.setup[Wrapped] { context => @@ -155,9 +159,8 @@ class MessageAdapterSpec extends ScalaTestWithActorTestKit(MessageAdapterSpec.co } } - EventFilter.warning(start = "unhandled message", occurrences = 1).intercept { - spawn(snitch) - } + spawn(snitch) + unhandledProbe.receiveMessage() probe.expectMessage(Wrapped("1", Pong1("hello-1"))) // hello-2 discarded because it was wrong type @@ -199,11 +202,7 @@ class MessageAdapterSpec extends ScalaTestWithActorTestKit(MessageAdapterSpec.co } } - EventFilter.warning(pattern = ".*received dead letter.*", occurrences = 1).intercept { - EventFilter[TestException](occurrences = 1).intercept { - spawn(snitch) - } - } + spawn(snitch) probe.expectMessage(Wrapped(1, Pong("hello"))) probe.expectMessage(Wrapped(2, Pong("hello"))) @@ -250,11 +249,9 @@ class MessageAdapterSpec extends ScalaTestWithActorTestKit(MessageAdapterSpec.co behv(count = 1) } - EventFilter.warning(pattern = ".*received dead letter.*", occurrences = 2).intercept { - // Not expecting "Exception thrown out of adapter. Stopping myself" - EventFilter[TestException](message = "boom", occurrences = 1).intercept { - spawn(snitch) - } + // Not expecting "Exception thrown out of adapter. Stopping myself" + LoggingEventFilter.error[TestException].withMessageContains("boom").intercept { + spawn(snitch) } probe.expectMessage(1) diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/OnSignalSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/OnSignalSpec.scala index 9dcb791a45..57982f50b6 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/OnSignalSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/OnSignalSpec.scala @@ -8,9 +8,10 @@ package scaladsl import akka.Done import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.testkit.typed.scaladsl.TestProbe +import akka.actor.testkit.typed.scaladsl.LogCapturing import org.scalatest.WordSpecLike -final class OnSignalSpec extends ScalaTestWithActorTestKit with WordSpecLike { +final class OnSignalSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { "An Actor.OnSignal behavior" must { "must correctly install the signal handler" in { diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/ReceivePartialSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/ReceivePartialSpec.scala index e2e3a9946c..bc541c4c2a 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/ReceivePartialSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/ReceivePartialSpec.scala @@ -7,9 +7,10 @@ package scaladsl import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.testkit.typed.scaladsl.TestProbe +import akka.actor.testkit.typed.scaladsl.LogCapturing import org.scalatest.WordSpecLike -class ReceivePartialSpec extends ScalaTestWithActorTestKit with WordSpecLike { +class ReceivePartialSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { implicit val ec = system.executionContext diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/RoutersSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/RoutersSpec.scala index 96cb451bd3..fd7cdb69d8 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/RoutersSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/RoutersSpec.scala @@ -6,23 +6,24 @@ package akka.actor.typed.scaladsl import java.util.concurrent.atomic.AtomicInteger import akka.actor.Dropped +import akka.actor.testkit.typed.scaladsl.LoggingEventFilter import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.testkit.typed.scaladsl.TestProbe +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.ActorRef import akka.actor.typed.Behavior +import akka.actor.typed.eventstream.EventStream import akka.actor.typed.internal.routing.GroupRouterImpl import akka.actor.typed.internal.routing.RoutingLogics import akka.actor.typed.receptionist.Receptionist import akka.actor.typed.receptionist.ServiceKey import akka.actor.typed.scaladsl.adapter._ -import akka.testkit.EventFilter import org.scalatest.Matchers import org.scalatest.WordSpecLike class RoutersSpec extends ScalaTestWithActorTestKit(""" - akka.loggers = ["akka.testkit.TestEventListener"] akka.loglevel=debug - """) with WordSpecLike with Matchers { + """) with WordSpecLike with Matchers with LogCapturing { // needed for the event filter implicit val classicSystem = system.toClassic @@ -84,7 +85,7 @@ class RoutersSpec extends ScalaTestWithActorTestKit(""" Behaviors.same })) - EventFilter.debug(start = "Pool child stopped", occurrences = 2).intercept { + LoggingEventFilter.debug("Pool child stopped").withOccurrences(2).intercept { pool ! "stop" pool ! "stop" } @@ -109,7 +110,7 @@ class RoutersSpec extends ScalaTestWithActorTestKit(""" Behaviors.stopped })) - EventFilter.info(start = "Last pool child stopped, stopping pool", occurrences = 1).intercept { + LoggingEventFilter.info("Last pool child stopped, stopping pool").intercept { (0 to 3).foreach { _ => pool ! "stop" } @@ -152,18 +153,12 @@ class RoutersSpec extends ScalaTestWithActorTestKit(""" val serviceKey = ServiceKey[String]("group-routing-2") val group = spawn(Routers.group(serviceKey), "group-router-2") val probe = TestProbe[Dropped]() - system.toClassic.eventStream.subscribe(probe.ref.toClassic, classOf[Dropped]) + system.eventStream ! EventStream.Subscribe(probe.ref) (0 to 3).foreach { n => val msg = s"message-$n" -// EventFilter.info(start = "Message [java.lang.String] ... was not delivered.", occurrences = 1).intercept { */ - EventFilter.warning(start = "dropped message", occurrences = 1).intercept { - EventFilter.info(pattern = ".*was dropped. No routees in group router", occurrences = 1).intercept { - group ! msg - probe.expectMessageType[Dropped] - } - } - /* } */ + group ! msg + probe.expectMessageType[Dropped] } testKit.stop(group) diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/StashBufferSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/StashBufferSpec.scala index f1af111dc1..2e43415ca9 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/StashBufferSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/StashBufferSpec.scala @@ -7,9 +7,10 @@ package akka.actor.typed.scaladsl import akka.actor.typed.Behavior import akka.actor.testkit.typed.internal.StubbedActorContext import akka.actor.testkit.typed.scaladsl.TestInbox +import akka.actor.testkit.typed.scaladsl.LogCapturing import org.scalatest.{ Matchers, WordSpec } -class StashBufferSpec extends WordSpec with Matchers { +class StashBufferSpec extends WordSpec with Matchers with LogCapturing { val context = new StubbedActorContext[String]( "StashBufferSpec", diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/StashSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/StashSpec.scala index 155b83b040..b695895f91 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/StashSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/StashSpec.scala @@ -10,10 +10,14 @@ import java.util.concurrent.TimeUnit import akka.actor.DeadLetter import scala.concurrent.duration._ + +import akka.actor.UnhandledMessage import akka.actor.testkit.typed.TestException +import akka.actor.testkit.typed.scaladsl.LoggingEventFilter import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.testkit.typed.scaladsl.TestProbe -import akka.testkit.EventFilter +import akka.actor.testkit.typed.scaladsl.LogCapturing +import akka.actor.typed.eventstream.EventStream import org.scalatest.WordSpecLike object AbstractStashSpec { @@ -197,7 +201,7 @@ class MutableStashSpec extends AbstractStashSpec { } } -abstract class AbstractStashSpec extends ScalaTestWithActorTestKit with WordSpecLike { +abstract class AbstractStashSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { import AbstractStashSpec._ def testQualifier: String @@ -252,15 +256,7 @@ abstract class AbstractStashSpec extends ScalaTestWithActorTestKit with WordSpec } -class UnstashingSpec extends ScalaTestWithActorTestKit(""" - akka.loggers = ["akka.testkit.TestEventListener"] - """) with WordSpecLike { - - // needed for EventFilter - private implicit val classicSys: akka.actor.ActorSystem = { - import akka.actor.typed.scaladsl.adapter._ - system.toClassic - } +class UnstashingSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { private def slowStoppingChild(latch: CountDownLatch): Behavior[String] = Behaviors.receiveSignal { @@ -422,13 +418,16 @@ class UnstashingSpec extends ScalaTestWithActorTestKit(""" ref ! "stash" ref ! "stash-fail" ref ! "stash" - EventFilter[TestException](start = "unstash-fail", occurrences = 1).intercept { - ref ! "unstash" - probe.expectMessage("unstashing-0") - probe.expectMessage("unstashing-1") - probe.expectMessage("stash-fail-2") - probe.expectMessage("post-stop-2") - } + LoggingEventFilter + .error[TestException] + .withMessageContains("unstash-fail") + .intercept { + ref ! "unstash" + probe.expectMessage("unstashing-0") + probe.expectMessage("unstashing-1") + probe.expectMessage("stash-fail-2") + probe.expectMessage("post-stop-2") + }(system) } "signal PostStop to the latest unstashed behavior on failure" in { @@ -449,7 +448,9 @@ class UnstashingSpec extends ScalaTestWithActorTestKit(""" ref ! "stash" ref ! "stash-fail" ref ! "stash" - EventFilter[TestException](start = "Supervisor RestartSupervisor saw failure: unstash-fail", occurrences = 1) + LoggingEventFilter + .error[TestException] + .withMessageContains("Supervisor RestartSupervisor saw failure: unstash-fail") .intercept { ref ! "unstash" // when childLatch is defined this be stashed in the internal stash of the RestartSupervisor @@ -466,7 +467,7 @@ class UnstashingSpec extends ScalaTestWithActorTestKit(""" ref ! "get-stash-size" probe.expectMessage("stash-size-0") - } + }(system) } "signal PreRestart to the latest unstashed behavior on failure with restart supervision" in { @@ -530,7 +531,9 @@ class UnstashingSpec extends ScalaTestWithActorTestKit(""" ref ! "stash" ref ! "stash-fail" ref ! "stash" - EventFilter[TestException](start = "Supervisor ResumeSupervisor saw failure: unstash-fail", occurrences = 1) + LoggingEventFilter + .error[TestException] + .withMessageContains("Supervisor ResumeSupervisor saw failure: unstash-fail") .intercept { ref ! "unstash" ref ! "get-current" @@ -541,7 +544,7 @@ class UnstashingSpec extends ScalaTestWithActorTestKit(""" probe.expectMessage("current-2") ref ! "get-stash-size" probe.expectMessage("stash-size-5") - } + }(system) ref ! "unstash" ref ! "get-current" @@ -588,6 +591,9 @@ class UnstashingSpec extends ScalaTestWithActorTestKit(""" "deal with unhandled the same way as normal unhandled" in { val probe = TestProbe[String]() + val unhandledProbe = createTestProbe[UnhandledMessage]() + system.eventStream ! EventStream.Subscribe(unhandledProbe.ref) + val ref = spawn(Behaviors.withStash[String](10) { stash => stash.stash("unhandled") stash.stash("handled") @@ -609,11 +615,12 @@ class UnstashingSpec extends ScalaTestWithActorTestKit(""" } }) - EventFilter.warning(start = "unhandled message from", occurrences = 2).intercept { - ref ! "unstash" - } + ref ! "unstash" + + unhandledProbe.receiveMessage() probe.expectMessage("handled 1") probe.expectMessage("handled 2") + unhandledProbe.receiveMessage() probe.expectMessage("handled 3") ref ! "handled" @@ -637,18 +644,17 @@ class UnstashingSpec extends ScalaTestWithActorTestKit(""" "deal with stop" in { val probe = TestProbe[Any] - import akka.actor.typed.scaladsl.adapter._ - classicSys.eventStream.subscribe(probe.ref.toClassic, classOf[DeadLetter]) + system.eventStream ! EventStream.Subscribe(probe.ref.narrow[DeadLetter]) + val ref = spawn(Behaviors.withStash[String](10) { stash => stash.stash("one") stash.stash("two") Behaviors.receiveMessage { case "unstash" => - stash.unstashAll(Behaviors.receiveMessage { - case unstashed => - probe.ref ! unstashed - Behaviors.stopped + stash.unstashAll(Behaviors.receiveMessage { unstashed => + probe.ref ! unstashed + Behaviors.stopped }) case _ => Behaviors.same diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/StopSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/StopSpec.scala index 3ea21b2399..cfdec3b7dd 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/StopSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/StopSpec.scala @@ -7,13 +7,14 @@ package akka.actor.typed.scaladsl import akka.Done import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.testkit.typed.scaladsl.TestProbe +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed import akka.actor.typed.Behavior import akka.actor.typed.BehaviorInterceptor import akka.actor.typed.PostStop import org.scalatest.WordSpecLike -class StopSpec extends ScalaTestWithActorTestKit with WordSpecLike { +class StopSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { import BehaviorInterceptor._ "Stopping an actor" should { diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/adapter/AdapterSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/adapter/AdapterSpec.scala index 71378c5489..fa7dbde6ab 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/adapter/AdapterSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/adapter/AdapterSpec.scala @@ -16,6 +16,7 @@ import akka.actor.typed.Terminated import akka.testkit._ import akka.Done import akka.NotUsed +import akka.actor.testkit.typed.scaladsl.LoggingEventFilter import akka.{ actor => classic } object AdapterSpec { @@ -161,9 +162,7 @@ object AdapterSpec { } -class AdapterSpec extends AkkaSpec(""" - akka.loggers = [akka.testkit.TestEventListener] - """) { +class AdapterSpec extends AkkaSpec { import AdapterSpec._ "ActorSystem adaption" must { @@ -278,10 +277,12 @@ class AdapterSpec extends AkkaSpec(""" val typedRef = system.spawnAnonymous(typed1(ignore, probe.ref)) // only stop supervisorStrategy - EventFilter[AdapterSpec.ThrowIt3.type](occurrences = 1).intercept { - typedRef ! "supervise-restart" - probe.expectMsg("ok") - } + LoggingEventFilter + .error[AdapterSpec.ThrowIt3.type] + .intercept { + typedRef ! "supervise-restart" + probe.expectMsg("ok") + }(system.toTyped) } "stop typed child from classic parent" in { @@ -302,10 +303,12 @@ class AdapterSpec extends AkkaSpec(""" "log exception if not by handled typed supervisor" in { val throwMsg = "sad panda" - EventFilter.warning(pattern = ".*sad panda.*").intercept { - system.spawnAnonymous(unhappyTyped(throwMsg)) - Thread.sleep(1000) - } + LoggingEventFilter + .error("sad panda") + .intercept { + system.spawnAnonymous(unhappyTyped(throwMsg)) + Thread.sleep(1000) + }(system.toTyped) } } } diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/adapter/GuardianStartupSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/adapter/GuardianStartupSpec.scala index 2eeaf099d3..634e9857d5 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/adapter/GuardianStartupSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/adapter/GuardianStartupSpec.scala @@ -9,13 +9,14 @@ import java.util.concurrent.TimeUnit import akka.actor.ActorSystemImpl import akka.actor.testkit.typed.scaladsl.ActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.ActorSystem import akka.actor.typed.scaladsl.Behaviors import org.scalatest.Matchers import org.scalatest.WordSpec import org.scalatest.concurrent.ScalaFutures -class GuardianStartupSpec extends WordSpec with Matchers with ScalaFutures { +class GuardianStartupSpec extends WordSpec with Matchers with ScalaFutures with LogCapturing { "The user guardian" must { diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/testkit/AppenderInterceptor.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/testkit/AppenderInterceptor.scala deleted file mode 100644 index bad8fc117d..0000000000 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/testkit/AppenderInterceptor.scala +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2019 Lightbend Inc. - */ - -package akka.actor.typed.testkit -import java.util.concurrent.locks.ReentrantLock - -import ch.qos.logback.classic.spi.ILoggingEvent -import org.slf4j.LoggerFactory -import org.slf4j.event.LoggingEvent - -object AppenderInterceptor { - - val logger = LoggerFactory.getLogger("akka.actor.typed.scaladsl.ActorLoggingSpec") - val root = logger.asInstanceOf[ch.qos.logback.classic.Logger] - val myAppender = root.getAppender("INTERCEPTOR").asInstanceOf[TestAppender] - def events:() => Seq[LoggingEvent] = () => myAppender.events.map { each => - each match { - //TODO implement also for log4j, ILoggingEvent is logback specific - case event: ILoggingEvent => { - new Sl4jLoggingEvent( - event.getLevel(), - event.getMessage(), - event.getLoggerName(), - event.getTimeStamp, - event.getArgumentArray, - ) - } - } - } - -} diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/testkit/LoggingEventFilter.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/testkit/LoggingEventFilter.scala deleted file mode 100644 index 7ef92e3920..0000000000 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/testkit/LoggingEventFilter.scala +++ /dev/null @@ -1,560 +0,0 @@ -/* - * Copyright (C) 2019 Lightbend Inc. - */ - -package akka.actor.typed.testkit - -/* - * Copyright (C) 2009-2019 Lightbend Inc. - */ - -import java.util -import java.util.concurrent.LinkedBlockingQueue - -import akka.actor.{ActorSystem, DeadLetter, Dropped, NoSerializationVerificationNeeded, UnhandledMessage} -import akka.dispatch.sysmsg.{SystemMessage, Terminate} -import akka.event.Logging -import akka.event.Logging._ -import akka.event.slf4j.Slf4jLogger -import akka.japi.Util.immutableSeq -import akka.testkit.TestEvent.{Mute, UnMute} -import akka.testkit.{EventFilter, TestEvent, TestKit, TestKitExtension} -import akka.util.BoxedType -import akka.util.ccompat.ccompatUsedUntil213 -import ch.qos.logback.classic.spi.ILoggingEvent -import org.slf4j.Marker -import org.slf4j.event.{Level, LoggingEvent} - -import scala.collection.mutable.ArrayBuffer -import scala.concurrent.duration.Duration -import scala.reflect.ClassTag -import scala.util.matching.Regex - -/** - * Facilities for selectively filtering out expected org.slf4j.event.LoggingEvent from logging so - * that you can keep your test run’s console output clean and do not miss real - * error messages. - * - * See the companion object for convenient factory methods. - * - * If the `occurrences` is set to Int.MaxValue, no tracking is done. - */ -abstract class LoggingEventFilter[LE <: LoggingEvent](occurrences: Int) { - - /* - * these default values are just there for easier subclassing - */ - protected val source: Option[String] = None - protected val message: Either[String, Regex] = Left("") - protected val complete: Boolean = false - @volatile // JMM does not guarantee visibility for non-final fields - private var todo = occurrences - - import scala.collection.JavaConverters._ - - /** - * Apply this filter while executing the given code block. Care is taken to - * remove the filter when the block is finished or aborted. - */ - def intercept[T, LE <: LoggingEvent](code: => T, loggingEvents: util.Collection[LE])(implicit system: ActorSystem): T = { - //TODO @see original TestEventListener#116: system.eventStream.publish(TestEvent.Mute(this)) - def leftToDo: Int = todo - loggingEvents.asScala.count(matches) - val leeway = TestKitExtension(system).TestEventFilterLeeway - val result = code - if (!awaitDone(leeway, leftToDo)) - if (leftToDo > 0) - throw new AssertionError(s"timeout ($leeway) waiting for $leftToDo messages on $this") - else - throw new AssertionError(s"received ${-leftToDo} excess messages on $this") - - result - } - - import scala.concurrent.duration._ - /** - * Apply this filter while executing the given code block. Care is taken to - * remove the filter when the block is finished or aborted. - */ - def interceptIt[T] (code: => T, loggingEvents: () => Seq[LoggingEvent])(implicit system: ActorSystem): T = { - //TODO @see original TestEventListener#116: system.eventStream.publish(TestEvent.Mute(this)) - val result = code - def leftToDo: Int = todo - loggingEvents.apply().count(matches) - val leeway = TestKitExtension(system).TestEventFilterLeeway - if (!awaitDone(leeway, leftToDo)) - if (leftToDo > 0) - throw new AssertionError(s"timeout ($leeway) waiting for $leftToDo messages on $this") - else - throw new AssertionError(s"received ${-leftToDo} excess messages on $this") - result - } - - def awaitDone(max: Duration, leftToDo: => Int): Boolean = { - if (leftToDo != Int.MaxValue && leftToDo > 0) TestKit.awaitCond(leftToDo <= 0, max, noThrow = true) - leftToDo == Int.MaxValue || leftToDo == 0 - } - - /** - * This method decides whether to filter the event (true) or not - * (false). - */ - protected def matches(event: LoggingEvent): Boolean - - /** - * internal implementation helper, no guaranteed API - */ - protected def doMatch(msg: Any) = { - val msgstr = if (msg != null) msg.toString else "null" - (message match { - case Left(s) => - if (complete) msgstr == s else msgstr.startsWith(s) - case Right(p) => p.findFirstIn(msgstr).isDefined - }) - } -} - -/** - * Facilities for selectively filtering out expected events from logging so - * that you can keep your test run’s console output clean and do not miss real - * error messages. - * - * '''Also have a look at the `akka.testkit` package object’s `filterEvents` and - * `filterException` methods.''' - * - * The source filters do accept `Class[_]` arguments, matching any - * object which is an instance of the given class, e.g. - * - * {{{ - * EventFilter.info(source = classOf[MyActor]) // will match Info events from any MyActor instance - * }}} - * - * The message object will be converted to a string before matching (`"null"` if it is `null`). - */ -object LoggingEventFilter { - - /** - * Create a filter for Error events. Give up to one of start and pattern: - * - * {{{ - * EventFilter[MyException]() // filter only on exception type - * EventFilter[MyException]("message") // filter on exactly matching message - * EventFilter[MyException](source = obj) // filter on event source - * EventFilter[MyException](start = "Expected") // filter on start of message - * EventFilter[MyException](source = obj, pattern = "weird.*message") // filter on pattern and message - * }}} - * - * ''Please note that filtering on the `source` being - * `null` does NOT work (passing `null` disables the - * source filter).'' - */ - private def apply[A <: Throwable: ClassTag]( - message: String = null, - source: String = null, - start: String = "", - pattern: String = null, - occurrences: Int = Int.MaxValue, - marker: Marker)(implicit ev: A = Logging.Error.NoCause.getClass): LoggingEventFilter[_] = - ErrorFilterLogging( - ev.getClass, - Option(source), - if (message ne null) Left(message) else Option(pattern).map(new Regex(_)).toRight(start), - message ne null)(occurrences) - - /** - * Create a filter for Error events. See apply() for more details. - */ - def error( - message: String = null, - source: String = null, - start: String = "", - pattern: String = null, - occurrences: Int = Int.MaxValue): LoggingEventFilter[_] = - ErrorFilterLogging( - Logging.Error.NoCause.getClass, - Option(source), - if (message ne null) Left(message) else Option(pattern).map(new Regex(_)).toRight(start), - message ne null)(occurrences) - - /** - * Create a filter for Warning events. Give up to one of start and pattern: - * - * {{{ - * EventFilter.warning() // filter only on warning event - * EventFilter.warning(source = obj) // filter on event source - * EventFilter.warning(start = "Expected") // filter on start of message - * EventFilter.warning(source = obj, pattern = "weird.*message") // filter on pattern and message - * }}} - * - * ''Please note that filtering on the `source` being - * `null` does NOT work (passing `null` disables the - * source filter).'' - */ - def warning( - message: String = null, - source: String = null, - start: String = "", - pattern: String = null, - occurrences: Int = Int.MaxValue): LoggingEventFilter[_] = - WarningFilterLogging( - Option(source), - if (message ne null) Left(message) else Option(pattern).map(new Regex(_)).toRight(start), - message ne null)(occurrences) - - /** - * Create a filter for Info events. Give up to one of start and pattern: - * - * {{{ - * EventFilter.info() // filter only on info event - * EventFilter.info(source = obj) // filter on event source - * EventFilter.info(start = "Expected") // filter on start of message - * EventFilter.info(source = obj, pattern = "weird.*message") // filter on pattern and message - * }}} - * - * ''Please note that filtering on the `source` being - * `null` does NOT work (passing `null` disables the - * source filter).'' - */ - def info( - message: String = null, - source: String = null, - start: String = "", - pattern: String = null, - occurrences: Int = Int.MaxValue): LoggingEventFilter[_] = - InfoFilterLogging( - Option(source), - if (message ne null) Left(message) else Option(pattern).map(new Regex(_)).toRight(start), - message ne null)(occurrences) - - /** - * Create a filter for Debug events. Give up to one of start and pattern: - * - * {{{ - * EventFilter.debug() // filter only on debug type - * EventFilter.debug(source = obj) // filter on event source - * EventFilter.debug(start = "Expected") // filter on start of message - * EventFilter.debug(source = obj, pattern = "weird.*message") // filter on pattern and message - * }}} - * - * ''Please note that filtering on the `source` being - * `null` does NOT work (passing `null` disables the - * source filter).'' - */ - def debug( - message: String = null, - source: String = null, - start: String = "", - pattern: String = null, - occurrences: Int = Int.MaxValue): LoggingEventFilter[_] = - DebugFilterLogging( - Option(source), - if (message ne null) Left(message) else Option(pattern).map(new Regex(_)).toRight(start), - message ne null)(occurrences) - - /** - * Create a custom event filter. The filter will affect those events for - * which the supplied partial function is defined and returns - * `true`. - * - * {{{ - * EventFilter.custom { - * case Warning(ref, "my warning") if ref == actor || ref == null => true - * } - * }}} - */ - def custom( - test: PartialFunction[LoggingEvent, Boolean], - occurrences: Int = Int.MaxValue): LoggingEventFilter[LoggingEvent] = - CustomLoggingEventFilter(test)(occurrences) -} - -/** - * Filter which matches Error events, if they satisfy the given criteria: - *
    - *
  • throwable applies an upper bound on the type of exception contained in the Error event
  • - *
  • source, if given, applies a filter on the event’s origin
  • - *
  • message applies a filter on the event’s message (either - * with String.startsWith or Regex.findFirstIn().isDefined); if the message - * itself does not match, the match is retried with the contained Exception’s - * message; if both are null, the filter always matches if at - * the same time the Exception’s stack trace is empty (this catches - * JVM-omitted “fast-throw” exceptions)
  • - *
- * If you want to match all Error events, the most efficient is to use Left(""). - */ -final case class ErrorFilterLogging( - throwable: Class[_], - override val source: Option[String], - override val message: Either[String, Regex], - override val complete: Boolean)(occurrences: Int) - extends LoggingEventFilter[LoggingEvent](occurrences) { - - def matches(event: LoggingEvent) = { - event.getLevel match { - case Level.ERROR => true - case _ => false - } - } - - /** - * Java API: create an ErrorFilter - * - * @param source - * apply this filter only to events from the given source; do not filter on source if this is given as null - * @param message - * apply this filter only to events whose message matches; do not filter on message if this is given as null - * @param pattern - * if false, the message string must start with the given - * string, otherwise the message argument is treated as - * regular expression which is matched against the message (may match only - * a substring to filter) - * @param complete - * whether the event’s message must match the given message string or pattern completely - */ - def this( - throwable: Class[_], - source: String, - message: String, - pattern: Boolean, - complete: Boolean, - occurrences: Int) = - this( - throwable, - Option(source), - if (message eq null) Left("") - else if (pattern) Right(new Regex(message)) - else Left(message), - complete)(occurrences) - - /** - * Java API: filter only on the given type of exception - */ - def this(throwable: Class[_]) = this(throwable, null, null, false, false, Int.MaxValue) - -} - -/** - * Filter which matches Warning events, if they satisfy the given criteria: - *
    - *
  • source, if given, applies a filter on the event’s origin
  • - *
  • message applies a filter on the event’s message (either with String.startsWith or Regex.findFirstIn().isDefined)
  • - *
- * If you want to match all Warning events, the most efficient is to use Left(""). - */ -final case class WarningFilterLogging( - override val source: Option[String], - override val message: Either[String, Regex], - override val complete: Boolean)(occurrences: Int) - extends LoggingEventFilter[LoggingEvent](occurrences) { - - def matches(event: LoggingEvent) = { - event.getLevel match { - case Level.WARN => doMatch(event.getLoggerName, event.getMessage) - case _ => false - } - } - - /** - * Java API: create a WarningFilter - * - * @param source - * apply this filter only to events from the given source; do not filter on source if this is given as null - * @param message - * apply this filter only to events whose message matches; do not filter on message if this is given as null - * @param pattern - * if false, the message string must start with the given - * string, otherwise the message argument is treated as - * regular expression which is matched against the message (may match only - * a substring to filter) - * @param complete - * whether the event’s message must match the given message string or pattern completely - */ - def this(source: String, message: String, pattern: Boolean, complete: Boolean, occurrences: Int) = - this( - Option(source), - if (message eq null) Left("") - else if (pattern) Right(new Regex(message)) - else Left(message), - complete)(occurrences) -} - -/** - * Filter which matches Info events, if they satisfy the given criteria: - *
    - *
  • source, if given, applies a filter on the event’s origin
  • - *
  • message applies a filter on the event’s message (either with String.startsWith or Regex.findFirstIn().isDefined)
  • - *
- * If you want to match all Info events, the most efficient is to use Left(""). - */ -final case class InfoFilterLogging( - override val source: Option[String], - override val message: Either[String, Regex], - override val complete: Boolean)(occurrences: Int) - extends LoggingEventFilter[LoggingEvent](occurrences) { - - def matches(event: LoggingEvent) = { - event.getLevel match { - case Level.INFO => doMatch(event.getMessage) - case _ => false - } - } - - /** - * Java API: create an InfoFilter - * - * @param source - * apply this filter only to events from the given source; do not filter on source if this is given as null - * @param message - * apply this filter only to events whose message matches; do not filter on message if this is given as null - * @param pattern - * if false, the message string must start with the given - * string, otherwise the message argument is treated as - * regular expression which is matched against the message (may match only - * a substring to filter) - * @param complete - * whether the event’s message must match the given message string or pattern completely - */ - def this(source: String, message: String, pattern: Boolean, complete: Boolean, occurrences: Int) = - this( - Option(source), - if (message eq null) Left("") - else if (pattern) Right(new Regex(message)) - else Left(message), - complete)(occurrences) -} - -/** - * Filter which matches Debug events, if they satisfy the given criteria: - *
    - *
  • source, if given, applies a filter on the event’s origin
  • - *
  • message applies a filter on the event’s message (either with String.startsWith or Regex.findFirstIn().isDefined)
  • - *
- * If you want to match all Debug events, the most efficient is to use Left(""). - */ -final case class DebugFilterLogging( - override val source: Option[String], - override val message: Either[String, Regex], - override val complete: Boolean)(occurrences: Int) - extends LoggingEventFilter[LoggingEvent](occurrences) { - - def matches(event: LoggingEvent) = { - event.getLevel match { - case Level.DEBUG => doMatch(event.getMessage) - case _ => false - } - } - - /** - * Java API: create a DebugFilter - * - * @param source - * apply this filter only to events from the given source; do not filter on source if this is given as null - * @param message - * apply this filter only to events whose message matches; do not filter on message if this is given as null - * @param pattern - * if false, the message string must start with the given - * string, otherwise the message argument is treated as - * regular expression which is matched against the message (may match only - * a substring to filter) - * @param complete - * whether the event’s message must match the given message string or pattern completely - */ - def this(source: String, message: String, pattern: Boolean, complete: Boolean, occurrences: Int) = - this( - Option(source), - if (message eq null) Left("") - else if (pattern) Right(new Regex(message)) - else Left(message), - complete)(occurrences) -} - -/** - * Custom event filter when the others do not fit the bill. - * - * If the partial function is defined and returns true, filter the event. - */ -final case class CustomLoggingEventFilter(test: PartialFunction[LoggingEvent, Boolean])(occurrences: Int) - extends LoggingEventFilter[LoggingEvent](occurrences) { - def matches(event: LoggingEvent) = { - test.isDefinedAt(event) && test(event) - } -} - -object DeadLettersFilterLogging { - def apply[T](implicit t: ClassTag[T]): DeadLettersFilterLogging = - new DeadLettersFilterLogging(t.runtimeClass.asInstanceOf[Class[T]])(Int.MaxValue) -} - -/** - * Filter which matches DeadLetter events, if the wrapped message conforms to the - * given type. - */ -final case class DeadLettersFilterLogging(val messageClass: Class[_])(occurrences: Int) - extends LoggingEventFilter[LoggingEvent](occurrences) { - - def matches(event: LoggingEvent) = { - event.getLevel match { - case Level.WARN => BoxedType(messageClass).isInstance(event.getMessage) - case _ => false - } - } - -} - - -//TODO this still has EventFilter!! Slf4jLogger has also to be changed - -@ccompatUsedUntil213 -class TestEventListener extends Slf4jLogger { - import TestEvent._ - - var filters: List[EventFilter] = Nil - - override def receive = { - case InitializeLogger(bus) => - Seq(classOf[Mute], classOf[UnMute], classOf[DeadLetter], classOf[UnhandledMessage], classOf[Dropped]) - .foreach(bus.subscribe(context.self, _)) - sender() ! LoggerInitialized - case Mute(filters) => filters.foreach(addFilter) - case UnMute(filters) => filters.foreach(removeFilter) - case event: LogEvent => if (!filter(event)) print(event) - case DeadLetter(msg, snd, rcp) => - if (!msg.isInstanceOf[Terminate]) { - val event = Warning(rcp.path.toString, rcp.getClass, msg) - if (!filter(event)) { - val msgPrefix = - if (msg.isInstanceOf[SystemMessage]) "received dead system message" - else if (snd eq context.system.deadLetters) "received dead letter" - else "received dead letter from " + snd - val event2 = Warning(rcp.path.toString, rcp.getClass, msgPrefix + ": " + msg) - if (!filter(event2)) print(event2) - } - } - case UnhandledMessage(msg, sender, rcp) => - val event = Warning(rcp.path.toString, rcp.getClass, s"unhandled message from $sender: $msg") - if (!filter(event)) print(event) - case Dropped(msg, reason, sender, rcp) => - val event = - Warning(rcp.path.toString, rcp.getClass, s"dropped message from $sender. $reason: $msg") - if (!filter(event)) print(event) - - case m => print(Debug(context.system.name, this.getClass, m)) - } - - def filter(event: LogEvent): Boolean = - filters.exists(f => - try { - f(event) - } catch { case _: Exception => false }) - - def addFilter(filter: EventFilter): Unit = filters ::= filter - - def removeFilter(filter: EventFilter): Unit = { - @scala.annotation.tailrec - def removeFirst(list: List[EventFilter], zipped: List[EventFilter] = Nil): List[EventFilter] = list match { - case head :: tail if head == filter => tail.reverse_:::(zipped) - case head :: tail => removeFirst(tail, head :: zipped) - case Nil => filters // filter not found, just return original list - } - filters = removeFirst(filters) - } - -} diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/testkit/Sl4jLoggingEvent.java b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/testkit/Sl4jLoggingEvent.java deleted file mode 100644 index e97e38a007..0000000000 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/testkit/Sl4jLoggingEvent.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2019 Lightbend Inc. - */ - -package akka.actor.typed.testkit; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.slf4j.Marker; -import org.slf4j.event.Level; -import org.slf4j.event.LoggingEvent; - -public class Sl4jLoggingEvent implements LoggingEvent { - - Level level; - Marker marker; - String loggerName; - Logger logger; - String threadName; - String message; - Object[] argArray; - long timeStamp; - Throwable throwable; - - public Sl4jLoggingEvent(Level level, String msg, String loggerName) { - setLevel(level); - setMessage(msg); - setLoggerName(loggerName); - } - - public Sl4jLoggingEvent(ch.qos.logback.classic.Level level, String msg, String loggerName, long timeStamp, Object[] argArray) { - setLevel(toLogbackLevel(level)); - setArgumentArray(argArray); - setMessage(msg); - setLoggerName(loggerName); - setTimeStamp(timeStamp); - } - - public Level toLogbackLevel(ch.qos.logback.classic.Level level) { - switch (level.levelInt) { - case ch.qos.logback.classic.Level.TRACE_INT: - return Level.TRACE; - case ch.qos.logback.classic.Level.DEBUG_INT: - return Level.DEBUG; - case ch.qos.logback.classic.Level.INFO_INT: - return Level.INFO; - case ch.qos.logback.classic.Level.WARN_INT: - return Level.WARN; - case ch.qos.logback.classic.Level.ERROR_INT: - return Level.ERROR; - default: - throw new IllegalStateException("Level " + level.levelStr + ", " + level.levelInt + " is unknown."); - - } - } - - public Level getLevel() { - return level; - } - - public void setLevel(Level level) { - this.level = level; - } - - public Marker getMarker() { - return marker; - } - - public void setMarker(Marker marker) { - this.marker = marker; - } - - public String getLoggerName() { - return loggerName; - } - - public void setLoggerName(String loggerName) { - this.loggerName = loggerName; - setLogger(LoggerFactory.getLogger(loggerName)); - } - - public Logger getLogger() { - return logger; - } - - public void setLogger(Logger logger) { - this.logger = logger; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - if(getArgumentArray() != null) { - for (int i = 0; i < getArgumentArray().length; i++) { - message = message.replaceFirst("\\{\\}", getArgumentArray()[i].toString()); - } - } - this.message = message; - } - - public Object[] getArgumentArray() { - return argArray; - } - - public void setArgumentArray(Object[] argArray) { - this.argArray = argArray; - } - - public long getTimeStamp() { - return timeStamp; - } - - public void setTimeStamp(long timeStamp) { - this.timeStamp = timeStamp; - } - - public String getThreadName() { - return threadName; - } - - public void setThreadName(String threadName) { - this.threadName = threadName; - } - - public Throwable getThrowable() { - return throwable; - } - - public void setThrowable(Throwable throwable) { - this.throwable = throwable; - } -} diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/testkit/TestAppender.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/testkit/TestAppender.scala deleted file mode 100644 index 73b177a845..0000000000 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/testkit/TestAppender.scala +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (C) 2019 Lightbend Inc. - */ - -package akka.actor.typed.testkit -import ch.qos.logback.classic.spi.ILoggingEvent -import ch.qos.logback.core.AppenderBase - -import scala.collection.mutable.ArrayBuffer - -class TestAppender extends AppenderBase[ILoggingEvent] { - - var events = new ArrayBuffer[ILoggingEvent]() - - override def append(event: ILoggingEvent) = - events += event - -} diff --git a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/DispatchersDocSpec.scala b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/DispatchersDocSpec.scala index 5c2c343fdb..72456cba4c 100644 --- a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/DispatchersDocSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/DispatchersDocSpec.scala @@ -14,9 +14,10 @@ import DispatchersDocSpec._ import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import com.typesafe.config.ConfigFactory import org.scalatest.WordSpecLike - import scala.concurrent.Future +import akka.actor.testkit.typed.scaladsl.LogCapturing + object DispatchersDocSpec { val config = ConfigFactory.parseString(""" @@ -57,7 +58,10 @@ object DispatchersDocSpec { } -class DispatchersDocSpec extends ScalaTestWithActorTestKit(DispatchersDocSpec.config) with WordSpecLike { +class DispatchersDocSpec + extends ScalaTestWithActorTestKit(DispatchersDocSpec.config) + with WordSpecLike + with LogCapturing { "Actor Dispatchers" should { "support default and blocking dispatcher" in { diff --git a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/FSMDocSpec.scala b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/FSMDocSpec.scala index 675f917933..b08f53b64f 100644 --- a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/FSMDocSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/FSMDocSpec.scala @@ -7,10 +7,11 @@ package docs.akka.typed import akka.actor.testkit.typed.scaladsl.TestProbe import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.{ ActorRef, Behavior } - import scala.collection.immutable import scala.concurrent.duration._ + import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import org.scalatest.WordSpecLike object FSMDocSpec { @@ -67,7 +68,7 @@ object FSMDocSpec { //#test-code } -class FSMDocSpec extends ScalaTestWithActorTestKit with WordSpecLike { +class FSMDocSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { import FSMDocSpec._ diff --git a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/GracefulStopDocSpec.scala b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/GracefulStopDocSpec.scala index 936099a652..834eb5e734 100644 --- a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/GracefulStopDocSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/GracefulStopDocSpec.scala @@ -5,6 +5,8 @@ package docs.akka.typed //#imports +import akka.actor.testkit.typed.scaladsl.LogCapturing +import akka.actor.typed.Behavior import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.{ ActorSystem, PostStop } @@ -72,7 +74,7 @@ object GracefulStopDocSpec { } -class GracefulStopDocSpec extends ScalaTestWithActorTestKit with WordSpecLike { +class GracefulStopDocSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { import GracefulStopDocSpec._ diff --git a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/InteractionPatternsSpec.scala b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/InteractionPatternsSpec.scala index a83ba580bd..4bbf3af4db 100644 --- a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/InteractionPatternsSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/InteractionPatternsSpec.scala @@ -6,17 +6,23 @@ package docs.akka.typed import java.net.URI -import akka.NotUsed -import akka.actor.typed.{ ActorRef, ActorSystem, Behavior } -import akka.actor.typed.scaladsl.{ Behaviors, TimerScheduler } - import scala.concurrent.Future import scala.concurrent.duration._ -import scala.util.{ Failure, Success } +import scala.util.Failure +import scala.util.Success + +import akka.NotUsed import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing +import akka.actor.typed.ActorRef +import akka.actor.typed.ActorSystem +import akka.actor.typed.Behavior +import akka.actor.typed.scaladsl.Behaviors +import akka.actor.typed.scaladsl.LoggerOps +import akka.actor.typed.scaladsl.TimerScheduler import org.scalatest.WordSpecLike -class InteractionPatternsSpec extends ScalaTestWithActorTestKit with WordSpecLike { +class InteractionPatternsSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { "The interaction patterns docs" must { @@ -120,10 +126,10 @@ class InteractionPatternsSpec extends ScalaTestWithActorTestKit with WordSpecLik context.log.info("Started {}", taskId) Behaviors.same case Backend.JobProgress(taskId, progress) => - context.log.info("Progress {}: {}", taskId, progress) + context.log.info2("Progress {}: {}", taskId, progress) Behaviors.same case Backend.JobCompleted(taskId, result) => - context.log.info("Completed {}: {}", taskId, result) + context.log.info2("Completed {}: {}", taskId, result) inProgress(taskId) ! result active(inProgress - taskId, count) } diff --git a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala index 0a8264269c..0c227c0dd5 100644 --- a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/IntroSpec.scala @@ -6,7 +6,9 @@ package docs.akka.typed //#fiddle_code //#imports +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.scaladsl.Behaviors +import akka.actor.typed.scaladsl.LoggerOps import akka.actor.typed.{ ActorRef, ActorSystem, Behavior } //#imports //#fiddle_code @@ -54,7 +56,7 @@ object IntroSpec { Behaviors.receive { (context, message) => val n = greetingCounter + 1 //#fiddle_code - context.log.info("Greeting {} for {}", n, message.whom) + context.log.info2("Greeting {} for {}", n, message.whom) //#fiddle_code //#hello-world-bot println(s"Greeting $n for ${message.whom}") @@ -190,7 +192,7 @@ object IntroSpec { handle ! PostMessage("Hello World!") Behaviors.same case MessagePosted(screenName, message) => - context.log.info("message has been posted by '{}': {}", screenName, message: Any) + context.log.info2("message has been posted by '{}': {}", screenName, message) Behaviors.stopped } } @@ -221,7 +223,7 @@ object IntroSpec { } -class IntroSpec extends ScalaTestWithActorTestKit with WordSpecLike { +class IntroSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { import IntroSpec._ diff --git a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/MailboxDocSpec.scala b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/MailboxDocSpec.scala index 7b87fc80e5..0fcdebf586 100644 --- a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/MailboxDocSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/MailboxDocSpec.scala @@ -6,6 +6,7 @@ package docs.akka.typed import akka.Done import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.Behavior import akka.actor.typed.MailboxSelector import akka.actor.typed.scaladsl.Behaviors @@ -14,7 +15,8 @@ import org.scalatest.WordSpecLike class MailboxDocSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("mailbox-config-sample.conf")) - with WordSpecLike { + with WordSpecLike + with LogCapturing { "Specifying mailbox through props" must { "work" in { diff --git a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/OOIntroSpec.scala b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/OOIntroSpec.scala index 1b1634b3b4..9976cba581 100644 --- a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/OOIntroSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/OOIntroSpec.scala @@ -6,8 +6,9 @@ package docs.akka.typed //#imports import akka.Done +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.{ ActorRef, ActorSystem, Behavior } -import akka.actor.typed.scaladsl.{ AbstractBehavior, ActorContext, Behaviors } +import akka.actor.typed.scaladsl.{ AbstractBehavior, ActorContext, Behaviors, LoggerOps } //#imports import akka.NotUsed @@ -104,7 +105,7 @@ object OOIntroSpec { handle ! PostMessage("Hello World!") Behaviors.same case MessagePosted(screenName, message) => - context.log.info("message has been posted by '{}': {}", Array(screenName, message): _*) + context.log.info2("message has been posted by '{}': {}", screenName, message) Behaviors.stopped } } @@ -135,7 +136,7 @@ object OOIntroSpec { } -class OOIntroSpec extends ScalaTestWithActorTestKit with WordSpecLike { +class OOIntroSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { import OOIntroSpec._ diff --git a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/RouterSpec.scala b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/RouterSpec.scala index 082e4e6e74..6f9a87a0dd 100644 --- a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/RouterSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/RouterSpec.scala @@ -6,6 +6,7 @@ package docs.akka.typed // #pool import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.Behavior import akka.actor.typed.SupervisorStrategy import akka.actor.typed.receptionist.Receptionist @@ -39,7 +40,7 @@ object RouterSpec { val serviceKey = ServiceKey[Worker.Command]("log-worker") } -class RouterSpec extends ScalaTestWithActorTestKit("akka.loglevel=warning") with WordSpecLike { +class RouterSpec extends ScalaTestWithActorTestKit("akka.loglevel=warning") with WordSpecLike with LogCapturing { import RouterSpec._ "The routing sample" must { diff --git a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/SpawnProtocolDocSpec.scala b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/SpawnProtocolDocSpec.scala index 766fa7e7d8..4364f2ebd1 100644 --- a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/SpawnProtocolDocSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/SpawnProtocolDocSpec.scala @@ -7,8 +7,10 @@ package docs.akka.typed import scala.concurrent.ExecutionContext import scala.concurrent.Future import scala.concurrent.duration._ + import akka.actor.testkit.typed.scaladsl.ActorTestKit import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import docs.akka.typed.IntroSpec.HelloWorld import org.scalatest.WordSpecLike import com.github.ghik.silencer.silent @@ -17,6 +19,7 @@ import com.github.ghik.silencer.silent import akka.actor.typed.Behavior import akka.actor.typed.SpawnProtocol import akka.actor.typed.scaladsl.Behaviors +import akka.actor.typed.scaladsl.LoggerOps //#imports1 @@ -46,7 +49,7 @@ object SpawnProtocolDocSpec { //#main } -class SpawnProtocolDocSpec extends ScalaTestWithActorTestKit with WordSpecLike { +class SpawnProtocolDocSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { import SpawnProtocolDocSpec._ @@ -67,7 +70,7 @@ class SpawnProtocolDocSpec extends ScalaTestWithActorTestKit with WordSpecLike { system.ask(SpawnProtocol.Spawn(behavior = HelloWorld(), name = "greeter", props = Props.empty, _)) val greetedBehavior = Behaviors.receive[HelloWorld.Greeted] { (context, message) => - context.log.info("Greeting for {} from {}", Array(message.whom, message.from): _*) + context.log.info2("Greeting for {} from {}", message.whom, message.from) Behaviors.stopped } diff --git a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StashDocSpec.scala b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StashDocSpec.scala index 7ef8ee752d..390ab66ac9 100644 --- a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StashDocSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StashDocSpec.scala @@ -5,7 +5,7 @@ package docs.akka.typed import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit - +import akka.actor.testkit.typed.scaladsl.LogCapturing import org.scalatest.WordSpecLike object StashDocSpec { @@ -100,7 +100,7 @@ object StashDocSpec { // #stashing } -class StashDocSpec extends ScalaTestWithActorTestKit with WordSpecLike { +class StashDocSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { import StashDocSpec.DB import StashDocSpec.DataAccess import scala.concurrent.Future diff --git a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala index 0073546ac2..9fca66bb1e 100644 --- a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala +++ b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/StyleGuideDocExamples.scala @@ -9,6 +9,7 @@ import scala.concurrent.Future import akka.actor.typed.ActorSystem import akka.actor.typed.ActorRef +import akka.actor.typed.scaladsl.LoggerOps import akka.actor.typed.scaladsl.TimerScheduler import scala.concurrent.duration.FiniteDuration @@ -126,7 +127,7 @@ object StyleGuideDocExamples { Behaviors.same case Increment => val newValue = n + 1 - context.log.debug("[{}] Incremented counter to [{}]", name, newValue) + context.log.debug2("[{}] Incremented counter to [{}]", name, newValue) counter(name, timers, newValue) case GetValue(replyTo) => replyTo ! Value(n) @@ -160,16 +161,16 @@ object StyleGuideDocExamples { private def counter(setup: Setup, n: Int): Behavior[Command] = Behaviors.receiveMessage { case IncrementRepeatedly(interval) => - setup.context.log.debug( + setup.context.log.debugN( "[{}] Starting repeated increments with interval [{}], current count is [{}]", setup.name, - interval.toString, - n.toString) + interval, + n) setup.timers.startTimerWithFixedDelay("repeat", Increment, interval) Behaviors.same case Increment => val newValue = n + 1 - setup.context.log.debug("[{}] Incremented counter to [{}]", setup.name, newValue) + setup.context.log.debug2("[{}] Incremented counter to [{}]", setup.name, newValue) counter(setup, newValue) case GetValue(replyTo) => replyTo ! Value(n) @@ -207,16 +208,16 @@ object StyleGuideDocExamples { private def counter(n: Int): Behavior[Command] = Behaviors.receiveMessage { case IncrementRepeatedly(interval) => - context.log.debug( + context.log.debugN( "[{}] Starting repeated increments with interval [{}], current count is [{}]", name, - interval.toString, - n.toString) + interval, + n) timers.startTimerWithFixedDelay("repeat", Increment, interval) Behaviors.same case Increment => val newValue = n + 1 - context.log.debug("[{}] Incremented counter to [{}]", name, newValue) + context.log.debug2("[{}] Incremented counter to [{}]", name, newValue) counter(newValue) case GetValue(replyTo) => replyTo ! Value(n) @@ -243,16 +244,16 @@ object StyleGuideDocExamples { def counter(n: Int): Behavior[Command] = Behaviors.receiveMessage { case IncrementRepeatedly(interval) => - context.log.debug( + context.log.debugN( "[{}] Starting repeated increments with interval [{}], current count is [{}]", name, - interval.toString, - n.toString) + interval, + n) timers.startTimerWithFixedDelay("repeat", Increment, interval) Behaviors.same case Increment => val newValue = n + 1 - context.log.debug("[{}] Incremented counter to [{}]", name, newValue) + context.log.debug2("[{}] Incremented counter to [{}]", name, newValue) counter(newValue) case GetValue(replyTo) => replyTo ! Value(n) @@ -353,11 +354,11 @@ object StyleGuideDocExamples { Behaviors.receiveMessage { case Increment => val newValue = n + 1 - context.log.debug("[{}] Incremented counter to [{}]", name, newValue) + context.log.debug2("[{}] Incremented counter to [{}]", name, newValue) counter(newValue) case Tick => val newValue = n + 1 - context.log.debug("[{}] Incremented counter by background tick to [{}]", name, newValue) + context.log.debug2("[{}] Incremented counter by background tick to [{}]", name, newValue) counter(newValue) case GetValue(replyTo) => replyTo ! Value(n) @@ -404,11 +405,11 @@ object StyleGuideDocExamples { Behaviors.receiveMessage { case Increment => val newValue = n + 1 - context.log.debug("[{}] Incremented counter to [{}]", name, newValue) + context.log.debug2("[{}] Incremented counter to [{}]", name, newValue) counter(newValue) case Tick => val newValue = n + 1 - context.log.debug("[{}] Incremented counter by background tick to [{}]", name, newValue) + context.log.debug2("[{}] Incremented counter by background tick to [{}]", name, newValue) counter(newValue) case GetValue(replyTo) => replyTo ! Value(n) diff --git a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/coexistence/ClassicWatchingTypedSpec.scala b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/coexistence/ClassicWatchingTypedSpec.scala index 55051293cf..b1547c36f8 100644 --- a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/coexistence/ClassicWatchingTypedSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/coexistence/ClassicWatchingTypedSpec.scala @@ -5,6 +5,7 @@ package docs.akka.typed.coexistence import akka.actor.ActorLogging +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed._ import akka.actor.typed.scaladsl.Behaviors import akka.testkit.TestKit @@ -71,7 +72,7 @@ object ClassicWatchingTypedSpec { //#typed } -class ClassicWatchingTypedSpec extends WordSpec { +class ClassicWatchingTypedSpec extends WordSpec with LogCapturing { import ClassicWatchingTypedSpec._ diff --git a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/coexistence/TypedWatchingClassicSpec.scala b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/coexistence/TypedWatchingClassicSpec.scala index 4765819414..ce727407bc 100644 --- a/akka-actor-typed-tests/src/test/scala/docs/akka/typed/coexistence/TypedWatchingClassicSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/docs/akka/typed/coexistence/TypedWatchingClassicSpec.scala @@ -4,6 +4,7 @@ package docs.akka.typed.coexistence +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed._ import akka.actor.typed.scaladsl.Behaviors import akka.testkit.TestKit @@ -67,7 +68,7 @@ object TypedWatchingClassicSpec { //#classic } -class TypedWatchingClassicSpec extends WordSpec { +class TypedWatchingClassicSpec extends WordSpec with LogCapturing { import TypedWatchingClassicSpec._ diff --git a/akka-actor-typed/src/main/resources/reference.conf b/akka-actor-typed/src/main/resources/reference.conf index ba101309c0..a9de17de7f 100644 --- a/akka-actor-typed/src/main/resources/reference.conf +++ b/akka-actor-typed/src/main/resources/reference.conf @@ -45,3 +45,17 @@ akka.actor { "akka.actor.typed.internal.receptionist.DefaultServiceKey" = service-key } } + +# When using Akka Typed (having akka-actor-typed in classpath) the +# akka.event.slf4j.Slf4jLogger is enabled instead of the DefaultLogger +# even though it has not been explicitly defined in `akka.loggers` +# configuration. +# +# Slf4jLogger will be used for all Akka classic logging via eventStream, +# including logging from Akka internals. The Slf4jLogger is then using +# an ordinary org.slf4j.Logger to emit the log events. +# +# The Slf4jLoggingFilter is also enabled automatically. +# +# This behavior can be disabled by setting this property to `off`. +akka.use-slf4j = on diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/ActorSystem.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/ActorSystem.scala index 658f4c252f..ec98313ab2 100644 --- a/akka-actor-typed/src/main/scala/akka/actor/typed/ActorSystem.scala +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/ActorSystem.scala @@ -271,12 +271,6 @@ object ActorSystem { * This class is immutable. */ final class Settings(val config: Config, val classicSettings: classic.ActorSystem.Settings, val name: String) { - def this(classLoader: ClassLoader, config: Config, name: String) = - this({ - val cfg = config.withFallback(ConfigFactory.defaultReference(classLoader)) - cfg.checkValid(ConfigFactory.defaultReference(classLoader), "akka") - cfg - }, new classic.ActorSystem.Settings(classLoader, config, name), name) def this(settings: classic.ActorSystem.Settings) = this(settings.config, settings, settings.name) diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/ActorContextImpl.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/ActorContextImpl.scala index 07317c1362..a33455a314 100644 --- a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/ActorContextImpl.scala +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/ActorContextImpl.scala @@ -16,11 +16,13 @@ import java.util.function.BiFunction import scala.concurrent.{ ExecutionContextExecutor, Future } import scala.reflect.ClassTag import scala.util.Try + import akka.annotation.InternalApi import akka.util.OptionVal import akka.util.Timeout import akka.util.JavaDurationConverters._ import org.slf4j.Logger +import org.slf4j.LoggerFactory /** * INTERNAL API @@ -30,6 +32,10 @@ import org.slf4j.Logger with javadsl.ActorContext[T] with scaladsl.ActorContext[T] { + // lazily initialized + private var logger: OptionVal[Logger] = OptionVal.None + private var mdcUsed: Boolean = false + private var messageAdapterRef: OptionVal[ActorRef[Any]] = OptionVal.None private var _messageAdapters: List[(Class[_], Any => T)] = Nil private var _timer: OptionVal[TimerSchedulerImpl[T]] = OptionVal.None @@ -77,8 +83,42 @@ import org.slf4j.Logger override def getSystem: akka.actor.typed.ActorSystem[Void] = system.asInstanceOf[ActorSystem[Void]] + override def log: Logger = { + val l = logger match { + case OptionVal.Some(l) => l + case OptionVal.None => + val logClass = LoggerClass.detectLoggerClassFromStack(classOf[Behavior[_]]) + initLoggerWithName(logClass.getName) + } + // avoid access to MDC ThreadLocal if not needed + mdcUsed = true + ActorMdc.setMdc(self.path.toString) + l + } + override def getLog: Logger = log + override def setLoggerName(name: String): Unit = + initLoggerWithName(name) + + override def setLoggerName(clazz: Class[_]): Unit = + setLoggerName(clazz.getName) + + // MDC is cleared (if used) from aroundReceive in ActorAdapter after processing each message + override private[akka] def clearMdc(): Unit = { + // avoid access to MDC ThreadLocal if not needed + if (mdcUsed) { + ActorMdc.clearMdc() + mdcUsed = false + } + } + + private def initLoggerWithName(name: String): Logger = { + val l = LoggerFactory.getLogger(name) + logger = OptionVal.Some(l) + l + } + override def setReceiveTimeout(d: java.time.Duration, msg: T): Unit = setReceiveTimeout(d.asScala, msg) diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/ActorMdc.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/ActorMdc.scala new file mode 100644 index 0000000000..e1c4c6c98a --- /dev/null +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/ActorMdc.scala @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019 Lightbend Inc. + */ + +package akka.actor.typed.internal + +import akka.annotation.InternalApi +import org.slf4j.MDC + +/** + * INTERNAL API + */ +@InternalApi private[akka] object ActorMdc { + val SourceKey = "akkaSource" + + def setMdc(source: String): Unit = { + val mdcAdpater = MDC.getMDCAdapter + mdcAdpater.put(SourceKey, source) + } + + // MDC is cleared (if used) from aroundReceive in ActorAdapter after processing each message, + // via ActorContextImpl.clearMdc() + def clearMdc(): Unit = + MDC.clear() + +} diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/InterceptorImpl.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/InterceptorImpl.scala index 269ac8f1c2..8960838526 100644 --- a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/InterceptorImpl.scala +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/InterceptorImpl.scala @@ -11,7 +11,7 @@ import akka.actor.typed.LogOptions import akka.actor.typed._ import akka.annotation.InternalApi import akka.util.LineNumbers -import org.slf4j.{ LoggerFactory, MDC } +import org.slf4j.LoggerFactory import org.slf4j.event.Level /** @@ -113,7 +113,7 @@ private[akka] final class InterceptorImpl[O, I]( } /** - * Fire off any incoming signal to another actor before receiving it ourselves. + * Fire off any incoming message to another actor before receiving it ourselves. * * INTERNAL API */ @@ -142,6 +142,9 @@ private[akka] final case class MonitorInterceptor[T: ClassTag](actorRef: ActorRe def apply[T](opts: LogOptions): BehaviorInterceptor[T, T] = { new LogMessagesInterceptor(opts).asInstanceOf[BehaviorInterceptor[T, T]] } + + private val LogMessageTemplate = "actor [{}] received message: {}" + private val LogSignalTemplate = "actor [{}] received signal: {}" } /** @@ -153,49 +156,32 @@ private[akka] final case class MonitorInterceptor[T: ClassTag](actorRef: ActorRe private[akka] final class LogMessagesInterceptor(val opts: LogOptions) extends BehaviorInterceptor[Any, Any] { import BehaviorInterceptor._ + import LogMessagesInterceptor._ - private val log = opts.getLogger.orElse(LoggerFactory.getLogger(classOf[BehaviorInterceptor[Any, Any]])) + private val logger = opts.getLogger.orElse(LoggerFactory.getLogger(getClass)) override def aroundReceive(ctx: TypedActorContext[Any], msg: Any, target: ReceiveTarget[Any]): Behavior[Any] = { - if (opts.enabled) { - val actorPath = ctx.asScala.self.path.toString - val finalMsg = s"actor $actorPath received message ${msg}${addMDC}" - - opts.level match { - case Level.ERROR => log.error(finalMsg) - case Level.WARN => log.warn(finalMsg) - case Level.INFO => log.info(finalMsg) - case Level.DEBUG => log.debug(finalMsg) - case Level.TRACE => log.trace(finalMsg) - //TODO check this debug case is actually best option - case _ => log.debug(finalMsg) - } - } + log(LogMessageTemplate, msg, ctx) target(ctx, msg) } - def addMDC: String = { - import scala.collection.JavaConverters._ - if (MDC.getMDCAdapter.getCopyOfContextMap != null) - s" MDC is ${MDC.getMDCAdapter.getCopyOfContextMap.asScala}" - else "" - } override def aroundSignal(ctx: TypedActorContext[Any], signal: Signal, target: SignalTarget[Any]): Behavior[Any] = { + log(LogSignalTemplate, signal, ctx) + target(ctx, signal) + } + + private def log(template: String, messageOrSignal: Any, context: TypedActorContext[Any]): Unit = { if (opts.enabled) { - val actorPath = ctx.asScala.self.path.toString - val finalSignal = s"actor $actorPath received signal ${signal}${addMDC}" + val selfPath = context.asScala.self.path opts.level match { - case Level.ERROR => log.error(finalSignal) - case Level.WARN => log.warn(finalSignal) - case Level.INFO => log.info(finalSignal) - case Level.DEBUG => log.debug(finalSignal) - case Level.TRACE => log.trace(finalSignal) - //TODO check this debug case is actually best option - case _ => log.debug(finalSignal) + case Level.ERROR => logger.error(template, selfPath, messageOrSignal) + case Level.WARN => logger.warn(template, selfPath, messageOrSignal) + case Level.INFO => logger.info(template, selfPath, messageOrSignal) + case Level.DEBUG => logger.debug(template, selfPath, messageOrSignal) + case Level.TRACE => logger.trace(template, selfPath, messageOrSignal) + case other => throw new IllegalArgumentException(s"Unknown log level [$other].") } } - - target(ctx, signal) } // only once in the same behavior stack @@ -203,7 +189,6 @@ private[akka] final class LogMessagesInterceptor(val opts: LogOptions) extends B case a: LogMessagesInterceptor => a.opts == opts case _ => false } - } /** diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/Supervision.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/Supervision.scala index 51b12a6ddb..1feca5dd4a 100644 --- a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/Supervision.scala +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/Supervision.scala @@ -7,20 +7,26 @@ package internal import java.util.concurrent.ThreadLocalRandom -import akka.actor.{ DeadLetterSuppression, Dropped } -import akka.actor.typed.BehaviorInterceptor.{ PreStartTarget, ReceiveTarget, SignalTarget } -import akka.actor.typed.SupervisorStrategy._ -import akka.actor.typed.scaladsl.{ Behaviors, StashBuffer } -import akka.annotation.InternalApi -import akka.event.Logging -import akka.util.{ unused, OptionVal } -import org.slf4j.event.Level - -import scala.concurrent.duration.{ Deadline, FiniteDuration } +import scala.concurrent.duration.Deadline +import scala.concurrent.duration.FiniteDuration import scala.reflect.ClassTag import scala.util.control.Exception.Catcher import scala.util.control.NonFatal +import akka.actor.DeadLetterSuppression +import akka.actor.Dropped +import akka.actor.typed.BehaviorInterceptor.PreStartTarget +import akka.actor.typed.BehaviorInterceptor.ReceiveTarget +import akka.actor.typed.BehaviorInterceptor.SignalTarget +import akka.actor.typed.SupervisorStrategy._ +import akka.actor.typed.scaladsl.Behaviors +import akka.actor.typed.scaladsl.StashBuffer +import akka.annotation.InternalApi +import akka.event.Logging +import akka.util.OptionVal +import akka.util.unused +import org.slf4j.event.Level + /** * INTERNAL API */ @@ -49,6 +55,9 @@ private abstract class AbstractSupervisor[I, Thr <: Throwable](strategy: Supervi private val throwableClass = implicitly[ClassTag[Thr]].runtimeClass + protected def isInstanceOfTheThrowableClass(t: Throwable): Boolean = + throwableClass.isAssignableFrom(UnstashException.unwrap(t).getClass) + override def isSame(other: BehaviorInterceptor[Any, Any]): Boolean = { other match { case as: AbstractSupervisor[_, Thr] if throwableClass == as.throwableClass => true @@ -69,22 +78,17 @@ private abstract class AbstractSupervisor[I, Thr <: Throwable](strategy: Supervi } def log(ctx: TypedActorContext[_], t: Throwable): Unit = { - val msg = s"Supervisor $this saw failure:" if (strategy.loggingEnabled) { val unwrapped = UnstashException.unwrap(t) + val logMessage = s"Supervisor $this saw failure: ${unwrapped.getMessage}" + val logger = ctx.asScala.log strategy.logLevel match { - case Level.ERROR => - ctx.asScala.log.error(msg, unwrapped) - case Level.WARN => - ctx.asScala.log.warn(msg, unwrapped) - case Level.INFO => - ctx.asScala.log.info(msg, unwrapped) - case Level.DEBUG => - ctx.asScala.log.debug(msg, unwrapped) - case Level.TRACE => - ctx.asScala.log.trace(msg, unwrapped) - //TODO check this debug case is actually best option when other level is found - case _ => ctx.asScala.log.debug(msg, unwrapped) + case Level.ERROR => logger.error(logMessage, unwrapped) + case Level.WARN => logger.warn(logMessage, unwrapped) + case Level.INFO => logger.info(logMessage, unwrapped) + case Level.DEBUG => logger.debug(logMessage, unwrapped) + case Level.TRACE => logger.trace(logMessage, unwrapped) + case other => throw new IllegalArgumentException(s"Unknown log level [$other].") } } } @@ -95,16 +99,11 @@ private abstract class AbstractSupervisor[I, Thr <: Throwable](strategy: Supervi .publish(Dropped(signalOrMessage, s"Stash is full in [${getClass.getSimpleName}]", ctx.asScala.self.toClassic)) } - override def toString: String = Logging.simpleName(getClass) - - protected def isInstanceOfTheThrowableClass(t: Throwable): Boolean = - throwableClass.isAssignableFrom(UnstashException.unwrap(t).getClass) - protected def handleExceptionOnStart(ctx: TypedActorContext[Any], target: PreStartTarget[I]): Catcher[Behavior[I]] - protected def handleSignalException(ctx: TypedActorContext[Any], target: SignalTarget[I]): Catcher[Behavior[I]] - protected def handleReceiveException(ctx: TypedActorContext[Any], target: ReceiveTarget[I]): Catcher[Behavior[I]] + + override def toString: String = Logging.simpleName(getClass) } /** @@ -119,9 +118,6 @@ private abstract class SimpleSupervisor[T, Thr <: Throwable: ClassTag](ss: Super } catch handleReceiveException(ctx, target) } - protected def handleReceiveException(ctx: TypedActorContext[Any], target: ReceiveTarget[T]): Catcher[Behavior[T]] = - handleException(ctx) - protected def handleException(@unused ctx: TypedActorContext[Any]): Catcher[Behavior[T]] = { case NonFatal(t) if isInstanceOfTheThrowableClass(t) => BehaviorImpl.failed(t) @@ -130,9 +126,10 @@ private abstract class SimpleSupervisor[T, Thr <: Throwable: ClassTag](ss: Super // convenience if target not required to handle exception protected def handleExceptionOnStart(ctx: TypedActorContext[Any], target: PreStartTarget[T]): Catcher[Behavior[T]] = handleException(ctx) - protected def handleSignalException(ctx: TypedActorContext[Any], target: SignalTarget[T]): Catcher[Behavior[T]] = handleException(ctx) + protected def handleReceiveException(ctx: TypedActorContext[Any], target: ReceiveTarget[T]): Catcher[Behavior[T]] = + handleException(ctx) } private class StopSupervisor[T, Thr <: Throwable: ClassTag](@unused initial: Behavior[T], strategy: Stop) @@ -190,6 +187,11 @@ private class RestartSupervisor[T, Thr <: Throwable: ClassTag](initial: Behavior private var gotScheduledRestart = true private var deadline: OptionVal[Deadline] = OptionVal.None + private def deadlineHasTimeLeft: Boolean = deadline match { + case OptionVal.None => true + case OptionVal.Some(d) => d.hasTimeLeft + } + override def aroundSignal(ctx: TypedActorContext[Any], signal: Signal, target: SignalTarget[T]): Behavior[T] = { restartingInProgress match { case OptionVal.None => @@ -290,7 +292,6 @@ private class RestartSupervisor[T, Thr <: Throwable: ClassTag](initial: Behavior case _ => target(ctx, PreRestart) }) } - override protected def handleReceiveException( ctx: TypedActorContext[Any], target: ReceiveTarget[T]): Catcher[Behavior[T]] = { @@ -300,11 +301,6 @@ private class RestartSupervisor[T, Thr <: Throwable: ClassTag](initial: Behavior }) } - private def deadlineHasTimeLeft: Boolean = deadline match { - case OptionVal.None => true - case OptionVal.Some(d) => d.hasTimeLeft - } - private def handleException(ctx: TypedActorContext[Any], signalRestart: Throwable => Unit): Catcher[Behavior[T]] = { case NonFatal(t) if isInstanceOfTheThrowableClass(t) => ctx.asScala.cancelAllTimers() diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/TimerSchedulerImpl.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/TimerSchedulerImpl.scala index e6cc67e266..90cfca4a4c 100644 --- a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/TimerSchedulerImpl.scala +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/TimerSchedulerImpl.scala @@ -11,6 +11,7 @@ import scala.concurrent.duration.FiniteDuration import akka.actor.Cancellable import akka.actor.NotInfluenceReceiveTimeout import akka.actor.typed.scaladsl.ActorContext +import akka.actor.typed.scaladsl.LoggerOps import akka.annotation.InternalApi import akka.dispatch.ExecutionContexts import akka.util.JavaDurationConverters._ @@ -158,9 +159,12 @@ import org.slf4j.Logger OptionVal.Some(t.msg) } else { // it was from an old timer that was enqueued in mailbox before canceled - log.debug( - "Received timer [{}] from old generation [{}], expected generation [{}], discarding", - Array(timerMsg.key, timerMsg.generation, t.generation)) + if (log.isDebugEnabled) + log.debugN( + "Received timer [{}] from old generation [{}], expected generation [{}], discarding", + timerMsg.key, + timerMsg.generation, + t.generation) OptionVal.none // message should be ignored } } diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/WithMdcBehaviorInterceptor.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/WithMdcBehaviorInterceptor.scala index 21aa58c4f2..9a947abe0c 100644 --- a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/WithMdcBehaviorInterceptor.scala +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/WithMdcBehaviorInterceptor.scala @@ -7,9 +7,7 @@ package akka.actor.typed.internal import akka.actor.typed.{ Behavior, BehaviorInterceptor, Signal, TypedActorContext } import akka.annotation.InternalApi import org.slf4j.MDC -import scala.collection.JavaConverters._ -import scala.collection.immutable.HashMap import scala.reflect.ClassTag /** @@ -74,34 +72,30 @@ import scala.reflect.ClassTag } override def aroundReceive(ctx: TypedActorContext[T], msg: T, target: ReceiveTarget[T]): Behavior[T] = { - val mdc = merge(staticMdc, mdcForMessage(msg)) - MDC.getMDCAdapter.setContextMap(mdc.asJava) - val next = - try { - target(ctx, msg) - } finally { - MDC.clear() - } - next + try { + setMdcValues(mdcForMessage(msg)) + target(ctx, msg) + } finally { + MDC.clear() + } } override def aroundSignal(ctx: TypedActorContext[T], signal: Signal, target: SignalTarget[T]): Behavior[T] = { - MDC.getMDCAdapter.setContextMap(staticMdc.asJava) try { + setMdcValues(Map.empty) target(ctx, signal) } finally { MDC.clear() } } - private def merge(staticMdc: Map[String, String], mdcForMessage: Map[String, String]): Map[String, String] = { - if (staticMdc.isEmpty) mdcForMessage - else if (mdcForMessage.isEmpty) staticMdc - else if (staticMdc.isInstanceOf[HashMap[String, String]] && mdcForMessage.isInstanceOf[HashMap[String, String]]) { - // merged is more efficient than ++ - mdcForMessage.asInstanceOf[HashMap[String, String]].merged(staticMdc.asInstanceOf[HashMap[String, String]])(null) - } else { - staticMdc ++ mdcForMessage + private def setMdcValues(dynamicMdc: Map[String, String]): Unit = { + val mdcAdapter = MDC.getMDCAdapter + if (staticMdc.nonEmpty) staticMdc.foreach { + case (key, value) => mdcAdapter.put(key, value) + } + if (dynamicMdc.nonEmpty) dynamicMdc.foreach { + case (key, value) => mdcAdapter.put(key, value) } } diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/adapter/ActorAdapter.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/adapter/ActorAdapter.scala index 25794370b2..900ba9cee1 100644 --- a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/adapter/ActorAdapter.scala +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/adapter/ActorAdapter.scala @@ -72,34 +72,36 @@ import akka.util.OptionVal def receive: Receive = ActorAdapter.DummyReceive override protected[akka] def aroundReceive(receive: Receive, msg: Any): Unit = { - // as we know we never become in "normal" typed actors, it is just the current behavior that - // changes, we can avoid some overhead with the partial function/behavior stack of classic entirely - // we also know that the receive is total, so we can avoid the orElse part as well. - msg match { - case classic.Terminated(ref) => - val msg = - if (failures contains ref) { - val ex = failures(ref) - failures -= ref - ChildFailed(ActorRefAdapter(ref), ex) - } else Terminated(ActorRefAdapter(ref)) - handleSignal(msg) - case classic.ReceiveTimeout => - handleMessage(ctx.receiveTimeoutMsg) - case wrapped: AdaptMessage[Any, T] @unchecked => - withSafelyAdapted(() => wrapped.adapt()) { - case AdaptWithRegisteredMessageAdapter(msg) => - adaptAndHandle(msg) - case msg: T @unchecked => - handleMessage(msg) - } - case AdaptWithRegisteredMessageAdapter(msg) => - adaptAndHandle(msg) - case signal: Signal => - handleSignal(signal) - case msg: T @unchecked => - handleMessage(msg) - } + try { + // as we know we never become in "normal" typed actors, it is just the current behavior that + // changes, we can avoid some overhead with the partial function/behavior stack of untyped entirely + // we also know that the receive is total, so we can avoid the orElse part as well. + msg match { + case classic.Terminated(ref) => + val msg = + if (failures contains ref) { + val ex = failures(ref) + failures -= ref + ChildFailed(ActorRefAdapter(ref), ex) + } else Terminated(ActorRefAdapter(ref)) + handleSignal(msg) + case classic.ReceiveTimeout => + handleMessage(ctx.receiveTimeoutMsg) + case wrapped: AdaptMessage[Any, T] @unchecked => + withSafelyAdapted(() => wrapped.adapt()) { + case AdaptWithRegisteredMessageAdapter(msg) => + adaptAndHandle(msg) + case msg: T @unchecked => + handleMessage(msg) + } + case AdaptWithRegisteredMessageAdapter(msg) => + adaptAndHandle(msg) + case signal: Signal => + handleSignal(signal) + case msg: T @unchecked => + handleMessage(msg) + } + } finally ctx.clearMdc() } private def handleMessage(msg: T): Unit = { @@ -179,7 +181,7 @@ import akka.util.OptionVal case Success(a) => body(a) case Failure(ex) => - log.error(ex, "Exception thrown out of adapter. Stopping myself.") + log.error(ex, s"Exception thrown out of adapter. Stopping myself. ${ex.getMessage}") context.stop(self) } } @@ -233,33 +235,41 @@ import akka.util.OptionVal } override def preStart(): Unit = { - if (Behavior.isAlive(behavior)) { - behavior = Behavior.validateAsInitial(Behavior.start(behavior, ctx)) - } - // either was stopped initially or became stopped on start - if (!Behavior.isAlive(behavior)) context.stop(self) + try { + if (Behavior.isAlive(behavior)) { + behavior = Behavior.validateAsInitial(Behavior.start(behavior, ctx)) + } + // either was stopped initially or became stopped on start + if (!Behavior.isAlive(behavior)) context.stop(self) + } finally ctx.clearMdc() } override def preRestart(reason: Throwable, message: Option[Any]): Unit = { - ctx.cancelAllTimers() - Behavior.interpretSignal(behavior, ctx, PreRestart) - behavior = BehaviorImpl.stopped + try { + ctx.cancelAllTimers() + Behavior.interpretSignal(behavior, ctx, PreRestart) + behavior = BehaviorImpl.stopped + } finally ctx.clearMdc() } override def postRestart(reason: Throwable): Unit = { - ctx.cancelAllTimers() - behavior = Behavior.validateAsInitial(Behavior.start(behavior, ctx)) - if (!Behavior.isAlive(behavior)) context.stop(self) + try { + ctx.cancelAllTimers() + behavior = Behavior.validateAsInitial(Behavior.start(behavior, ctx)) + if (!Behavior.isAlive(behavior)) context.stop(self) + } finally ctx.clearMdc() } override def postStop(): Unit = { - ctx.cancelAllTimers() - behavior match { - case _: DeferredBehavior[_] => - // Do not undefer a DeferredBehavior as that may cause creation side-effects, which we do not want on termination. - case b => Behavior.interpretSignal(b, ctx, PostStop) - } - behavior = BehaviorImpl.stopped + try { + ctx.cancelAllTimers() + behavior match { + case _: DeferredBehavior[_] => + // Do not undefer a DeferredBehavior as that may cause creation side-effects, which we do not want on termination. + case b => Behavior.interpretSignal(b, ctx, PostStop) + } + behavior = BehaviorImpl.stopped + } finally ctx.clearMdc() } } diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/adapter/ActorContextAdapter.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/adapter/ActorContextAdapter.scala index 4a00ec6c4f..e7971186d5 100644 --- a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/adapter/ActorContextAdapter.scala +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/adapter/ActorContextAdapter.scala @@ -7,8 +7,6 @@ package internal package adapter import akka.annotation.InternalApi -import akka.util.OptionVal -import org.slf4j.{ Logger, LoggerFactory } import akka.{ actor => classic } import scala.concurrent.ExecutionContextExecutor @@ -57,9 +55,6 @@ private[akka] object ActorContextAdapter { private[akka] override def currentBehavior: Behavior[T] = adapter.currentBehavior - // lazily initialized - private var actorLogger: OptionVal[Logger] = OptionVal.None - final override val self = ActorRefAdapter(classicContext.self) final override val system = ActorSystemAdapter(classicContext.system) private[akka] def classicActorContext = classicContext @@ -120,27 +115,6 @@ private[akka] object ActorContextAdapter { ActorRefAdapter[U](ref) } - private def initLoggerWithClass(logClass: Class[_]): Logger = { - val logger = LoggerFactory.getLogger(logClass) - //TODO remove it from MDC. Under discussion - org.slf4j.MDC.put("actorPath", self.path.name) - actorLogger = OptionVal.Some(logger) - logger - } - - override def log: Logger = { - actorLogger match { - case OptionVal.Some(logger) => logger - case OptionVal.None => - val logClass = LoggerClass.detectLoggerClassFromStack(classOf[Behavior[_]]) - initLoggerWithClass(logClass) - } - } - - override def setLoggerClass(clazz: Class[_]): Unit = { - initLoggerWithClass(clazz) - } - /** * Made accessible to allow stash to deal with unhandled messages as though they were interpreted by * the adapter itself, even though the unstashing occurs inside the behavior stack. diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/receptionist/LocalReceptionist.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/receptionist/LocalReceptionist.scala index a5982dfd29..2d83dcef7d 100644 --- a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/receptionist/LocalReceptionist.scala +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/receptionist/LocalReceptionist.scala @@ -4,11 +4,14 @@ package akka.actor.typed.internal.receptionist -import akka.actor.typed.{ ActorRef, Behavior, Terminated } +import akka.actor.typed.ActorRef +import akka.actor.typed.Behavior +import akka.actor.typed.Terminated import akka.actor.typed.receptionist.Receptionist._ import akka.actor.typed.receptionist.ServiceKey -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } -import akka.actor.typed.scaladsl.Behaviors.{ receive, same } +import akka.actor.typed.scaladsl.ActorContext +import akka.actor.typed.scaladsl.Behaviors +import akka.actor.typed.scaladsl.LoggerOps import akka.annotation.InternalApi import akka.util.TypedMultiMap @@ -45,7 +48,7 @@ private[akka] object LocalReceptionist extends ReceptionistBehaviorProvider { extends InternalCommand override def behavior: Behavior[Command] = Behaviors.setup { ctx => - ctx.setLoggerClass(classOf[LocalReceptionist]) + ctx.setLoggerName(classOf[LocalReceptionist]) behavior(TypedMultiMap.empty[AbstractServiceKey, KV], TypedMultiMap.empty[AbstractServiceKey, SubscriptionsKV]) .narrow[Command] } @@ -98,7 +101,7 @@ private[akka] object LocalReceptionist extends ReceptionistBehaviorProvider { def onCommand(ctx: ActorContext[Any], cmd: Command): Behavior[Any] = cmd match { case ReceptionistMessages.Register(key, serviceInstance, maybeReplyTo) => - ctx.log.debug("Actor was registered: {} {}", Array(key, serviceInstance)) + ctx.log.debug2("Actor was registered: {} {}", key, serviceInstance) watchWith(ctx, serviceInstance, RegisteredActorTerminated(key, serviceInstance)) maybeReplyTo match { case Some(replyTo) => replyTo ! ReceptionistMessages.Registered(key, serviceInstance) @@ -108,7 +111,7 @@ private[akka] object LocalReceptionist extends ReceptionistBehaviorProvider { case ReceptionistMessages.Find(key, replyTo) => replyWithListing(key, replyTo) - same + Behaviors.same case ReceptionistMessages.Subscribe(key, subscriber) => watchWith(ctx, subscriber, SubscriberTerminated(key, subscriber)) @@ -121,14 +124,14 @@ private[akka] object LocalReceptionist extends ReceptionistBehaviorProvider { def onInternal(ctx: ActorContext[Any], cmd: InternalCommand): Behavior[Any] = cmd match { case RegisteredActorTerminated(key, serviceInstance) => - ctx.log.debug("Registered actor terminated: {} {}", Array(key, serviceInstance)) + ctx.log.debug2("Registered actor terminated: {} {}", key, serviceInstance) updateRegistry(Set(key), _.removed(key)(serviceInstance)) case SubscriberTerminated(key, subscriber) => next(newSubscriptions = subscriptions.removed(key)(subscriber)) } - receive[Any] { (ctx, msg) => + Behaviors.receive[Any] { (ctx, msg) => msg match { case cmd: Command => onCommand(ctx, cmd) case cmd: InternalCommand => onInternal(ctx, cmd) diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/javadsl/ActorContext.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/javadsl/ActorContext.scala index 18c14d49b4..70628d41ff 100644 --- a/akka-actor-typed/src/main/scala/akka/actor/typed/javadsl/ActorContext.scala +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/javadsl/ActorContext.scala @@ -68,7 +68,11 @@ trait ActorContext[T] extends TypedActorContext[T] with ClassicActorContextProvi def getSystem: ActorSystem[Void] /** - * An actor specific logger + * An actor specific logger. + * + * The logger name will be an estimated source class for the actor which is calculated when the + * logger is first used (the logger is lazily created upon first use). If this yields the wrong + * class or another class is preferred this can be changed with `setLoggerName`. * * *Warning*: This method is not thread-safe and must not be accessed from threads other * than the ordinary actor message processing thread, such as [[java.util.concurrent.CompletionStage]] callbacks. @@ -77,12 +81,21 @@ trait ActorContext[T] extends TypedActorContext[T] with ClassicActorContextProvi /** * Replace the current logger (or initialize a new logger if the logger was not touched before) with one that - * has ghe given class as logging class. Logger source will be actor path. + * has ghe given name as logger name. Logger source MDC entry "akkaSource" will be the actor path. * * *Warning*: This method is not thread-safe and must not be accessed from threads other * than the ordinary actor message processing thread, such as [[java.util.concurrent.CompletionStage]] callbacks. */ - def setLoggerClass(clazz: Class[_]): Unit + def setLoggerName(name: String): Unit + + /** + * Replace the current logger (or initialize a new logger if the logger was not touched before) with one that + * has ghe given class name as logger name. Logger source MDC entry "akkaSource" will be the actor path. + * + * *Warning*: This method is not thread-safe and must not be accessed from threads other + * than the ordinary actor message processing thread, such as [[java.util.concurrent.CompletionStage]] callbacks. + */ + def setLoggerName(clazz: Class[_]): Unit /** * The list of child Actors created by this Actor during its lifetime that diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/scaladsl/ActorContext.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/scaladsl/ActorContext.scala index d68b381d87..97d9c174f8 100644 --- a/akka-actor-typed/src/main/scala/akka/actor/typed/scaladsl/ActorContext.scala +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/scaladsl/ActorContext.scala @@ -68,10 +68,9 @@ trait ActorContext[T] extends TypedActorContext[T] with ClassicActorContextProvi /** * An actor specific logger. * - * The logger will have the actor path as `logSource` and will an estimated source class for the actor - * which is calculated when the logger is first used (the logger is lazily created upon first use). If this - * yields the wrong class or another class is preferred this can be achieved through `Logger.withLoggerClass` - * or `setLoggerClass`. + * The logger name will be an estimated source class for the actor which is calculated when the + * logger is first used (the logger is lazily created upon first use). If this yields the wrong + * class or another class is preferred this can be changed with `setLoggerName`. * * *Warning*: This method is not thread-safe and must not be accessed from threads other * than the ordinary actor message processing thread, such as [[scala.concurrent.Future]] callbacks. @@ -80,12 +79,21 @@ trait ActorContext[T] extends TypedActorContext[T] with ClassicActorContextProvi /** * Replace the current logger (or initialize a new logger if the logger was not touched before) with one that - * has ghe given class as logging class. Logger source will be actor path. + * has ghe given name as logger name. Logger source MDC entry "akkaSource" will be the actor path. + * + * *Warning*: This method is not thread-safe and must not be accessed from threads other + * than the ordinary actor message processing thread, such as [[java.util.concurrent.CompletionStage]] callbacks. + */ + def setLoggerName(name: String): Unit + + /** + * Replace the current logger (or initialize a new logger if the logger was not touched before) with one that + * has ghe given class name as logger name. Logger source MDC entry "akkaSource" will be the actor path. * * *Warning*: This method is not thread-safe and must not be accessed from threads other * than the ordinary actor message processing thread, such as [[scala.concurrent.Future]] callbacks. */ - def setLoggerClass(clazz: Class[_]): Unit + def setLoggerName(clazz: Class[_]): Unit /** * The list of child Actors created by this Actor during its lifetime that @@ -324,4 +332,10 @@ trait ActorContext[T] extends TypedActorContext[T] with ClassicActorContextProvi @InternalApi private[akka] def cancelAllTimers(): Unit + /** + * INTERNAL API + */ + @InternalApi + private[akka] def clearMdc(): Unit + } diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/scaladsl/package.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/scaladsl/package.scala new file mode 100644 index 0000000000..7995583e39 --- /dev/null +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/scaladsl/package.scala @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2019 Lightbend Inc. + */ + +package akka.actor.typed + +import org.slf4j.Logger +import org.slf4j.Marker + +package object scaladsl { + + /** + * Extension methods to [[org.slf4j.Logger]] that are useful because the Scala + * compiler can't select the right overloaded methods for some cases when using + * 2 template arguments and varargs (>= 3 arguments) with primitive types. + * + * Enable these extension methods with: + * + * {{{ + * import akka.actor.typed.scaladsl.LoggerOps + * }}} + * or + * {{{ + *import akka.actor.typed.scaladsl._ + * }}} + * + * @param log the underlying [[org.slf4j.Logger]] + */ + implicit class LoggerOps(val log: Logger) extends AnyVal { + + /** + * Log a message at the TRACE level according to the specified format + * and 2 arguments. + * + * This form avoids superfluous object creation when the logger + * is disabled for the TRACE level. + * + * @param format the format string + * @param arg1 the first argument + * @param arg2 the second argument + */ + def trace2(format: String, arg1: Any, arg2: Any): Unit = + log.trace(format, arg1, arg2) + + /** + * Log marker data and message at the TRACE level according to the specified format + * and 2 arguments. + * + * This form avoids superfluous object creation when the logger + * is disabled for the TRACE level. + * + * @param marker the marker data specific to this log statement + * @param format the format string + * @param arg1 the first argument + * @param arg2 the second argument + */ + def trace2(marker: Marker, format: String, arg1: Any, arg2: Any): Unit = + log.trace(marker, format, arg1, arg2) + + /** + * Log a message at the TRACE level according to the specified format + * and arguments. + * + * This form avoids superfluous string concatenation when the logger + * is disabled for the TRACE level. However, this variant incurs the hidden + * (and relatively small) cost of creating an `Array[Object]` before invoking the method, + * even if this logger is disabled for TRACE. The `trace` variants taking + * one and `trace2` taking two arguments exist solely in order to avoid this hidden cost. + * + * @param format the format string + * @param arguments a list of 3 or more arguments + */ + def traceN(format: String, arguments: Any*): Unit = { + val arr = arguments.toArray + if (arr.isInstanceOf[Array[Object]]) + log.trace(format, arr.asInstanceOf[Array[Object]]: _*) // this seems to always be the case + else + log.trace(format, arr.map(_.asInstanceOf[AnyRef]): _*) + } + + /** + * Log marker data and message at the TRACE level according to the specified format + * and arguments. + * + * This form avoids superfluous string concatenation when the logger + * is disabled for the TRACE level. However, this variant incurs the hidden + * (and relatively small) cost of creating an `Array[Object]` before invoking the method, + * even if this logger is disabled for TRACE. The `trace` variants taking + * one and `trace2` taking two arguments exist solely in order to avoid this hidden cost. + * + * @param format the format string + * @param arguments a list of 3 or more arguments + */ + def traceN(marker: Marker, format: String, arguments: Any*): Unit = { + val arr = arguments.toArray + if (arr.isInstanceOf[Array[Object]]) + log.trace(marker, format, arr.asInstanceOf[Array[Object]]: _*) // this seems to always be the case + else + log.trace(marker, format, arr.map(_.asInstanceOf[AnyRef]): _*) + } + + /** + * Log a message at the DEBUG level according to the specified format + * and 2 arguments. + * + * This form avoids superfluous object creation when the logger + * is disabled for the DEBUG level. + * + * @param format the format string + * @param arg1 the first argument + * @param arg2 the second argument + */ + def debug2(format: String, arg1: Any, arg2: Any): Unit = + log.debug(format, arg1, arg2) + + /** + * Log marker data and message at the DEBUG level according to the specified format + * and 2 arguments. + * + * This form avoids superfluous object creation when the logger + * is disabled for the DEBUG level. + * + * @param marker the marker data specific to this log statement + * @param format the format string + * @param arg1 the first argument + * @param arg2 the second argument + */ + def debug2(marker: Marker, format: String, arg1: Any, arg2: Any): Unit = + log.debug(marker, format, arg1, arg2) + + /** + * Log a message at the DEBUG level according to the specified format + * and arguments. + * + * This form avoids superfluous string concatenation when the logger + * is disabled for the DEBUG level. However, this variant incurs the hidden + * (and relatively small) cost of creating an `Array[Object]` before invoking the method, + * even if this logger is disabled for DEBUG. The `debug` variants taking + * one and `debug2` taking two arguments exist solely in order to avoid this hidden cost. + * + * @param format the format string + * @param arguments a list of 3 or more arguments + */ + def debugN(format: String, arguments: Any*): Unit = { + val arr = arguments.toArray + if (arr.isInstanceOf[Array[Object]]) + log.debug(format, arr.asInstanceOf[Array[Object]]: _*) // this seems to always be the case + else + log.debug(format, arr.map(_.asInstanceOf[AnyRef]): _*) + } + + /** + * Log marker data and message at the DEBUG level according to the specified format + * and arguments. + * + * This form avoids superfluous string concatenation when the logger + * is disabled for the DEBUG level. However, this variant incurs the hidden + * (and relatively small) cost of creating an `Array[Object]` before invoking the method, + * even if this logger is disabled for DEBUG. The `debug` variants taking + * one and `debug2` taking two arguments exist solely in order to avoid this hidden cost. + * + * @param format the format string + * @param arguments a list of 3 or more arguments + */ + def debugN(marker: Marker, format: String, arguments: Any*): Unit = { + val arr = arguments.toArray + if (arr.isInstanceOf[Array[Object]]) + log.debug(marker, format, arr.asInstanceOf[Array[Object]]: _*) // this seems to always be the case + else + log.debug(marker, format, arr.map(_.asInstanceOf[AnyRef]): _*) + } + + /** + * Log a message at the INFO level according to the specified format + * and 2 arguments. + * + * This form avoids superfluous object creation when the logger + * is disabled for the INFO level. + * + * @param format the format string + * @param arg1 the first argument + * @param arg2 the second argument + */ + def info2(format: String, arg1: Any, arg2: Any): Unit = + log.info(format, arg1, arg2) + + /** + * Log marker data and message at the INFO level according to the specified format + * and 2 arguments. + * + * This form avoids superfluous object creation when the logger + * is disabled for the INFO level. + * + * @param marker the marker data specific to this log statement + * @param format the format string + * @param arg1 the first argument + * @param arg2 the second argument + */ + def info2(marker: Marker, format: String, arg1: Any, arg2: Any): Unit = + log.info(marker, format, arg1, arg2) + + /** + * Log a message at the INFO level according to the specified format + * and arguments. + * + * This form avoids superfluous string concatenation when the logger + * is disabled for the INFO level. However, this variant incurs the hidden + * (and relatively small) cost of creating an `Array[Object]` before invoking the method, + * even if this logger is disabled for INFO. The `info` variants taking + * one and `info2` taking two arguments exist solely in order to avoid this hidden cost. + * + * @param format the format string + * @param arguments a list of 3 or more arguments + */ + def infoN(format: String, arguments: Any*): Unit = { + val arr = arguments.toArray + if (arr.isInstanceOf[Array[Object]]) + log.info(format, arr.asInstanceOf[Array[Object]]: _*) // this seems to always be the case + else + log.info(format, arr.map(_.asInstanceOf[AnyRef]): _*) + } + + /** + * Log marker data and message at the INFO level according to the specified format + * and arguments. + * + * This form avoids superfluous string concatenation when the logger + * is disabled for the INFO level. However, this variant incurs the hidden + * (and relatively small) cost of creating an `Array[Object]` before invoking the method, + * even if this logger is disabled for INFO. The `info` variants taking + * one and `info2` taking two arguments exist solely in order to avoid this hidden cost. + * + * @param format the format string + * @param arguments a list of 3 or more arguments + */ + def infoN(marker: Marker, format: String, arguments: Any*): Unit = { + val arr = arguments.toArray + if (arr.isInstanceOf[Array[Object]]) + log.info(marker, format, arr.asInstanceOf[Array[Object]]: _*) // this seems to always be the case + else + log.info(marker, format, arr.map(_.asInstanceOf[AnyRef]): _*) + } + + /** + * Log a message at the WARN level according to the specified format + * and 2 arguments. + * + * This form avoids superfluous object creation when the logger + * is disabled for the WARN level. + * + * @param format the format string + * @param arg1 the first argument + * @param arg2 the second argument + */ + def warn2(format: String, arg1: Any, arg2: Any): Unit = + log.warn(format, arg1, arg2) + + /** + * Log marker data and message at the WARN level according to the specified format + * and 2 arguments. + * + * This form avoids superfluous object creation when the logger + * is disabled for the WARN level. + * + * @param marker the marker data specific to this log statement + * @param format the format string + * @param arg1 the first argument + * @param arg2 the second argument + */ + def warn2(marker: Marker, format: String, arg1: Any, arg2: Any): Unit = + log.warn(marker, format, arg1, arg2) + + /** + * Log a message at the WARN level according to the specified format + * and arguments. + * + * This form avoids superfluous string concatenation when the logger + * is disabled for the WARN level. However, this variant incurs the hidden + * (and relatively small) cost of creating an `Array[Object]` before invoking the method, + * even if this logger is disabled for WARN. The `warn` variants taking + * one and `warn2` taking two arguments exist solely in order to avoid this hidden cost. + * + * @param format the format string + * @param arguments a list of 3 or more arguments + */ + def warnN(format: String, arguments: Any*): Unit = { + val arr = arguments.toArray + if (arr.isInstanceOf[Array[Object]]) + log.warn(format, arr.asInstanceOf[Array[Object]]: _*) // this seems to always be the case + else + log.warn(format, arr.map(_.asInstanceOf[AnyRef]): _*) + } + + /** + * Log marker data and message at the WARN level according to the specified format + * and arguments. + * + * This form avoids superfluous string concatenation when the logger + * is disabled for the WARN level. However, this variant incurs the hidden + * (and relatively small) cost of creating an `Array[Object]` before invoking the method, + * even if this logger is disabled for WARN. The `warn` variants taking + * one and `warn2` taking two arguments exist solely in order to avoid this hidden cost. + * + * @param format the format string + * @param arguments a list of 3 or more arguments + */ + def warnN(marker: Marker, format: String, arguments: Any*): Unit = { + val arr = arguments.toArray + if (arr.isInstanceOf[Array[Object]]) + log.warn(marker, format, arr.asInstanceOf[Array[Object]]: _*) // this seems to always be the case + else + log.warn(marker, format, arr.map(_.asInstanceOf[AnyRef]): _*) + } + + /** + * Log a message at the ERROR level according to the specified format + * and 2 arguments. + * + * This form avoids superfluous object creation when the logger + * is disabled for the ERROR level. + * + * @param format the format string + * @param arg1 the first argument + * @param arg2 the second argument + */ + def error2(format: String, arg1: Any, arg2: Any): Unit = + log.error(format, arg1, arg2) + + /** + * Log marker data and message at the ERROR level according to the specified format + * and 2 arguments. + * + * This form avoids superfluous object creation when the logger + * is disabled for the ERROR level. + * + * @param marker the marker data specific to this log statement + * @param format the format string + * @param arg1 the first argument + * @param arg2 the second argument + */ + def error2(marker: Marker, format: String, arg1: Any, arg2: Any): Unit = + log.error(marker, format, arg1, arg2) + + /** + * Log a message at the ERROR level according to the specified format + * and arguments. + * + * This form avoids superfluous string concatenation when the logger + * is disabled for the ERROR level. However, this variant incurs the hidden + * (and relatively small) cost of creating an `Array[Object]` before invoking the method, + * even if this logger is disabled for ERROR. The `error` variants taking + * one and `error2` taking two arguments exist solely in order to avoid this hidden cost. + * + * @param format the format string + * @param arguments a list of 3 or more arguments + */ + def errorN(format: String, arguments: Any*): Unit = { + val arr = arguments.toArray + if (arr.isInstanceOf[Array[Object]]) + log.error(format, arr.asInstanceOf[Array[Object]]: _*) // this seems to always be the case + else + log.error(format, arr.map(_.asInstanceOf[AnyRef]): _*) + } + + /** + * Log marker data and message at the ERROR level according to the specified format + * and arguments. + * + * This form avoids superfluous string concatenation when the logger + * is disabled for the ERROR level. However, this variant incurs the hidden + * (and relatively small) cost of creating an `Array[Object]` before invoking the method, + * even if this logger is disabled for ERROR. The `error` variants taking + * one and `error2` taking two arguments exist solely in order to avoid this hidden cost. + * + * @param format the format string + * @param arguments a list of 3 or more arguments + */ + def errorN(marker: Marker, format: String, arguments: Any*): Unit = { + val arr = arguments.toArray + if (arr.isInstanceOf[Array[Object]]) + log.error(marker, format, arr.asInstanceOf[Array[Object]]: _*) // this seems to always be the case + else + log.error(marker, format, arr.map(_.asInstanceOf[AnyRef]): _*) + } + + } + +} diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala index ce7efc67ea..0374caa8da 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala @@ -20,7 +20,6 @@ import akka.japi.Util.immutableSeq import akka.util.Helpers.toRootLowerCase import akka.util._ import com.typesafe.config.{ Config, ConfigFactory } - import scala.annotation.tailrec import scala.collection.immutable import scala.compat.java8.FutureConverters @@ -30,6 +29,8 @@ import scala.concurrent.{ ExecutionContext, ExecutionContextExecutor, Future, Pr import scala.util.control.{ ControlThrowable, NonFatal } import scala.util.{ Failure, Success, Try } +import akka.event.Logging.DefaultLogger + object BootstrapSetup { /** @@ -323,6 +324,55 @@ object ActorSystem { defaultExecutionContext: Option[ExecutionContext] = None): ActorSystem = apply(name, ActorSystemSetup(BootstrapSetup(classLoader, config, defaultExecutionContext))) + /** + * INTERNAL API + */ + @InternalApi private[akka] object Settings { + + /** + * INTERNAL API + * + * When using Akka Typed the Slf4jLogger should be used by default. + * Looking for config property `akka.use-slf4j` (defined in akka-actor-typed) and + * that `Slf4jLogger` (akka-slf4j) is in classpath. + * Then adds `Slf4jLogger` to configured loggers and removes `DefaultLogger`. + */ + @InternalApi private[akka] def amendSlf4jConfig(config: Config, dynamicAccess: DynamicAccess): Config = { + val slf4jLoggerClassName = "akka.event.slf4j.Slf4jLogger" + val slf4jLoggingFilterClassName = "akka.event.slf4j.Slf4jLoggingFilter" + val loggersConfKey = "akka.loggers" + val loggingFilterConfKey = "akka.logging-filter" + val configuredLoggers = immutableSeq(config.getStringList(loggersConfKey)) + val configuredLoggingFilter = config.getString(loggingFilterConfKey) + + val loggingFilterAlreadyConfigured = + configuredLoggingFilter == slf4jLoggingFilterClassName || configuredLoggingFilter != classOf[ + DefaultLoggingFilter].getName + + def newLoggingFilterConfStr = s"""$loggingFilterConfKey = "$slf4jLoggingFilterClassName"""" + + if (configuredLoggers.contains(slf4jLoggerClassName)) { + // already configured explicitly + if (loggingFilterAlreadyConfigured) + config + else + ConfigFactory.parseString(newLoggingFilterConfStr).withFallback(config) + } else { + val confKey = "akka.use-slf4j" + if (config.hasPath(confKey) && config.getBoolean(confKey) && dynamicAccess.classIsOnClasspath( + slf4jLoggerClassName)) { + val newLoggers = slf4jLoggerClassName +: configuredLoggers.filterNot(_ == classOf[DefaultLogger].getName) + val newLoggersConfStr = s"$loggersConfKey = [${newLoggers.mkString("\"", "\", \"", "\"")}]" + val newConfStr = + if (loggingFilterAlreadyConfigured) newLoggersConfStr + else newLoggersConfStr + "\n" + newLoggingFilterConfStr + ConfigFactory.parseString(newConfStr).withFallback(config) + } else + config + } + } + } + /** * Settings are the overall ActorSystem Settings which also provides a convenient access to the Config object. * @@ -340,14 +390,12 @@ object ActorSystem { * @see The Typesafe Config Library API Documentation */ final val config: Config = { - val config = cfg.withFallback(ConfigFactory.defaultReference(classLoader)) - - config.checkValid( + cfg.checkValid( ConfigFactory .defaultReference(classLoader) .withoutPath(Dispatchers.InternalDispatcherId), // allow this to be both string and config object "akka") - config + cfg } import akka.util.Helpers.ConfigOps @@ -758,7 +806,15 @@ private[akka] class ActorSystemImpl( import ActorSystem._ @volatile private var logDeadLetterListener: Option[ActorRef] = None - final val settings: Settings = new Settings(classLoader, applicationConfig, name, setup) + + private val _dynamicAccess: DynamicAccess = createDynamicAccess() + + final val settings: Settings = { + val config = Settings.amendSlf4jConfig( + applicationConfig.withFallback(ConfigFactory.defaultReference(classLoader)), + _dynamicAccess) + new Settings(classLoader, config, name, setup) + } protected def uncaughtExceptionHandler: Thread.UncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() { @@ -821,8 +877,7 @@ private[akka] class ActorSystemImpl( */ protected def createDynamicAccess(): DynamicAccess = new ReflectiveDynamicAccess(classLoader) - private val _pm: DynamicAccess = createDynamicAccess() - def dynamicAccess: DynamicAccess = _pm + def dynamicAccess: DynamicAccess = _dynamicAccess def logConfiguration(): Unit = log.info(settings.toString) diff --git a/akka-bench-jmh-typed/src/main/resources/logback-test.xml b/akka-bench-jmh-typed/src/main/resources/logback-test.xml new file mode 100644 index 0000000000..10814d5a0c --- /dev/null +++ b/akka-bench-jmh-typed/src/main/resources/logback-test.xml @@ -0,0 +1,18 @@ + + + + + + + + DEBUG + DENY + + + %date{ISO8601} %-5level %logger %X{akkaSource} %X{sourceThread} - %msg%n + + + + + + diff --git a/akka-cluster-sharding-typed/src/multi-jvm/resources/logback-test.xml b/akka-cluster-sharding-typed/src/multi-jvm/resources/logback-test.xml new file mode 100644 index 0000000000..b8aaf237b2 --- /dev/null +++ b/akka-cluster-sharding-typed/src/multi-jvm/resources/logback-test.xml @@ -0,0 +1,15 @@ + + + + + + + + %date{ISO8601} %-5level %logger %X{akkaSource} %X{sourceThread} - %msg%n + + + + + + + diff --git a/akka-cluster-sharding-typed/src/test/java/akka/cluster/sharding/typed/javadsl/ClusterShardingPersistenceTest.java b/akka-cluster-sharding-typed/src/test/java/akka/cluster/sharding/typed/javadsl/ClusterShardingPersistenceTest.java index 6af13e7d8f..8c1f461278 100644 --- a/akka-cluster-sharding-typed/src/test/java/akka/cluster/sharding/typed/javadsl/ClusterShardingPersistenceTest.java +++ b/akka-cluster-sharding-typed/src/test/java/akka/cluster/sharding/typed/javadsl/ClusterShardingPersistenceTest.java @@ -5,6 +5,7 @@ package akka.cluster.sharding.typed.javadsl; import akka.Done; +import akka.actor.testkit.typed.javadsl.LogCapturing; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import akka.actor.testkit.typed.javadsl.TestProbe; import akka.actor.typed.ActorRef; @@ -18,6 +19,7 @@ import akka.util.Timeout; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.scalatest.junit.JUnitSuite; @@ -36,6 +38,8 @@ public class ClusterShardingPersistenceTest extends JUnitSuite { @ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource(config); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + interface Command {} static class Add implements Command { diff --git a/akka-cluster-sharding-typed/src/test/java/akka/cluster/sharding/typed/javadsl/ShardingEventSourcedEntityWithEnforcedRepliesCompileOnlyTest.java b/akka-cluster-sharding-typed/src/test/java/akka/cluster/sharding/typed/javadsl/ShardingEventSourcedEntityWithEnforcedRepliesCompileOnlyTest.java index bf30d8ba4e..c36c031dc6 100644 --- a/akka-cluster-sharding-typed/src/test/java/akka/cluster/sharding/typed/javadsl/ShardingEventSourcedEntityWithEnforcedRepliesCompileOnlyTest.java +++ b/akka-cluster-sharding-typed/src/test/java/akka/cluster/sharding/typed/javadsl/ShardingEventSourcedEntityWithEnforcedRepliesCompileOnlyTest.java @@ -4,6 +4,7 @@ package akka.cluster.sharding.typed.javadsl; +import akka.actor.testkit.typed.javadsl.LogCapturing; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import akka.actor.typed.ActorRef; import akka.cluster.typed.Cluster; @@ -11,11 +12,14 @@ import akka.cluster.typed.Join; import akka.persistence.typed.ExpectingReply; import akka.persistence.typed.javadsl.*; import org.junit.ClassRule; +import org.junit.Rule; public class ShardingEventSourcedEntityWithEnforcedRepliesCompileOnlyTest { @ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource(); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + interface Command extends ExpectingReply {} static class Append implements Command { diff --git a/akka-cluster-sharding-typed/src/test/java/jdocs/akka/cluster/sharding/typed/AccountExampleTest.java b/akka-cluster-sharding-typed/src/test/java/jdocs/akka/cluster/sharding/typed/AccountExampleTest.java index b9694e69f5..8522fb1211 100644 --- a/akka-cluster-sharding-typed/src/test/java/jdocs/akka/cluster/sharding/typed/AccountExampleTest.java +++ b/akka-cluster-sharding-typed/src/test/java/jdocs/akka/cluster/sharding/typed/AccountExampleTest.java @@ -4,6 +4,7 @@ package jdocs.akka.cluster.sharding.typed; +import akka.actor.testkit.typed.javadsl.LogCapturing; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import akka.actor.testkit.typed.javadsl.TestProbe; import akka.actor.typed.ActorRef; @@ -15,6 +16,7 @@ import akka.cluster.typed.Join; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.scalatest.junit.JUnitSuite; @@ -40,6 +42,8 @@ public class AccountExampleTest extends JUnitSuite { @ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource(config); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + private ClusterSharding _sharding = null; private ClusterSharding sharding() { diff --git a/akka-cluster-sharding-typed/src/test/java/jdocs/akka/cluster/sharding/typed/HelloWorldEventSourcedEntityExampleTest.java b/akka-cluster-sharding-typed/src/test/java/jdocs/akka/cluster/sharding/typed/HelloWorldEventSourcedEntityExampleTest.java index c2023d0b43..69b0cf9ee7 100644 --- a/akka-cluster-sharding-typed/src/test/java/jdocs/akka/cluster/sharding/typed/HelloWorldEventSourcedEntityExampleTest.java +++ b/akka-cluster-sharding-typed/src/test/java/jdocs/akka/cluster/sharding/typed/HelloWorldEventSourcedEntityExampleTest.java @@ -4,6 +4,7 @@ package jdocs.akka.cluster.sharding.typed; +import akka.actor.testkit.typed.javadsl.LogCapturing; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import akka.actor.testkit.typed.javadsl.TestProbe; import akka.cluster.sharding.typed.javadsl.ClusterSharding; @@ -14,6 +15,7 @@ import akka.cluster.typed.Join; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.scalatest.junit.JUnitSuite; @@ -33,6 +35,8 @@ public class HelloWorldEventSourcedEntityExampleTest extends JUnitSuite { @ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource(config); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + private ClusterSharding _sharding = null; private ClusterSharding sharding() { diff --git a/akka-cluster-sharding-typed/src/test/resources/logback-test.xml b/akka-cluster-sharding-typed/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..5e80aa8ee0 --- /dev/null +++ b/akka-cluster-sharding-typed/src/test/resources/logback-test.xml @@ -0,0 +1,36 @@ + + + + + + + + DEBUG + DENY + + + %date{ISO8601} %-5level %logger %X{akkaSource} %X{sourceThread} { "persistenceId": "%X{persistenceId}", "persistencePhase": "%X{persistencePhase}" } - %msg%n + + + + + + + + + + + + + + + + diff --git a/akka-cluster-sharding-typed/src/test/scala/akka/cluster/sharding/typed/ShardingSerializerSpec.scala b/akka-cluster-sharding-typed/src/test/scala/akka/cluster/sharding/typed/ShardingSerializerSpec.scala index 7c530a0506..24ca6c2ef6 100644 --- a/akka-cluster-sharding-typed/src/test/scala/akka/cluster/sharding/typed/ShardingSerializerSpec.scala +++ b/akka-cluster-sharding-typed/src/test/scala/akka/cluster/sharding/typed/ShardingSerializerSpec.scala @@ -8,9 +8,10 @@ import akka.actor.typed.internal.adapter.ActorSystemAdapter import akka.cluster.sharding.typed.internal.ShardingSerializer import akka.serialization.SerializationExtension import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import org.scalatest.WordSpecLike -class ShardingSerializerSpec extends ScalaTestWithActorTestKit with WordSpecLike { +class ShardingSerializerSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { "The typed ShardingSerializer" must { diff --git a/akka-cluster-sharding-typed/src/test/scala/akka/cluster/sharding/typed/scaladsl/ClusterShardingPersistenceSpec.scala b/akka-cluster-sharding-typed/src/test/scala/akka/cluster/sharding/typed/scaladsl/ClusterShardingPersistenceSpec.scala index a42eb8a10a..cb3172d731 100644 --- a/akka-cluster-sharding-typed/src/test/scala/akka/cluster/sharding/typed/scaladsl/ClusterShardingPersistenceSpec.scala +++ b/akka-cluster-sharding-typed/src/test/scala/akka/cluster/sharding/typed/scaladsl/ClusterShardingPersistenceSpec.scala @@ -12,9 +12,11 @@ import scala.concurrent.ExecutionContext import scala.concurrent.Future import scala.concurrent.Promise import scala.concurrent.duration._ + import akka.Done import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.testkit.typed.scaladsl.TestProbe +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.ActorRef import akka.actor.typed.Behavior import akka.actor.typed.PostStop @@ -147,7 +149,8 @@ object ClusterShardingPersistenceSpec { class ClusterShardingPersistenceSpec extends ScalaTestWithActorTestKit(ClusterShardingPersistenceSpec.config) - with WordSpecLike { + with WordSpecLike + with LogCapturing { import ClusterShardingPersistenceSpec._ private var _entityId = 0 diff --git a/akka-cluster-sharding-typed/src/test/scala/akka/cluster/sharding/typed/scaladsl/ClusterShardingSpec.scala b/akka-cluster-sharding-typed/src/test/scala/akka/cluster/sharding/typed/scaladsl/ClusterShardingSpec.scala index ecef47b48f..75ed9997ca 100644 --- a/akka-cluster-sharding-typed/src/test/scala/akka/cluster/sharding/typed/scaladsl/ClusterShardingSpec.scala +++ b/akka-cluster-sharding-typed/src/test/scala/akka/cluster/sharding/typed/scaladsl/ClusterShardingSpec.scala @@ -9,10 +9,12 @@ import java.nio.charset.StandardCharsets import scala.concurrent.duration._ import scala.util.Failure import scala.util.Success + import akka.actor.ExtendedActorSystem import akka.actor.testkit.typed.scaladsl.ActorTestKit import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.testkit.typed.scaladsl.TestProbe +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.ActorRef import akka.actor.typed.ActorRefResolver import akka.actor.typed.ActorSystem @@ -179,7 +181,10 @@ object ClusterShardingSpec { } } -class ClusterShardingSpec extends ScalaTestWithActorTestKit(ClusterShardingSpec.config) with WordSpecLike { +class ClusterShardingSpec + extends ScalaTestWithActorTestKit(ClusterShardingSpec.config) + with WordSpecLike + with LogCapturing { import ClusterShardingSpec._ diff --git a/akka-cluster-sharding-typed/src/test/scala/akka/cluster/sharding/typed/scaladsl/ClusterShardingStateSpec.scala b/akka-cluster-sharding-typed/src/test/scala/akka/cluster/sharding/typed/scaladsl/ClusterShardingStateSpec.scala index a855640f10..064916031d 100644 --- a/akka-cluster-sharding-typed/src/test/scala/akka/cluster/sharding/typed/scaladsl/ClusterShardingStateSpec.scala +++ b/akka-cluster-sharding-typed/src/test/scala/akka/cluster/sharding/typed/scaladsl/ClusterShardingStateSpec.scala @@ -4,6 +4,7 @@ package akka.cluster.sharding.typed.scaladsl +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.testkit.typed.scaladsl.{ ScalaTestWithActorTestKit, TestProbe } import akka.actor.typed.ActorRef import akka.cluster.sharding.ShardRegion.{ CurrentShardRegionState, ShardState } @@ -12,7 +13,10 @@ import akka.cluster.sharding.typed.{ GetShardRegionState, ShardingMessageExtract import akka.cluster.typed.{ Cluster, Join } import org.scalatest.WordSpecLike -class ClusterShardingStateSpec extends ScalaTestWithActorTestKit(ClusterShardingSpec.config) with WordSpecLike { +class ClusterShardingStateSpec + extends ScalaTestWithActorTestKit(ClusterShardingSpec.config) + with WordSpecLike + with LogCapturing { val sharding = ClusterSharding(system) diff --git a/akka-cluster-sharding-typed/src/test/scala/akka/cluster/sharding/typed/scaladsl/EntityTypeKeySpec.scala b/akka-cluster-sharding-typed/src/test/scala/akka/cluster/sharding/typed/scaladsl/EntityTypeKeySpec.scala index 9bb42ad569..498372565f 100644 --- a/akka-cluster-sharding-typed/src/test/scala/akka/cluster/sharding/typed/scaladsl/EntityTypeKeySpec.scala +++ b/akka-cluster-sharding-typed/src/test/scala/akka/cluster/sharding/typed/scaladsl/EntityTypeKeySpec.scala @@ -4,11 +4,12 @@ package akka.cluster.sharding.typed.scaladsl +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.persistence.typed.PersistenceId import org.scalatest.Matchers import org.scalatest.WordSpec -class EntityTypeKeySpec extends WordSpec with Matchers { +class EntityTypeKeySpec extends WordSpec with Matchers with LogCapturing { "EntityTypeKey" must { "use | as default entityIdSeparator for compatibility with Lagom's scaladsl" in { diff --git a/akka-cluster-sharding-typed/src/test/scala/docs/akka/cluster/sharding/typed/AccountExampleSpec.scala b/akka-cluster-sharding-typed/src/test/scala/docs/akka/cluster/sharding/typed/AccountExampleSpec.scala index 94da0a0fad..404db0c18e 100644 --- a/akka-cluster-sharding-typed/src/test/scala/docs/akka/cluster/sharding/typed/AccountExampleSpec.scala +++ b/akka-cluster-sharding-typed/src/test/scala/docs/akka/cluster/sharding/typed/AccountExampleSpec.scala @@ -8,6 +8,7 @@ import scala.concurrent.ExecutionContext import scala.concurrent.Future import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.cluster.sharding.typed.scaladsl.ClusterSharding import akka.cluster.sharding.typed.scaladsl.Entity import akka.cluster.typed.Cluster @@ -29,7 +30,10 @@ object AccountExampleSpec { } -class AccountExampleSpec extends ScalaTestWithActorTestKit(AccountExampleSpec.config) with WordSpecLike { +class AccountExampleSpec + extends ScalaTestWithActorTestKit(AccountExampleSpec.config) + with WordSpecLike + with LogCapturing { import AccountExampleWithEventHandlersInState.AccountEntity import AccountExampleWithEventHandlersInState.AccountEntity._ diff --git a/akka-cluster-sharding-typed/src/test/scala/docs/akka/cluster/sharding/typed/HelloWorldEventSourcedEntityExampleSpec.scala b/akka-cluster-sharding-typed/src/test/scala/docs/akka/cluster/sharding/typed/HelloWorldEventSourcedEntityExampleSpec.scala index bac1eadbd3..184075a3c8 100644 --- a/akka-cluster-sharding-typed/src/test/scala/docs/akka/cluster/sharding/typed/HelloWorldEventSourcedEntityExampleSpec.scala +++ b/akka-cluster-sharding-typed/src/test/scala/docs/akka/cluster/sharding/typed/HelloWorldEventSourcedEntityExampleSpec.scala @@ -5,6 +5,7 @@ package docs.akka.cluster.sharding.typed import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.cluster.sharding.typed.scaladsl.ClusterSharding import akka.cluster.sharding.typed.scaladsl.Entity import akka.cluster.typed.Cluster @@ -27,7 +28,8 @@ object HelloWorldEventSourcedEntityExampleSpec { class HelloWorldEventSourcedEntityExampleSpec extends ScalaTestWithActorTestKit(HelloWorldEventSourcedEntityExampleSpec.config) - with WordSpecLike { + with WordSpecLike + with LogCapturing { import HelloWorldPersistentEntityExample.HelloWorld import HelloWorldPersistentEntityExample.HelloWorld._ diff --git a/akka-cluster-typed/src/main/scala/akka/cluster/ddata/typed/scaladsl/DistributedData.scala b/akka-cluster-typed/src/main/scala/akka/cluster/ddata/typed/scaladsl/DistributedData.scala index 838b965e1e..65f023506e 100644 --- a/akka-cluster-typed/src/main/scala/akka/cluster/ddata/typed/scaladsl/DistributedData.scala +++ b/akka-cluster-typed/src/main/scala/akka/cluster/ddata/typed/scaladsl/DistributedData.scala @@ -10,12 +10,14 @@ import akka.actor.typed.{ ActorRef, ActorSystem, Extension, ExtensionId, Props } import akka.actor.ExtendedActorSystem import akka.actor.typed.Behavior import akka.actor.typed.scaladsl.Behaviors +import akka.actor.typed.scaladsl.LoggerOps import akka.annotation.InternalApi import akka.cluster.Cluster import akka.cluster.ddata.ReplicatedData import akka.cluster.{ ddata => dd } import akka.cluster.ddata.SelfUniqueAddress import akka.util.JavaDurationConverters._ +import org.slf4j.LoggerFactory object DistributedData extends ExtensionId[DistributedData] { def get(system: ActorSystem[_]): DistributedData = apply(system) @@ -85,13 +87,13 @@ class DistributedData(system: ActorSystem[_]) extends Extension { */ val replicator: ActorRef[Replicator.Command] = if (isTerminated) { - val log = system.log.withLoggerClass(getClass) + val log = LoggerFactory.getLogger(getClass) if (Cluster(classicSystem).isTerminated) - log.warning("Replicator points to dead letters, because Cluster is terminated.") + log.warn("Replicator points to dead letters, because Cluster is terminated.") else - log.warning( + log.warn2( "Replicator points to dead letters. Make sure the cluster node has the proper role. " + - "Node has roles [], Distributed Data is configured for roles []", + "Node has roles [{}], Distributed Data is configured for roles [{}].", Cluster(classicSystem).selfRoles.mkString(","), settings.roles.mkString(",")) system.deadLetters diff --git a/akka-cluster-typed/src/main/scala/akka/cluster/typed/internal/receptionist/ClusterReceptionist.scala b/akka-cluster-typed/src/main/scala/akka/cluster/typed/internal/receptionist/ClusterReceptionist.scala index 45bcb391d8..697478ad4e 100644 --- a/akka-cluster-typed/src/main/scala/akka/cluster/typed/internal/receptionist/ClusterReceptionist.scala +++ b/akka-cluster-typed/src/main/scala/akka/cluster/typed/internal/receptionist/ClusterReceptionist.scala @@ -8,7 +8,7 @@ import akka.actor.typed.internal.receptionist.{ AbstractServiceKey, Receptionist import akka.actor.typed.receptionist.Receptionist.Command import akka.actor.typed.receptionist.ServiceKey import akka.actor.typed.scaladsl.adapter._ -import akka.actor.typed.scaladsl.{ ActorContext, Behaviors } +import akka.actor.typed.scaladsl.{ ActorContext, Behaviors, LoggerOps } import akka.actor.typed.{ ActorRef, Behavior, Terminated } import akka.annotation.InternalApi import akka.cluster.ClusterEvent.MemberRemoved @@ -92,7 +92,7 @@ private[typed] object ClusterReceptionist extends ReceptionistBehaviorProvider { override def behavior: Behavior[Command] = Behaviors.setup { ctx => - ctx.setLoggerClass(classOf[ClusterReceptionist]) + ctx.setLoggerName(classOf[ClusterReceptionist]) Behaviors.withTimers { timers => val setup = new Setup(ctx) // include selfUniqueAddress so that it can be used locally before joining cluster @@ -194,7 +194,7 @@ private[typed] object ClusterReceptionist extends ReceptionistBehaviorProvider { if (removals.nonEmpty) { if (ctx.log.isDebugEnabled) - ctx.log.debug( + ctx.log.debugN( "ClusterReceptionist [{}] - Node(s) removed [{}], updating registry removing entries: [{}]", cluster.selfAddress, addresses.mkString(","), @@ -237,7 +237,8 @@ private[typed] object ClusterReceptionist extends ReceptionistBehaviorProvider { case ReceptionistMessages.Register(key, serviceInstance, maybeReplyTo) => if (serviceInstance.path.address.hasLocalScope) { val entry = Entry(serviceInstance, setup.selfSystemUid) - ctx.log.debug("ClusterReceptionist [{}] - Actor was registered: [{}] [{}]", cluster.selfAddress, key, entry) + ctx.log + .debugN("ClusterReceptionist [{}] - Actor was registered: [{}] [{}]", cluster.selfAddress, key, entry) watchWith(ctx, serviceInstance, RegisteredActorTerminated(key, serviceInstance)) maybeReplyTo match { case Some(replyTo) => replyTo ! ReceptionistMessages.Registered(key, serviceInstance) @@ -248,7 +249,7 @@ private[typed] object ClusterReceptionist extends ReceptionistBehaviorProvider { ServiceRegistry(registry).addBinding(key, entry).toORMultiMap } } else { - ctx.log.error(s"ClusterReceptionist [{}] - Register of non-local [{}] is not supported", serviceInstance) + ctx.log.error("ClusterReceptionist [{}] - Register of non-local [{}] is not supported", serviceInstance) } Behaviors.same @@ -277,7 +278,7 @@ private[typed] object ClusterReceptionist extends ReceptionistBehaviorProvider { case RegisteredActorTerminated(key, serviceInstance) => val entry = Entry(serviceInstance, setup.selfSystemUid) - ctx.log.debug( + ctx.log.debugN( "ClusterReceptionist [{}] - Registered actor terminated: [{}] [{}]", cluster.selfAddress, key.asServiceKey.id, @@ -297,7 +298,7 @@ private[typed] object ClusterReceptionist extends ReceptionistBehaviorProvider { val newRegistry = registry.withServiceRegistry(ddataKey, newState) if (changedKeys.nonEmpty) { if (ctx.log.isDebugEnabled) { - ctx.log.debug( + ctx.log.debugN( "ClusterReceptionist [{}] - Change from replicator: [{}], changes: [{}], tombstones [{}]", cluster.selfAddress, newState.entries.entries, @@ -323,7 +324,7 @@ private[typed] object ClusterReceptionist extends ReceptionistBehaviorProvider { val tombstonedButReAdded = newRegistry.actorRefsFor(serviceKey).filter(newRegistry.hasTombstone) if (tombstonedButReAdded.nonEmpty) { if (ctx.log.isDebugEnabled) - ctx.log.debug( + ctx.log.debug2( "ClusterReceptionist [{}] - Saw ActorRefs that were tomstoned [{}], re-removing.", cluster.selfAddress, tombstonedButReAdded.mkString(", ")) @@ -355,7 +356,7 @@ private[typed] object ClusterReceptionist extends ReceptionistBehaviorProvider { } else { // Ok to update from several nodes but more efficient to try to do it from one node. if (isLeader) { - ctx.log.debug( + ctx.log.debug2( "ClusterReceptionist [{}] - Leader node observed removed node [{}]", cluster.selfAddress, uniqueAddress) @@ -369,7 +370,7 @@ private[typed] object ClusterReceptionist extends ReceptionistBehaviorProvider { val keysForNode = registry.keysFor(uniqueAddress) val newRegistry = registry.addUnreachable(uniqueAddress) if (keysForNode.nonEmpty) { - ctx.log.debug( + ctx.log.debug2( "ClusterReceptionist [{}] - Node with registered services unreachable [{}]", cluster.selfAddress, uniqueAddress) @@ -381,7 +382,7 @@ private[typed] object ClusterReceptionist extends ReceptionistBehaviorProvider { val keysForNode = registry.keysFor(uniqueAddress) val newRegistry = registry.removeUnreachable(uniqueAddress) if (keysForNode.nonEmpty) { - ctx.log.debug( + ctx.log.debug2( "ClusterReceptionist [{}] - Node with registered services reachable again [{}]", cluster.selfAddress, uniqueAddress) @@ -398,7 +399,7 @@ private[typed] object ClusterReceptionist extends ReceptionistBehaviorProvider { if (notInCluster.isEmpty) Behaviors.same else { if (ctx.log.isDebugEnabled) - ctx.log.debug( + ctx.log.debug2( "ClusterReceptionist [{}] - Leader node cleanup tick, removed nodes: [{}]", cluster.selfAddress, notInCluster.mkString(",")) diff --git a/akka-cluster-typed/src/multi-jvm/resources/logback-test.xml b/akka-cluster-typed/src/multi-jvm/resources/logback-test.xml new file mode 100644 index 0000000000..b8aaf237b2 --- /dev/null +++ b/akka-cluster-typed/src/multi-jvm/resources/logback-test.xml @@ -0,0 +1,15 @@ + + + + + + + + %date{ISO8601} %-5level %logger %X{akkaSource} %X{sourceThread} - %msg%n + + + + + + + diff --git a/akka-cluster-typed/src/test/java/akka/cluster/ddata/typed/javadsl/ReplicatorTest.java b/akka-cluster-typed/src/test/java/akka/cluster/ddata/typed/javadsl/ReplicatorTest.java index d349d12662..344ddcd62c 100644 --- a/akka-cluster-typed/src/test/java/akka/cluster/ddata/typed/javadsl/ReplicatorTest.java +++ b/akka-cluster-typed/src/test/java/akka/cluster/ddata/typed/javadsl/ReplicatorTest.java @@ -6,12 +6,14 @@ package akka.cluster.ddata.typed.javadsl; // FIXME move to doc package +import akka.actor.testkit.typed.javadsl.LogCapturing; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import akka.actor.testkit.typed.javadsl.TestProbe; import akka.cluster.ddata.*; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.scalatest.junit.JUnitSuite; @@ -179,6 +181,8 @@ public class ReplicatorTest extends JUnitSuite { @ClassRule public static TestKitJunitResource testKit = new TestKitJunitResource(config); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + @Test public void shouldHaveApiForUpdateAndGet() { TestProbe probe = testKit.createTestProbe(Integer.class); diff --git a/akka-cluster-typed/src/test/resources/logback-test.xml b/akka-cluster-typed/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..f7f657efd7 --- /dev/null +++ b/akka-cluster-typed/src/test/resources/logback-test.xml @@ -0,0 +1,36 @@ + + + + + + + + DEBUG + DENY + + + %date{ISO8601} %-5level %logger %X{akkaSource} %X{sourceThread} - %msg%n + + + + + + + + + + + + + + + + diff --git a/akka-cluster-typed/src/test/scala/akka/cluster/ddata/typed/scaladsl/ReplicatorSpec.scala b/akka-cluster-typed/src/test/scala/akka/cluster/ddata/typed/scaladsl/ReplicatorSpec.scala index 4361d57eff..c0ac375302 100644 --- a/akka-cluster-typed/src/test/scala/akka/cluster/ddata/typed/scaladsl/ReplicatorSpec.scala +++ b/akka-cluster-typed/src/test/scala/akka/cluster/ddata/typed/scaladsl/ReplicatorSpec.scala @@ -185,7 +185,7 @@ object ReplicatorSpec { } -class ReplicatorSpec extends ScalaTestWithActorTestKit(ReplicatorSpec.config) with WordSpecLike { +class ReplicatorSpec extends ScalaTestWithActorTestKit(ReplicatorSpec.config) with WordSpecLike with LogCapturing { import ReplicatorSpec._ diff --git a/akka-cluster-typed/src/test/scala/akka/cluster/typed/ActorSystemSpec.scala b/akka-cluster-typed/src/test/scala/akka/cluster/typed/ActorSystemSpec.scala index 3b7057a569..719ff5c127 100644 --- a/akka-cluster-typed/src/test/scala/akka/cluster/typed/ActorSystemSpec.scala +++ b/akka-cluster-typed/src/test/scala/akka/cluster/typed/ActorSystemSpec.scala @@ -13,6 +13,7 @@ import akka.Done import akka.actor.CoordinatedShutdown import akka.actor.InvalidMessageException import akka.actor.testkit.typed.scaladsl.TestInbox +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.ActorRef import akka.actor.typed.ActorSystem import akka.actor.typed.Behavior @@ -25,7 +26,13 @@ import org.scalatest.concurrent.Eventually import org.scalatest.concurrent.ScalaFutures import org.scalatest.time.Span -class ActorSystemSpec extends WordSpec with Matchers with BeforeAndAfterAll with ScalaFutures with Eventually { +class ActorSystemSpec + extends WordSpec + with Matchers + with BeforeAndAfterAll + with ScalaFutures + with Eventually + with LogCapturing { implicit val patience: PatienceConfig = PatienceConfig(3.seconds, Span(100, org.scalatest.time.Millis)) diff --git a/akka-cluster-typed/src/test/scala/akka/cluster/typed/ClusterApiSpec.scala b/akka-cluster-typed/src/test/scala/akka/cluster/typed/ClusterApiSpec.scala index 79626da331..525798fc9f 100644 --- a/akka-cluster-typed/src/test/scala/akka/cluster/typed/ClusterApiSpec.scala +++ b/akka-cluster-typed/src/test/scala/akka/cluster/typed/ClusterApiSpec.scala @@ -13,6 +13,7 @@ import akka.actor.testkit.typed.TestKitSettings import akka.actor.testkit.typed.scaladsl.ActorTestKit import com.typesafe.config.ConfigFactory import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import org.scalatest.WordSpecLike object ClusterApiSpec { @@ -35,7 +36,7 @@ object ClusterApiSpec { """) } -class ClusterApiSpec extends ScalaTestWithActorTestKit(ClusterApiSpec.config) with WordSpecLike { +class ClusterApiSpec extends ScalaTestWithActorTestKit(ClusterApiSpec.config) with WordSpecLike with LogCapturing { val testSettings = TestKitSettings(system) val clusterNode1 = Cluster(system) diff --git a/akka-cluster-typed/src/test/scala/akka/cluster/typed/ClusterSingletonApiSpec.scala b/akka-cluster-typed/src/test/scala/akka/cluster/typed/ClusterSingletonApiSpec.scala index 6412b47ff9..364436ee46 100644 --- a/akka-cluster-typed/src/test/scala/akka/cluster/typed/ClusterSingletonApiSpec.scala +++ b/akka-cluster-typed/src/test/scala/akka/cluster/typed/ClusterSingletonApiSpec.scala @@ -14,10 +14,11 @@ import akka.actor.testkit.typed.scaladsl.TestProbe import akka.actor.typed.{ ActorRef, ActorRefResolver } import akka.serialization.SerializerWithStringManifest import com.typesafe.config.ConfigFactory - import scala.concurrent.Await import scala.concurrent.duration._ + import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import org.scalatest.WordSpecLike object ClusterSingletonApiSpec { @@ -85,7 +86,10 @@ object ClusterSingletonApiSpec { } } -class ClusterSingletonApiSpec extends ScalaTestWithActorTestKit(ClusterSingletonApiSpec.config) with WordSpecLike { +class ClusterSingletonApiSpec + extends ScalaTestWithActorTestKit(ClusterSingletonApiSpec.config) + with WordSpecLike + with LogCapturing { import ClusterSingletonApiSpec._ implicit val testSettings = TestKitSettings(system) diff --git a/akka-cluster-typed/src/test/scala/akka/cluster/typed/ClusterSingletonPersistenceSpec.scala b/akka-cluster-typed/src/test/scala/akka/cluster/typed/ClusterSingletonPersistenceSpec.scala index ae0ed90db9..dd32e7e29b 100644 --- a/akka-cluster-typed/src/test/scala/akka/cluster/typed/ClusterSingletonPersistenceSpec.scala +++ b/akka-cluster-typed/src/test/scala/akka/cluster/typed/ClusterSingletonPersistenceSpec.scala @@ -8,6 +8,7 @@ import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.typed.{ ActorRef, Behavior } import akka.persistence.typed.scaladsl.{ Effect, EventSourcedBehavior } import akka.actor.testkit.typed.scaladsl.TestProbe +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.persistence.typed.PersistenceId import com.typesafe.config.ConfigFactory import org.scalatest.WordSpecLike @@ -52,7 +53,8 @@ object ClusterSingletonPersistenceSpec { class ClusterSingletonPersistenceSpec extends ScalaTestWithActorTestKit(ClusterSingletonPersistenceSpec.config) - with WordSpecLike { + with WordSpecLike + with LogCapturing { import ClusterSingletonPersistenceSpec._ import akka.actor.typed.scaladsl.adapter._ diff --git a/akka-cluster-typed/src/test/scala/akka/cluster/typed/ClusterSingletonPoisonPillSpec.scala b/akka-cluster-typed/src/test/scala/akka/cluster/typed/ClusterSingletonPoisonPillSpec.scala index 80c9051490..dba81b23ab 100644 --- a/akka-cluster-typed/src/test/scala/akka/cluster/typed/ClusterSingletonPoisonPillSpec.scala +++ b/akka-cluster-typed/src/test/scala/akka/cluster/typed/ClusterSingletonPoisonPillSpec.scala @@ -12,9 +12,10 @@ import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.adapter._ import akka.cluster.typed.ClusterSingletonPoisonPillSpec.GetSelf import org.scalatest.WordSpecLike - import scala.concurrent.duration._ +import akka.actor.testkit.typed.scaladsl.LogCapturing + object ClusterSingletonPoisonPillSpec { final case class GetSelf(replyTo: ActorRef[ActorRef[Any]]) @@ -27,7 +28,8 @@ object ClusterSingletonPoisonPillSpec { class ClusterSingletonPoisonPillSpec extends ScalaTestWithActorTestKit(ClusterSingletonApiSpec.config) - with WordSpecLike { + with WordSpecLike + with LogCapturing { implicit val testSettings = TestKitSettings(system) val clusterNode1 = Cluster(system) diff --git a/akka-cluster-typed/src/test/scala/akka/cluster/typed/RemoteContextAskSpec.scala b/akka-cluster-typed/src/test/scala/akka/cluster/typed/RemoteContextAskSpec.scala index 21cebbe67f..4f89475661 100644 --- a/akka-cluster-typed/src/test/scala/akka/cluster/typed/RemoteContextAskSpec.scala +++ b/akka-cluster-typed/src/test/scala/akka/cluster/typed/RemoteContextAskSpec.scala @@ -16,10 +16,11 @@ import akka.actor.testkit.typed.scaladsl.TestProbe import akka.actor.typed.scaladsl.adapter._ import akka.util.Timeout import com.typesafe.config.ConfigFactory - import scala.concurrent.duration._ import scala.util.{ Failure, Success } + import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import org.scalatest.WordSpecLike class RemoteContextAskSpecSerializer(system: ExtendedActorSystem) extends SerializerWithStringManifest { @@ -83,7 +84,10 @@ object RemoteContextAskSpec { } -class RemoteContextAskSpec extends ScalaTestWithActorTestKit(RemoteContextAskSpec.config) with WordSpecLike { +class RemoteContextAskSpec + extends ScalaTestWithActorTestKit(RemoteContextAskSpec.config) + with WordSpecLike + with LogCapturing { import RemoteContextAskSpec._ diff --git a/akka-cluster-typed/src/test/scala/akka/cluster/typed/RemoteDeployNotAllowedSpec.scala b/akka-cluster-typed/src/test/scala/akka/cluster/typed/RemoteDeployNotAllowedSpec.scala index df5f7bfaea..004fff9d32 100644 --- a/akka-cluster-typed/src/test/scala/akka/cluster/typed/RemoteDeployNotAllowedSpec.scala +++ b/akka-cluster-typed/src/test/scala/akka/cluster/typed/RemoteDeployNotAllowedSpec.scala @@ -8,10 +8,11 @@ import akka.actor.typed.ActorSystem import akka.actor.typed.scaladsl.Behaviors import akka.actor.testkit.typed.scaladsl.TestProbe import com.typesafe.config.ConfigFactory - import scala.concurrent.duration._ + import akka.actor.testkit.typed.scaladsl.ActorTestKit import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import org.scalatest.WordSpecLike object RemoteDeployNotAllowedSpec { @@ -44,7 +45,8 @@ object RemoteDeployNotAllowedSpec { class RemoteDeployNotAllowedSpec extends ScalaTestWithActorTestKit(RemoteDeployNotAllowedSpec.config) - with WordSpecLike { + with WordSpecLike + with LogCapturing { "Typed cluster" must { diff --git a/akka-cluster-typed/src/test/scala/akka/cluster/typed/internal/AkkaClusterTypedSerializerSpec.scala b/akka-cluster-typed/src/test/scala/akka/cluster/typed/internal/AkkaClusterTypedSerializerSpec.scala index 8a29ed6417..5f9209dfda 100644 --- a/akka-cluster-typed/src/test/scala/akka/cluster/typed/internal/AkkaClusterTypedSerializerSpec.scala +++ b/akka-cluster-typed/src/test/scala/akka/cluster/typed/internal/AkkaClusterTypedSerializerSpec.scala @@ -9,10 +9,11 @@ import akka.actor.typed.scaladsl.adapter._ import akka.cluster.typed.internal.receptionist.ClusterReceptionist import akka.serialization.SerializationExtension import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.scaladsl.Behaviors import org.scalatest.WordSpecLike -class AkkaClusterTypedSerializerSpec extends ScalaTestWithActorTestKit with WordSpecLike { +class AkkaClusterTypedSerializerSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { val ref = spawn(Behaviors.empty[String]) val classicSystem = system.toClassic diff --git a/akka-cluster-typed/src/test/scala/akka/cluster/typed/internal/receptionist/ClusterReceptionistSpec.scala b/akka-cluster-typed/src/test/scala/akka/cluster/typed/internal/receptionist/ClusterReceptionistSpec.scala index 6758bd6679..299e1a03b1 100644 --- a/akka-cluster-typed/src/test/scala/akka/cluster/typed/internal/receptionist/ClusterReceptionistSpec.scala +++ b/akka-cluster-typed/src/test/scala/akka/cluster/typed/internal/receptionist/ClusterReceptionistSpec.scala @@ -21,6 +21,7 @@ import org.scalatest.{ Matchers, WordSpec } import scala.concurrent.Await import scala.concurrent.duration._ +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.cluster.typed.Down import akka.cluster.typed.JoinSeedNodes import akka.cluster.typed.Leave @@ -99,7 +100,7 @@ object ClusterReceptionistSpec { val PingKey = ServiceKey[PingProtocol]("pingy") } -class ClusterReceptionistSpec extends WordSpec with Matchers { +class ClusterReceptionistSpec extends WordSpec with Matchers with LogCapturing { import ClusterReceptionistSpec._ import Receptionist._ diff --git a/akka-cluster-typed/src/test/scala/docs/akka/cluster/typed/BasicClusterExampleSpec.scala b/akka-cluster-typed/src/test/scala/docs/akka/cluster/typed/BasicClusterExampleSpec.scala index fe6029d1cc..5401de1af9 100644 --- a/akka-cluster-typed/src/test/scala/docs/akka/cluster/typed/BasicClusterExampleSpec.scala +++ b/akka-cluster-typed/src/test/scala/docs/akka/cluster/typed/BasicClusterExampleSpec.scala @@ -4,6 +4,7 @@ package docs.akka.cluster.typed +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.testkit.SocketUtil import com.typesafe.config.ConfigFactory import org.scalatest.{ Matchers, WordSpec } @@ -49,7 +50,7 @@ akka { """).withFallback(configSystem1) } -class BasicClusterConfigSpec extends WordSpec with ScalaFutures with Eventually with Matchers { +class BasicClusterConfigSpec extends WordSpec with ScalaFutures with Eventually with Matchers with LogCapturing { import BasicClusterExampleSpec._ implicit override val patienceConfig = @@ -103,7 +104,7 @@ akka { } -class BasicClusterManualSpec extends WordSpec with ScalaFutures with Eventually with Matchers { +class BasicClusterManualSpec extends WordSpec with ScalaFutures with Eventually with Matchers with LogCapturing { import BasicClusterManualSpec._ diff --git a/akka-distributed-data/src/main/scala/akka/cluster/ddata/DistributedData.scala b/akka-distributed-data/src/main/scala/akka/cluster/ddata/DistributedData.scala index ac381f66b1..d69d79d727 100644 --- a/akka-distributed-data/src/main/scala/akka/cluster/ddata/DistributedData.scala +++ b/akka-distributed-data/src/main/scala/akka/cluster/ddata/DistributedData.scala @@ -44,9 +44,9 @@ class DistributedData(system: ExtendedActorSystem) extends Extension { else log.warning( "Replicator points to dead letters. Make sure the cluster node has the proper role. " + - "Node has roles [], Distributed Data is configured for roles []", + "Node has roles [{}], Distributed Data is configured for roles [{}].", Cluster(system).selfRoles.mkString(","), - settings.roles.mkString(",")) + settings.roles.mkString(","): Any) system.deadLetters } else { system.systemActorOf(Replicator.props(settings), ReplicatorSettings.name(system, None)) diff --git a/akka-docs/src/main/paradox/project/migration-guide-2.5.x-2.6.x.md b/akka-docs/src/main/paradox/project/migration-guide-2.5.x-2.6.x.md index 437a96c52a..9e21ec8694 100644 --- a/akka-docs/src/main/paradox/project/migration-guide-2.5.x-2.6.x.md +++ b/akka-docs/src/main/paradox/project/migration-guide-2.5.x-2.6.x.md @@ -476,12 +476,17 @@ made before finalizing the APIs. Compared to Akka 2.5.x the source incompatible * To align with the Akka Typed style guide `SpawnProtocol` is now created through @scala[`SpawnProtocol()`]@java[`SpawnProtocol.create()`], the special `Spawn` message factories has been removed and the top level of the actor protocol is now `SpawnProtocol.Command` * `Future` removed from `ActorSystem.systemActorOf`. +* `toUntyped` has been renamed to `toClassic`. +* Akka Typed is now using SLF4J as the logging API. @scala[`ActorContext.log`]@java[`ActorContext.getLog`] returns + an `org.slf4j.Logger`. MDC has been changed to only support `String` values. +* `setLoggerClass` in `ActorContext` has been renamed to `setLoggerName`. #### Akka Typed Stream API changes * `ActorSource.actorRef` relying on `PartialFunction` has been replaced in the Java API with a variant more suitable to be called by Java. -* `toUntyped` has been renamed to `toClassic`. - +* Factories for creating a materializer from an `akka.actor.typed.ActorSystem` have been removed. + A stream can be run with an `akka.actor.typed.ActorSystem` @scala[in implicit scope]@java[parameter] + and therefore the need for creating a materializer has been reduced. ## Akka Stream changes diff --git a/akka-docs/src/test/java/jdocs/typed/tutorial_4/DeviceGroup.java b/akka-docs/src/test/java/jdocs/typed/tutorial_4/DeviceGroup.java index 2170a6cb47..db2ceed759 100644 --- a/akka-docs/src/test/java/jdocs/typed/tutorial_4/DeviceGroup.java +++ b/akka-docs/src/test/java/jdocs/typed/tutorial_4/DeviceGroup.java @@ -69,7 +69,7 @@ public class DeviceGroup extends AbstractBehavior { } else { context .getLog() - .warning( + .warn( "Ignoring TrackDevice request for {}. This actor is responsible for {}.", groupId, this.groupId); diff --git a/akka-docs/src/test/java/jdocs/typed/tutorial_5/DeviceGroup.java b/akka-docs/src/test/java/jdocs/typed/tutorial_5/DeviceGroup.java index e3b699306b..ae1be3dfa3 100644 --- a/akka-docs/src/test/java/jdocs/typed/tutorial_5/DeviceGroup.java +++ b/akka-docs/src/test/java/jdocs/typed/tutorial_5/DeviceGroup.java @@ -65,7 +65,7 @@ public class DeviceGroup extends AbstractBehavior { } else { context .getLog() - .warning( + .warn( "Ignoring TrackDevice request for {}. This actor is responsible for {}.", groupId, this.groupId); diff --git a/akka-docs/src/test/resources/logback-test.xml b/akka-docs/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..1982b856a3 --- /dev/null +++ b/akka-docs/src/test/resources/logback-test.xml @@ -0,0 +1,19 @@ + + + + + + + + DEBUG + DENY + + + %date{ISO8601} %-5level %logger %X{akkaSource} %X{sourceThread} - %msg%n + + + + + + + diff --git a/akka-docs/src/test/scala/typed/tutorial_3/Device.scala b/akka-docs/src/test/scala/typed/tutorial_3/Device.scala index 245b828920..6a0b25c2dd 100644 --- a/akka-docs/src/test/scala/typed/tutorial_3/Device.scala +++ b/akka-docs/src/test/scala/typed/tutorial_3/Device.scala @@ -12,6 +12,7 @@ import akka.actor.typed.Signal import akka.actor.typed.scaladsl.AbstractBehavior import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.Behaviors +import akka.actor.typed.scaladsl.LoggerOps object Device { def apply(groupId: String, deviceId: String): Behavior[Command] = @@ -35,12 +36,12 @@ class Device(context: ActorContext[Device.Command], groupId: String, deviceId: S var lastTemperatureReading: Option[Double] = None - context.log.info("Device actor {}-{} started", groupId, deviceId) + context.log.info2("Device actor {}-{} started", groupId, deviceId) override def onMessage(msg: Command): Behavior[Command] = { msg match { case RecordTemperature(id, value, replyTo) => - context.log.info("Recorded temperature reading {} with {}", value, id) + context.log.info2("Recorded temperature reading {} with {}", value, id) lastTemperatureReading = Some(value) replyTo ! TemperatureRecorded(id) this @@ -53,7 +54,7 @@ class Device(context: ActorContext[Device.Command], groupId: String, deviceId: S override def onSignal: PartialFunction[Signal, Behavior[Command]] = { case PostStop => - context.log.info("Device actor {}-{} stopped", groupId, deviceId) + context.log.info2("Device actor {}-{} stopped", groupId, deviceId) this } diff --git a/akka-docs/src/test/scala/typed/tutorial_3/DeviceInProgress.scala b/akka-docs/src/test/scala/typed/tutorial_3/DeviceInProgress.scala index d41e6ea2de..629d753de3 100644 --- a/akka-docs/src/test/scala/typed/tutorial_3/DeviceInProgress.scala +++ b/akka-docs/src/test/scala/typed/tutorial_3/DeviceInProgress.scala @@ -4,9 +4,9 @@ package typed.tutorial_3 -import akka.actor.typed.ActorRef import akka.actor.typed.PostStop import akka.actor.typed.Signal +import akka.actor.typed.scaladsl.LoggerOps object DeviceInProgress1 { @@ -48,7 +48,7 @@ object DeviceInProgress2 { var lastTemperatureReading: Option[Double] = None - context.log.info("Device actor {}-{} started", groupId, deviceId) + context.log.info2("Device actor {}-{} started", groupId, deviceId) override def onMessage(msg: Command): Behavior[Command] = { msg match { @@ -60,7 +60,7 @@ object DeviceInProgress2 { override def onSignal: PartialFunction[Signal, Behavior[Command]] = { case PostStop => - context.log.info("Device actor {}-{} stopped", groupId, deviceId) + context.log.info2("Device actor {}-{} stopped", groupId, deviceId) this } diff --git a/akka-docs/src/test/scala/typed/tutorial_4/Device.scala b/akka-docs/src/test/scala/typed/tutorial_4/Device.scala index 3c3bdefd6e..197a68090c 100644 --- a/akka-docs/src/test/scala/typed/tutorial_4/Device.scala +++ b/akka-docs/src/test/scala/typed/tutorial_4/Device.scala @@ -12,6 +12,7 @@ import akka.actor.typed.Signal import akka.actor.typed.scaladsl.AbstractBehavior import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.Behaviors +import akka.actor.typed.scaladsl.LoggerOps object Device { def apply(groupId: String, deviceId: String): Behavior[Command] = @@ -37,12 +38,12 @@ class Device(context: ActorContext[Device.Command], groupId: String, deviceId: S var lastTemperatureReading: Option[Double] = None - context.log.info("Device actor {}-{} started", groupId, deviceId) + context.log.info2("Device actor {}-{} started", groupId, deviceId) override def onMessage(msg: Command): Behavior[Command] = { msg match { case RecordTemperature(id, value, replyTo) => - context.log.info("Recorded temperature reading {} with {}", value, id) + context.log.info2("Recorded temperature reading {} with {}", value, id) lastTemperatureReading = Some(value) replyTo ! TemperatureRecorded(id) this @@ -58,7 +59,7 @@ class Device(context: ActorContext[Device.Command], groupId: String, deviceId: S override def onSignal: PartialFunction[Signal, Behavior[Command]] = { case PostStop => - context.log.info("Device actor {}-{} stopped", groupId, deviceId) + context.log.info2("Device actor {}-{} stopped", groupId, deviceId) this } diff --git a/akka-docs/src/test/scala/typed/tutorial_4/DeviceGroup.scala b/akka-docs/src/test/scala/typed/tutorial_4/DeviceGroup.scala index 29b2c87d3d..4ab9ed8141 100644 --- a/akka-docs/src/test/scala/typed/tutorial_4/DeviceGroup.scala +++ b/akka-docs/src/test/scala/typed/tutorial_4/DeviceGroup.scala @@ -11,6 +11,7 @@ import akka.actor.typed.Signal import akka.actor.typed.scaladsl.AbstractBehavior import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.Behaviors +import akka.actor.typed.scaladsl.LoggerOps //#device-group-full //#device-group-register @@ -55,7 +56,7 @@ class DeviceGroup(context: ActorContext[DeviceGroup.Command], groupId: String) this case RequestTrackDevice(gId, _, _) => - context.log.warning("Ignoring TrackDevice request for {}. This actor is responsible for {}.", gId, groupId) + context.log.warn2("Ignoring TrackDevice request for {}. This actor is responsible for {}.", gId, groupId) this //#device-group-register //#device-group-remove diff --git a/akka-docs/src/test/scala/typed/tutorial_5/Device.scala b/akka-docs/src/test/scala/typed/tutorial_5/Device.scala index 7c5439a040..52a690f3bc 100644 --- a/akka-docs/src/test/scala/typed/tutorial_5/Device.scala +++ b/akka-docs/src/test/scala/typed/tutorial_5/Device.scala @@ -12,6 +12,7 @@ import akka.actor.typed.Signal import akka.actor.typed.scaladsl.AbstractBehavior import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.Behaviors +import akka.actor.typed.scaladsl.LoggerOps object Device { def apply(groupId: String, deviceId: String): Behavior[Command] = @@ -35,12 +36,12 @@ class Device(context: ActorContext[Device.Command], groupId: String, deviceId: S var lastTemperatureReading: Option[Double] = None - context.log.info("Device actor {}-{} started", groupId, deviceId) + context.log.info2("Device actor {}-{} started", groupId, deviceId) override def onMessage(msg: Command): Behavior[Command] = { msg match { case RecordTemperature(id, value, replyTo) => - context.log.info("Recorded temperature reading {} with {}", value, id) + context.log.info2("Recorded temperature reading {} with {}", value, id) lastTemperatureReading = Some(value) replyTo ! TemperatureRecorded(id) this @@ -56,7 +57,7 @@ class Device(context: ActorContext[Device.Command], groupId: String, deviceId: S override def onSignal: PartialFunction[Signal, Behavior[Command]] = { case PostStop => - context.log.info("Device actor {}-{} stopped", groupId, deviceId) + context.log.info2("Device actor {}-{} stopped", groupId, deviceId) this } diff --git a/akka-docs/src/test/scala/typed/tutorial_5/DeviceGroup.scala b/akka-docs/src/test/scala/typed/tutorial_5/DeviceGroup.scala index 334dc552c9..e226eea063 100644 --- a/akka-docs/src/test/scala/typed/tutorial_5/DeviceGroup.scala +++ b/akka-docs/src/test/scala/typed/tutorial_5/DeviceGroup.scala @@ -12,6 +12,7 @@ import akka.actor.typed.Signal import akka.actor.typed.scaladsl.AbstractBehavior import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.Behaviors +import akka.actor.typed.scaladsl.LoggerOps object DeviceGroup { def apply(groupId: String): Behavior[Command] = @@ -59,7 +60,7 @@ class DeviceGroup(context: ActorContext[DeviceGroup.Command], groupId: String) this case RequestTrackDevice(gId, _, _) => - context.log.warning("Ignoring TrackDevice request for {}. This actor is responsible for {}.", gId, groupId) + context.log.warn2("Ignoring TrackDevice request for {}. This actor is responsible for {}.", gId, groupId) this case RequestDeviceList(requestId, gId, replyTo) => diff --git a/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/BehaviorSetup.scala b/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/BehaviorSetup.scala index 6e7b3ad9dd..bc148c7928 100644 --- a/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/BehaviorSetup.scala +++ b/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/BehaviorSetup.scala @@ -6,8 +6,8 @@ package akka.persistence.typed.internal import scala.concurrent.ExecutionContext import scala.util.control.NonFatal + import akka.actor.Cancellable -import akka.actor.typed.Logger import akka.actor.typed.scaladsl.ActorContext import akka.actor.ActorRef import akka.actor.typed.Signal @@ -18,6 +18,8 @@ import akka.persistence.typed.scaladsl.EventSourcedBehavior import akka.persistence.typed.scaladsl.RetentionCriteria import akka.util.ConstantFun import akka.util.OptionVal +import org.slf4j.Logger +import org.slf4j.MDC /** * INTERNAL API @@ -62,28 +64,16 @@ private[akka] final class BehaviorSetup[C, E, S]( def selfClassic: ActorRef = context.self.toClassic - private var mdc: Map[String, Any] = Map.empty - private var _log: OptionVal[Logger] = OptionVal.Some(context.log) // changed when mdc is changed + private var mdcPhase = PersistenceMdc.Initializing def log: Logger = { - _log match { - case OptionVal.Some(l) => l - case OptionVal.None => - // lazy init if mdc changed - val l = context.log.withMdc(mdc) - _log = OptionVal.Some(l) - l - } + // MDC is cleared (if used) from aroundReceive in ActorAdapter after processing each message, + // but important to call `context.log` to mark MDC as used + PersistenceMdc.setMdc(persistenceId, mdcPhase) + context.log } - def setMdc(newMdc: Map[String, Any]): BehaviorSetup[C, E, S] = { - mdc = newMdc - // mdc is changed often, for each persisted event, but logging is rare, so lazy init of Logger - _log = OptionVal.None - this - } - - def setMdc(phaseName: String): BehaviorSetup[C, E, S] = { - setMdc(MDC.create(persistenceId, phaseName)) + def setMdcPhase(phaseName: String): BehaviorSetup[C, E, S] = { + mdcPhase = phaseName this } @@ -121,9 +111,10 @@ private[akka] final class BehaviorSetup[C, E, S]( } catch { case NonFatal(ex) => if (catchAndLog) - log.error(ex, s"Error while processing signal [{}]", signal) + log.error(s"Error while processing signal [$signal]: $ex", ex) else { - log.debug(s"Error while processing signal [{}]: {}", signal, ex) + if (log.isDebugEnabled) + log.debug(s"Error while processing signal [$signal]: $ex", ex) throw ex } } @@ -147,17 +138,26 @@ private[akka] final class BehaviorSetup[C, E, S]( * INTERNAL API */ @InternalApi -private[akka] object MDC { +private[akka] object PersistenceMdc { // format: OFF + val Initializing = "initializing" val AwaitingPermit = "get-permit" - val ReplayingSnapshot = "replay-snap" - val ReplayingEvents = "replay-evts" - val RunningCmds = "running-cmnds" - val PersistingEvents = "persist-evts" - val StoringSnapshot = "storing-snapshot" + val ReplayingSnapshot = "load-snap" + val ReplayingEvents = "replay-evt" + val RunningCmds = "running-cmd" + val PersistingEvents = "persist-evt" + val StoringSnapshot = "storing-snap" // format: ON - def create(persistenceId: PersistenceId, phaseName: String): Map[String, Any] = { - Map("persistenceId" -> persistenceId.id, "phase" -> phaseName) + val PersistencePhaseKey = "persistencePhase" + val PersistenceIdKey = "persistenceId" + + // MDC is cleared (if used) from aroundReceive in ActorAdapter after processing each message, + // but important to call `context.log` to mark MDC as used + def setMdc(persistenceId: PersistenceId, phase: String): Unit = { + val mdcAdpater = MDC.getMDCAdapter + mdcAdpater.put(PersistenceIdKey, persistenceId.id) + mdcAdpater.put(PersistencePhaseKey, phase) } + } diff --git a/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/EventSourcedBehaviorImpl.scala b/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/EventSourcedBehaviorImpl.scala index beff3063ad..49316f7e33 100644 --- a/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/EventSourcedBehaviorImpl.scala +++ b/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/EventSourcedBehaviorImpl.scala @@ -16,6 +16,7 @@ import akka.actor.typed.Signal import akka.actor.typed.SupervisorStrategy import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.Behaviors +import akka.actor.typed.scaladsl.LoggerOps import akka.annotation._ import akka.persistence.JournalProtocol import akka.persistence.Recovery @@ -82,7 +83,7 @@ private[akka] final case class EventSourcedBehaviorImpl[Command, Event, State]( override def apply(context: typed.TypedActorContext[Command]): Behavior[Command] = { val ctx = context.asScala - ctx.setLoggerClass(loggerClass) + ctx.setLoggerName(loggerClass) val settings = EventSourcedSettings(ctx.system, journalPluginId.getOrElse(""), snapshotPluginId.getOrElse("")) // stashState outside supervise because StashState should survive restarts due to persist failures @@ -93,19 +94,19 @@ private[akka] final case class EventSourcedBehaviorImpl[Command, Event, State]( case (_, SnapshotCompleted(meta)) => ctx.log.debug("Save snapshot successful, snapshot metadata [{}].", meta) case (_, SnapshotFailed(meta, failure)) => - ctx.log.error(failure, "Save snapshot failed, snapshot metadata [{}].", meta) + ctx.log.error(s"Save snapshot failed, snapshot metadata [$meta] due to: ${failure.getMessage}", failure) case (_, DeleteSnapshotsCompleted(DeletionTarget.Individual(meta))) => ctx.log.debug("Persistent snapshot [{}] deleted successfully.", meta) case (_, DeleteSnapshotsCompleted(DeletionTarget.Criteria(criteria))) => ctx.log.debug("Persistent snapshots given criteria [{}] deleted successfully.", criteria) case (_, DeleteSnapshotsFailed(DeletionTarget.Individual(meta), failure)) => - ctx.log.warning("Failed to delete snapshot with meta [{}] due to [{}].", meta, failure) + ctx.log.warn2("Failed to delete snapshot with meta [{}] due to: {}", meta, failure.getMessage) case (_, DeleteSnapshotsFailed(DeletionTarget.Criteria(criteria), failure)) => - ctx.log.warning("Failed to delete snapshots given criteria [{}] due to [{}].", criteria, failure) + ctx.log.warn2("Failed to delete snapshots given criteria [{}] due to: {}", criteria, failure.getMessage) case (_, DeleteEventsCompleted(toSequenceNr)) => ctx.log.debug("Events successfully deleted to sequence number [{}].", toSequenceNr) case (_, DeleteEventsFailed(toSequenceNr, failure)) => - ctx.log.warning("Failed to delete events to sequence number [{}] due to [{}].", toSequenceNr, failure) + ctx.log.warn2("Failed to delete events to sequence number [{}] due to: {}", toSequenceNr, failure.getMessage) } // do this once, even if the actor is restarted diff --git a/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/ExternalInteractions.scala b/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/ExternalInteractions.scala index 59c98cef9e..755a4d5786 100644 --- a/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/ExternalInteractions.scala +++ b/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/ExternalInteractions.scala @@ -13,6 +13,7 @@ import akka.actor.typed.PreRestart import akka.actor.typed.Signal import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.Behaviors +import akka.actor.typed.scaladsl.LoggerOps import akka.annotation.InternalApi import akka.annotation.InternalStableApi @@ -100,7 +101,7 @@ private[akka] trait JournalInteractions[C, E, S] { @unused repr: immutable.Seq[PersistentRepr]): Unit = () protected def replayEvents(fromSeqNr: Long, toSeqNr: Long): Unit = { - setup.log.debug("Replaying messages: from: {}, to: {}", fromSeqNr, toSeqNr) + setup.log.debug2("Replaying messages: from: {}, to: {}", fromSeqNr, toSeqNr) setup.journal ! ReplayMessages( fromSeqNr, toSeqNr, @@ -182,7 +183,7 @@ private[akka] trait SnapshotInteractions[C, E, S] { protected def internalDeleteSnapshots(fromSequenceNr: Long, toSequenceNr: Long): Unit = { if (toSequenceNr > 0) { val snapshotCriteria = SnapshotSelectionCriteria(minSequenceNr = fromSequenceNr, maxSequenceNr = toSequenceNr) - setup.log.debug("Deleting snapshots from sequenceNr [{}] to [{}]", fromSequenceNr, toSequenceNr) + setup.log.debug2("Deleting snapshots from sequenceNr [{}] to [{}]", fromSequenceNr, toSequenceNr) setup.snapshotStore .tell(SnapshotProtocol.DeleteSnapshots(setup.persistenceId.id, snapshotCriteria), setup.selfClassic) } diff --git a/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/ReplayingEvents.scala b/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/ReplayingEvents.scala index f2351bad11..d91afde722 100644 --- a/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/ReplayingEvents.scala +++ b/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/ReplayingEvents.scala @@ -10,7 +10,7 @@ import scala.concurrent.duration._ import akka.actor.typed.{ Behavior, Signal } import akka.actor.typed.internal.PoisonPill import akka.actor.typed.internal.UnstashException -import akka.actor.typed.scaladsl.{ AbstractBehavior, ActorContext, Behaviors } +import akka.actor.typed.scaladsl.{ AbstractBehavior, ActorContext, Behaviors, LoggerOps } import akka.annotation.{ InternalApi, InternalStableApi } import akka.event.Logging import akka.persistence.JournalProtocol._ @@ -56,7 +56,7 @@ private[akka] object ReplayingEvents { Behaviors.setup { _ => // protect against event recovery stalling forever because of journal overloaded and such setup.startRecoveryTimer(snapshot = false) - new ReplayingEvents[C, E, S](setup.setMdc(MDC.ReplayingEvents), state) + new ReplayingEvents[C, E, S](setup.setMdcPhase(PersistenceMdc.ReplayingEvents), state) } } @@ -181,7 +181,7 @@ private[akka] final class ReplayingEvents[C, E, S]( def onSnapshotterResponse(response: SnapshotProtocol.Response): Behavior[InternalProtocol] = { setup.log - .warning("Unexpected [{}] from SnapshotStore, already in replaying events state.", Logging.simpleName(response)) + .warn("Unexpected [{}] from SnapshotStore, already in replaying events state.", Logging.simpleName(response)) Behaviors.unhandled // ignore the response } @@ -199,7 +199,7 @@ private[akka] final class ReplayingEvents[C, E, S]( setup.cancelRecoveryTimer() tryReturnRecoveryPermit("on replay failure: " + cause.getMessage) if (setup.log.isDebugEnabled) { - setup.log.debug( + setup.log.debug2( "Recovery failure for persistenceId [{}] after {}", setup.persistenceId, (System.nanoTime() - state.recoveryStartTime).nanos.pretty) @@ -209,10 +209,10 @@ private[akka] final class ReplayingEvents[C, E, S]( val msg = event match { case Some(_: Message) | None => s"Exception during recovery. Last known sequence number [$sequenceNr]. " + - s"PersistenceId [${setup.persistenceId.id}]. ${cause.getMessage}" + s"PersistenceId [${setup.persistenceId.id}], due to: ${cause.getMessage}" case Some(evt) => s"Exception during recovery while handling [${evt.getClass.getName}] with sequence number [$sequenceNr]. " + - s"PersistenceId [${setup.persistenceId.id}]. ${cause.getMessage}" + s"PersistenceId [${setup.persistenceId.id}], due to: ${cause.getMessage}" } throw new JournalFailureException(msg, cause) @@ -223,7 +223,7 @@ private[akka] final class ReplayingEvents[C, E, S]( onRecoveryComplete(setup.context) tryReturnRecoveryPermit("replay completed successfully") if (setup.log.isDebugEnabled) { - setup.log.debug( + setup.log.debug2( "Recovery for persistenceId [{}] took {}", setup.persistenceId, (System.nanoTime() - state.recoveryStartTime).nanos.pretty) diff --git a/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/ReplayingSnapshot.scala b/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/ReplayingSnapshot.scala index fd56dbca91..c790efe587 100644 --- a/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/ReplayingSnapshot.scala +++ b/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/ReplayingSnapshot.scala @@ -32,7 +32,7 @@ import akka.util.unused private[akka] object ReplayingSnapshot { def apply[C, E, S](setup: BehaviorSetup[C, E, S], receivedPoisonPill: Boolean): Behavior[InternalProtocol] = - new ReplayingSnapshot(setup.setMdc(MDC.ReplayingSnapshot)).createBehavior(receivedPoisonPill) + new ReplayingSnapshot(setup.setMdcPhase(PersistenceMdc.ReplayingSnapshot)).createBehavior(receivedPoisonPill) } @@ -86,7 +86,7 @@ private[akka] class ReplayingSnapshot[C, E, S](override val setup: BehaviorSetup private def onRecoveryFailure(cause: Throwable): Behavior[InternalProtocol] = { onRecoveryFailed(setup.context, cause) setup.cancelRecoveryTimer() - setup.log.error(cause, s"Persistence failure when replaying snapshot. ${cause.getMessage}") + setup.log.error(s"Persistence failure when replaying snapshot, due to: ${cause.getMessage}", cause) Behaviors.stopped } diff --git a/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/RequestingRecoveryPermit.scala b/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/RequestingRecoveryPermit.scala index 8b874dd041..7de1ff24ce 100644 --- a/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/RequestingRecoveryPermit.scala +++ b/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/RequestingRecoveryPermit.scala @@ -23,7 +23,7 @@ import akka.annotation.InternalApi private[akka] object RequestingRecoveryPermit { def apply[C, E, S](setup: BehaviorSetup[C, E, S]): Behavior[InternalProtocol] = - new RequestingRecoveryPermit(setup.setMdc(MDC.AwaitingPermit)).createBehavior() + new RequestingRecoveryPermit(setup.setMdcPhase(PersistenceMdc.AwaitingPermit)).createBehavior() } diff --git a/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/Running.scala b/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/Running.scala index dcaf3a70f2..8c44b551ed 100644 --- a/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/Running.scala +++ b/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/Running.scala @@ -10,7 +10,7 @@ import akka.actor.UnhandledMessage import akka.actor.typed.Behavior import akka.actor.typed.Signal import akka.actor.typed.internal.PoisonPill -import akka.actor.typed.scaladsl.{ AbstractBehavior, ActorContext, Behaviors } +import akka.actor.typed.scaladsl.{ AbstractBehavior, ActorContext, Behaviors, LoggerOps } import akka.annotation.{ InternalApi, InternalStableApi } import akka.persistence.DeleteMessagesFailure import akka.persistence.DeleteMessagesSuccess @@ -79,7 +79,7 @@ private[akka] object Running { } def apply[C, E, S](setup: BehaviorSetup[C, E, S], state: RunningState[S]): Behavior[InternalProtocol] = { - val running = new Running(setup.setMdc(MDC.RunningCmds)) + val running = new Running(setup.setMdcPhase(PersistenceMdc.RunningCmds)) new running.HandlingCommands(state) } } @@ -95,10 +95,6 @@ private[akka] object Running { import Running.RunningState import BehaviorSetup._ - private val runningCmdsMdc = MDC.create(setup.persistenceId, MDC.RunningCmds) - private val persistingEventsMdc = MDC.create(setup.persistenceId, MDC.PersistingEvents) - private val storingSnapshotMdc = MDC.create(setup.persistenceId, MDC.StoringSnapshot) - final class HandlingCommands(state: RunningState[S]) extends AbstractBehavior[InternalProtocol] with WithSeqNrAccessible { @@ -130,7 +126,7 @@ private[akka] object Running { effect: Effect[E, S], sideEffects: immutable.Seq[SideEffect[S]] = Nil): Behavior[InternalProtocol] = { if (setup.log.isDebugEnabled && !effect.isInstanceOf[CompositeEffect[_, _]]) - setup.log.debug( + setup.log.debugN( s"Handled command [{}], resulting effect: [{}], side effects: [{}]", msg.getClass.getName, effect, @@ -205,7 +201,7 @@ private[akka] object Running { Tagged(adaptedEvent, tags) } - setup.setMdc(runningCmdsMdc) + setup.setMdcPhase(PersistenceMdc.RunningCmds) override def currentSequenceNumber: Long = state.seqNr } @@ -218,7 +214,7 @@ private[akka] object Running { numberOfEvents: Int, shouldSnapshotAfterPersist: SnapshotAfterPersist, sideEffects: immutable.Seq[SideEffect[S]]): Behavior[InternalProtocol] = { - setup.setMdc(persistingEventsMdc) + setup.setMdcPhase(PersistenceMdc.PersistingEvents) new PersistingEvents(state, visibleState, numberOfEvents, shouldSnapshotAfterPersist, sideEffects) } @@ -258,7 +254,10 @@ private[akka] object Running { final def onJournalResponse(response: Response): Behavior[InternalProtocol] = { if (setup.log.isDebugEnabled) { - setup.log.debug("Received Journal response: {} after: {} nanos", response, System.nanoTime() - persistStartTime) + setup.log.debug2( + "Received Journal response: {} after: {} nanos", + response, + System.nanoTime() - persistStartTime) } def onWriteResponse(p: PersistentRepr): Behavior[InternalProtocol] = { @@ -336,7 +335,7 @@ private[akka] object Running { state: RunningState[S], sideEffects: immutable.Seq[SideEffect[S]], snapshotReason: SnapshotAfterPersist): Behavior[InternalProtocol] = { - setup.setMdc(storingSnapshotMdc) + setup.setMdcPhase(PersistenceMdc.StoringSnapshot) def onCommand(cmd: IncomingCommand[C]): Behavior[InternalProtocol] = { if (state.receivedPoisonPill) { @@ -371,14 +370,14 @@ private[akka] object Running { Some(SnapshotCompleted(SnapshotMetadata.fromClassic(meta))) case SaveSnapshotFailure(meta, error) => - setup.log.warning("Failed to save snapshot given metadata [{}] due to [{}]", meta, error.getMessage) + setup.log.warn2("Failed to save snapshot given metadata [{}] due to: {}", meta, error.getMessage) Some(SnapshotFailed(SnapshotMetadata.fromClassic(meta), error)) case _ => None } - setup.log.debug("Received snapshot response [{}], emitting signal [{}].", response, signal) + setup.log.debug2("Received snapshot response [{}], emitting signal [{}].", response, signal) signal.foreach(setup.onSignal(state.state, _, catchAndLog = false)) } diff --git a/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/StashManagement.scala b/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/StashManagement.scala index adb6714fa1..13018a68c4 100644 --- a/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/StashManagement.scala +++ b/akka-persistence-typed/src/main/scala/akka/persistence/typed/internal/StashManagement.scala @@ -9,6 +9,7 @@ import akka.actor.Dropped import akka.actor.typed.scaladsl.adapter._ import akka.actor.typed.scaladsl.StashOverflowException import akka.actor.typed.scaladsl.ActorContext +import akka.actor.typed.scaladsl.LoggerOps import akka.actor.typed.scaladsl.StashBuffer import akka.annotation.InternalApi import akka.util.ConstantFun @@ -45,13 +46,11 @@ private[akka] trait StashManagement[C, E, S] { case e: StashOverflowException => setup.settings.stashOverflowStrategy match { case StashOverflowStrategy.Drop => - if (context.log.isWarningEnabled) { - val dropName = msg match { - case InternalProtocol.IncomingCommand(actual) => actual.getClass.getName - case other => other.getClass.getName - } - context.log.warning("Stash buffer is full, dropping message [{}]", dropName) + val dropName = msg match { + case InternalProtocol.IncomingCommand(actual) => actual.getClass.getName + case other => other.getClass.getName } + context.log.warn("Stash buffer is full, dropping message [{}]", dropName) context.system.toClassic.eventStream.publish(Dropped(msg, "Stash buffer is full", context.self.toClassic)) case StashOverflowStrategy.Fail => throw e @@ -97,7 +96,7 @@ private[akka] trait StashManagement[C, E, S] { private def logStashMessage(msg: InternalProtocol, buffer: StashBuffer[InternalProtocol]): Unit = { if (setup.settings.logOnStashing) - setup.log.debug( + setup.log.debugN( "Stashing message to {} stash: [{}] ", if (buffer eq stashState.internalStashBuffer) "internal" else "user", msg) @@ -105,7 +104,7 @@ private[akka] trait StashManagement[C, E, S] { private def logUnstashMessage(buffer: StashBuffer[InternalProtocol]): Unit = { if (setup.settings.logOnStashing) - setup.log.debug( + setup.log.debugN( "Unstashing message from {} stash: [{}]", if (buffer eq stashState.internalStashBuffer) "internal" else "user", buffer.head) @@ -113,7 +112,7 @@ private[akka] trait StashManagement[C, E, S] { private def logUnstashAll(): Unit = { if (setup.settings.logOnStashing) - setup.log.debug( + setup.log.debug2( "Unstashing all [{}] messages from user stash, first is: [{}]", stashState.userStashBuffer.size, stashState.userStashBuffer.head) diff --git a/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/EventSourcedActorFailureTest.java b/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/EventSourcedActorFailureTest.java index a9c2e59af3..4d8b84f58c 100644 --- a/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/EventSourcedActorFailureTest.java +++ b/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/EventSourcedActorFailureTest.java @@ -5,6 +5,7 @@ package akka.persistence.typed.javadsl; import akka.actor.testkit.typed.TestException; +import akka.actor.testkit.typed.javadsl.LogCapturing; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import akka.actor.testkit.typed.javadsl.TestProbe; import akka.actor.typed.ActorRef; @@ -13,11 +14,10 @@ import akka.actor.typed.SupervisorStrategy; import akka.persistence.typed.PersistenceId; import akka.persistence.typed.RecoveryCompleted; import akka.persistence.typed.RecoveryFailed; -import akka.testkit.EventFilter; -import akka.testkit.TestEvent; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.scalatest.junit.JUnitSuite; @@ -86,6 +86,8 @@ public class EventSourcedActorFailureTest extends JUnitSuite { @ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource(config); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + public static Behavior fail( PersistenceId pid, ActorRef probe, ActorRef recoveryFailureProbe) { return new FailingEventSourcedActor(pid, probe, recoveryFailureProbe); @@ -95,24 +97,6 @@ public class EventSourcedActorFailureTest extends JUnitSuite { return fail(pid, probe, testKit.createTestProbe().ref()); } - public EventSourcedActorFailureTest() { - // FIXME ##24348 silence logging in a proper way - akka.actor.typed.javadsl.Adapter.toClassic(testKit.system()) - .eventStream() - .publish( - new TestEvent.Mute( - akka.japi.Util.immutableSeq( - new EventFilter[] { - EventFilter.warning(null, null, "No default snapshot store", null, 1) - }))); - akka.actor.typed.javadsl.Adapter.toClassic(testKit.system()) - .eventStream() - .publish( - new TestEvent.Mute( - akka.japi.Util.immutableSeq( - new EventFilter[] {EventFilter.error(null, null, "", ".*saw failure.*", 1)}))); - } - @Test public void notifyRecoveryFailure() { TestProbe probe = testKit.createTestProbe(); diff --git a/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/LoggerSourceTest.java b/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/LoggerSourceTest.java index 8ceb5db24c..c7ade1337e 100644 --- a/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/LoggerSourceTest.java +++ b/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/LoggerSourceTest.java @@ -4,6 +4,8 @@ package akka.persistence.typed.javadsl; +import akka.actor.testkit.typed.javadsl.LogCapturing; +import akka.actor.testkit.typed.javadsl.LoggingEventFilter; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import akka.actor.testkit.typed.javadsl.TestProbe; import akka.actor.typed.ActorRef; @@ -12,26 +14,28 @@ import akka.actor.typed.javadsl.ActorContext; import akka.actor.typed.javadsl.Behaviors; import akka.persistence.typed.PersistenceId; import akka.persistence.typed.RecoveryCompleted; -import akka.testkit.EventFilter; -import akka.testkit.TestEvent; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.scalatest.junit.JUnitSuite; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; public class LoggerSourceTest extends JUnitSuite { private static final Config config = ConfigFactory.parseString( - "akka.loggers = [akka.testkit.TestEventListener] \n" - + "akka.persistence.journal.plugin = \"akka.persistence.journal.inmem\" \n" + "akka.persistence.journal.plugin = \"akka.persistence.journal.inmem\" \n" + "akka.persistence.journal.inmem.test-serialization = on \n"); @ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource(config); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + private static final AtomicInteger idCounter = new AtomicInteger(0); public static PersistenceId nextId() { @@ -94,26 +98,58 @@ public class LoggerSourceTest extends JUnitSuite { return new LoggingBehavior(nextId(), ctx); }); - public LoggerSourceTest() { - // FIXME ##24348 silence logging in a proper way - akka.actor.typed.javadsl.Adapter.toClassic(testKit.system()) - .eventStream() - .publish( - new TestEvent.Mute( - akka.japi.Util.immutableSeq( - new EventFilter[] { - EventFilter.warning(null, null, "No default snapshot store", null, 1) - }))); - } - @Test - public void verifyLoggerClass() { - ActorRef ref = testKit.spawn(behavior); - ref.tell("command"); - // no Java testkit support for custom event filters to look at the logger class - // so this is a manual-occular test for now (additionally it requires a small change to - // stdoutlogger to print log event class name) - // FIXME #24348: eventfilter support in typed testkit + public void verifyLogging() { + Map expectedMdc1 = new HashMap<>(); + expectedMdc1.put("persistenceId", "1"); + expectedMdc1.put("persistencePhase", "replay-evt"); + + ActorRef ref = + LoggingEventFilter.info("recovery-completed") + .withMdc(expectedMdc1) + .withCustom(event -> event.loggerName().equals(LoggingBehavior.class.getName())) + .intercept( + testKit.system(), + () -> { + return testKit.spawn(behavior); + }); + + // MDC persistenceId ajd persistencePhase for the "command-received" not included in the + // "command-received" logging, because that is via ActorContext.log directly and + // EventSourcedBehaviorImpl + // isn't involved. + + LoggingEventFilter.info("command-received") + .withCustom( + event -> { + return event.loggerName().equals(LoggingBehavior.class.getName()) + && event.getMdc().get("akkaSource").equals(ref.path().toString()); + }) + .intercept( + testKit.system(), + () -> { + ref.tell("command"); + return null; + }); + + Map expectedMdc3 = new HashMap<>(); + expectedMdc3.put("persistenceId", "1"); + expectedMdc3.put("persistencePhase", "running-cmd"); + + LoggingEventFilter.info("event-received") + .withMdc(expectedMdc3) + .withCustom( + event -> { + return event.loggerName().equals(LoggingBehavior.class.getName()) + && event.getMdc().get("akkaSource").equals(ref.path().toString()); + }) + .intercept( + testKit.system(), + () -> { + ref.tell("command"); + return null; + }); + TestProbe probe = testKit.createTestProbe(); ref.tell("stop"); probe.expectTerminated(ref); diff --git a/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/NullEmptyStateTest.java b/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/NullEmptyStateTest.java index a34dcc809d..2cdb05e174 100644 --- a/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/NullEmptyStateTest.java +++ b/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/NullEmptyStateTest.java @@ -4,6 +4,7 @@ package akka.persistence.typed.javadsl; +import akka.actor.testkit.typed.javadsl.LogCapturing; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import akka.actor.testkit.typed.javadsl.TestProbe; import akka.actor.typed.ActorRef; @@ -14,6 +15,7 @@ import akka.persistence.typed.RecoveryCompleted; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.scalatest.junit.JUnitSuite; @@ -26,6 +28,8 @@ public class NullEmptyStateTest extends JUnitSuite { @ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource(config); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + static class NullEmptyState extends EventSourcedBehavior { private final ActorRef probe; diff --git a/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/PersistentActorJavaDslTest.java b/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/PersistentActorJavaDslTest.java index 3946695872..10d0bc885b 100644 --- a/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/PersistentActorJavaDslTest.java +++ b/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/PersistentActorJavaDslTest.java @@ -5,6 +5,8 @@ package akka.persistence.typed.javadsl; import akka.Done; +import akka.actor.testkit.typed.javadsl.LogCapturing; +import akka.actor.testkit.typed.javadsl.LoggingEventFilter; import akka.actor.typed.*; import akka.actor.typed.javadsl.ActorContext; import akka.actor.typed.javadsl.Adapter; @@ -31,8 +33,11 @@ import com.google.common.collect.Sets; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; import org.scalatest.junit.JUnitSuite; +import org.slf4j.event.Level; import java.time.Duration; import java.util.*; @@ -44,11 +49,12 @@ import static org.junit.Assert.assertEquals; public class PersistentActorJavaDslTest extends JUnitSuite { public static final Config config = - ConfigFactory.parseString("akka.loggers = [akka.testkit.TestEventListener]") - .withFallback(EventSourcedBehaviorSpec.conf().withFallback(ConfigFactory.load())); + EventSourcedBehaviorSpec.conf().withFallback(ConfigFactory.load()); @ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource(config); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + private LeveldbReadJournal queries = PersistenceQuery.get(Adapter.toClassic(testKit.system())) .getReadJournalFor(LeveldbReadJournal.class, LeveldbReadJournal.Identifier()); @@ -697,13 +703,15 @@ public class PersistentActorJavaDslTest extends JUnitSuite { testKit.spawn( new IncorrectExpectedStateForThenRun(probe.getRef(), new PersistenceId("foiesftr"))); - probe.expectMessage("started!"); // workaround for #26256 + probe.expectMessage("started!"); - new EventFilter(Logging.Error.class, Adapter.toClassic(testKit.system())) - .occurrences(1) + LoggingEventFilter.empty() + .withLogLevel(Level.ERROR) // the error messages slightly changed in later JDKs - .matches("(class )?java.lang.Integer cannot be cast to (class )?java.lang.String.*") + .withMessageRegex( + "(class )?java.lang.Integer cannot be cast to (class )?java.lang.String.*") .intercept( + testKit.system(), () -> { c.tell("expect wrong type"); return null; diff --git a/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/PrimitiveStateTest.java b/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/PrimitiveStateTest.java index 037b438bf3..541b14bbdd 100644 --- a/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/PrimitiveStateTest.java +++ b/akka-persistence-typed/src/test/java/akka/persistence/typed/javadsl/PrimitiveStateTest.java @@ -4,6 +4,7 @@ package akka.persistence.typed.javadsl; +import akka.actor.testkit.typed.javadsl.LogCapturing; import akka.actor.testkit.typed.javadsl.TestKitJunitResource; import akka.actor.testkit.typed.javadsl.TestProbe; import akka.actor.typed.ActorRef; @@ -14,6 +15,7 @@ import akka.persistence.typed.RecoveryCompleted; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.scalatest.junit.JUnitSuite; @@ -26,6 +28,8 @@ public class PrimitiveStateTest extends JUnitSuite { @ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource(config); + @Rule public final LogCapturing logCapturing = new LogCapturing(); + static class PrimitiveState extends EventSourcedBehavior { private final ActorRef probe; diff --git a/akka-persistence-typed/src/test/resources/logback-test.xml b/akka-persistence-typed/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..5e80aa8ee0 --- /dev/null +++ b/akka-persistence-typed/src/test/resources/logback-test.xml @@ -0,0 +1,36 @@ + + + + + + + + DEBUG + DENY + + + %date{ISO8601} %-5level %logger %X{akkaSource} %X{sourceThread} { "persistenceId": "%X{persistenceId}", "persistencePhase": "%X{persistencePhase}" } - %msg%n + + + + + + + + + + + + + + + + diff --git a/akka-persistence-typed/src/test/scala/akka/persistence/typed/ManyRecoveriesSpec.scala b/akka-persistence-typed/src/test/scala/akka/persistence/typed/ManyRecoveriesSpec.scala index 00cdc92b59..46e4127596 100644 --- a/akka-persistence-typed/src/test/scala/akka/persistence/typed/ManyRecoveriesSpec.scala +++ b/akka-persistence-typed/src/test/scala/akka/persistence/typed/ManyRecoveriesSpec.scala @@ -14,8 +14,7 @@ import scala.concurrent.Await import scala.concurrent.duration._ import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit -import akka.testkit.EventFilter -import akka.testkit.TestEvent.Mute +import akka.actor.testkit.typed.scaladsl.LogCapturing import org.scalatest.WordSpecLike object ManyRecoveriesSpec { @@ -50,7 +49,6 @@ object ManyRecoveriesSpec { } class ManyRecoveriesSpec extends ScalaTestWithActorTestKit(s""" - akka.loggers = [akka.testkit.TestEventListener] akka.actor.default-dispatcher { type = Dispatcher executor = "thread-pool-executor" @@ -60,13 +58,10 @@ class ManyRecoveriesSpec extends ScalaTestWithActorTestKit(s""" } akka.persistence.max-concurrent-recoveries = 3 akka.persistence.journal.plugin = "akka.persistence.journal.inmem" - """) with WordSpecLike { + """) with WordSpecLike with LogCapturing { import ManyRecoveriesSpec._ - import akka.actor.typed.scaladsl.adapter._ - system.toClassic.eventStream.publish(Mute(EventFilter.warning(start = "No default snapshot store", occurrences = 1))) - "Many persistent actors" must { "be able to recover without overloading" in { val probe = TestProbe[String]() diff --git a/akka-persistence-typed/src/test/scala/akka/persistence/typed/StashingWhenSnapshottingSpec.scala b/akka-persistence-typed/src/test/scala/akka/persistence/typed/StashingWhenSnapshottingSpec.scala index 724119525e..6a38643f89 100644 --- a/akka-persistence-typed/src/test/scala/akka/persistence/typed/StashingWhenSnapshottingSpec.scala +++ b/akka-persistence-typed/src/test/scala/akka/persistence/typed/StashingWhenSnapshottingSpec.scala @@ -20,11 +20,12 @@ import akka.persistence.typed.StashingWhenSnapshottingSpec.ControllableSnapshotS import akka.persistence.typed.scaladsl.Effect import akka.persistence.typed.scaladsl.EventSourcedBehavior import org.scalatest.WordSpecLike - import scala.concurrent.Future import scala.concurrent.Promise import scala.util.Success +import akka.actor.testkit.typed.scaladsl.LogCapturing + object StashingWhenSnapshottingSpec { object ControllableSnapshotStoreExt extends ExtensionId[ControllableSnapshotStoreExt] { @@ -85,7 +86,8 @@ object StashingWhenSnapshottingSpec { class StashingWhenSnapshottingSpec extends ScalaTestWithActorTestKit(StashingWhenSnapshottingSpec.config) - with WordSpecLike { + with WordSpecLike + with LogCapturing { "A persistent actor" should { "stash messages and automatically replay when snapshot is in progress" in { val eventProbe = TestProbe[String]() diff --git a/akka-persistence-typed/src/test/scala/akka/persistence/typed/internal/RecoveryPermitterSpec.scala b/akka-persistence-typed/src/test/scala/akka/persistence/typed/internal/RecoveryPermitterSpec.scala index f915ee364f..046302c548 100644 --- a/akka-persistence-typed/src/test/scala/akka/persistence/typed/internal/RecoveryPermitterSpec.scala +++ b/akka-persistence-typed/src/test/scala/akka/persistence/typed/internal/RecoveryPermitterSpec.scala @@ -13,14 +13,14 @@ import akka.persistence.Persistence import akka.persistence.RecoveryPermitter.{ RecoveryPermitGranted, RequestRecoveryPermit, ReturnRecoveryPermit } import akka.persistence.typed.scaladsl.EventSourcedBehavior.CommandHandler import akka.persistence.typed.scaladsl.{ Effect, EventSourcedBehavior } -import akka.testkit.EventFilter import scala.concurrent.duration._ import scala.util.control.NoStackTrace +import akka.actor.testkit.typed.scaladsl.LoggingEventFilter import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.persistence.typed.PersistenceId import akka.persistence.typed.RecoveryCompleted -import akka.testkit.TestEvent.Mute import org.scalatest.WordSpecLike object RecoveryPermitterSpec { @@ -68,19 +68,16 @@ object RecoveryPermitterSpec { } class RecoveryPermitterSpec extends ScalaTestWithActorTestKit(s""" - akka.loggers = [akka.testkit.TestEventListener] akka.persistence.max-concurrent-recoveries = 3 akka.persistence.journal.plugin = "akka.persistence.journal.inmem" akka.persistence.journal.inmem.test-serialization = on akka.loggers = ["akka.testkit.TestEventListener"] - """) with WordSpecLike { + """) with WordSpecLike with LogCapturing { import RecoveryPermitterSpec._ implicit val classicSystem = system.toClassic - classicSystem.eventStream.publish(Mute(EventFilter.warning(start = "No default snapshot store", occurrences = 1))) - private val permitter = Persistence(classicSystem).recoveryPermitter def requestPermit(p: TestProbe[Any]): Unit = { @@ -192,7 +189,7 @@ class RecoveryPermitterSpec extends ScalaTestWithActorTestKit(s""" val stopProbe = createTestProbe[ActorRef[Command]]() val parent = - EventFilter.error(occurrences = 1, start = "Exception during recovery.").intercept { + LoggingEventFilter.error("Exception during recovery.").intercept { spawn(Behaviors.setup[Command](ctx => { val persistentActor = ctx.spawnAnonymous(persistentBehavior("p3", p3, p3, throwOnRecovery = true)) diff --git a/akka-persistence-typed/src/test/scala/akka/persistence/typed/internal/RetentionCriteriaSpec.scala b/akka-persistence-typed/src/test/scala/akka/persistence/typed/internal/RetentionCriteriaSpec.scala index daaa4d8a37..f567a41b60 100644 --- a/akka-persistence-typed/src/test/scala/akka/persistence/typed/internal/RetentionCriteriaSpec.scala +++ b/akka-persistence-typed/src/test/scala/akka/persistence/typed/internal/RetentionCriteriaSpec.scala @@ -4,12 +4,13 @@ package akka.persistence.typed.internal +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.persistence.typed.scaladsl.RetentionCriteria import org.scalatest.Matchers import org.scalatest.TestSuite import org.scalatest.WordSpecLike -class RetentionCriteriaSpec extends TestSuite with Matchers with WordSpecLike { +class RetentionCriteriaSpec extends TestSuite with Matchers with WordSpecLike with LogCapturing { "RetentionCriteria" must { diff --git a/akka-persistence-typed/src/test/scala/akka/persistence/typed/internal/StashStateSpec.scala b/akka-persistence-typed/src/test/scala/akka/persistence/typed/internal/StashStateSpec.scala index 3351488af9..58567fda2c 100644 --- a/akka-persistence-typed/src/test/scala/akka/persistence/typed/internal/StashStateSpec.scala +++ b/akka-persistence-typed/src/test/scala/akka/persistence/typed/internal/StashStateSpec.scala @@ -8,13 +8,14 @@ import scala.concurrent.duration._ import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.testkit.typed.scaladsl.TestProbe +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.Behavior import akka.actor.typed.scaladsl.Behaviors import akka.persistence.typed.internal.InternalProtocol.IncomingCommand import akka.persistence.typed.internal.InternalProtocol.RecoveryPermitGranted import org.scalatest.WordSpecLike -class StashStateSpec extends ScalaTestWithActorTestKit with WordSpecLike { +class StashStateSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing { "StashState" should { diff --git a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorFailureSpec.scala b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorFailureSpec.scala index c593e9690d..26b3a2ec35 100644 --- a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorFailureSpec.scala +++ b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorFailureSpec.scala @@ -27,8 +27,6 @@ import akka.persistence.typed.RecoveryCompleted import akka.persistence.typed.RecoveryCompleted import akka.persistence.typed.RecoveryFailed import akka.persistence.typed.internal.JournalFailureException -import akka.testkit.EventFilter -import akka.testkit.TestEvent.Mute import com.typesafe.config.Config import com.typesafe.config.ConfigFactory import org.scalatest.WordSpecLike @@ -74,7 +72,6 @@ object EventSourcedBehaviorFailureSpec { val conf: Config = ConfigFactory.parseString(s""" akka.loglevel = INFO - akka.loggers = [akka.testkit.TestEventListener] akka.persistence.journal.plugin = "failure-journal" failure-journal = $${akka.persistence.journal.inmem} failure-journal { @@ -85,15 +82,11 @@ object EventSourcedBehaviorFailureSpec { class EventSourcedBehaviorFailureSpec extends ScalaTestWithActorTestKit(EventSourcedBehaviorFailureSpec.conf) - with WordSpecLike { + with WordSpecLike + with LogCapturing { implicit val testSettings: TestKitSettings = TestKitSettings(system) - // Needed for the classic event filter - implicit val classic = system.toClassic - - classic.eventStream.publish(Mute(EventFilter.warning(start = "No default snapshot store", occurrences = 1))) - def failingPersistentActor( pid: PersistenceId, probe: ActorRef[String], @@ -128,7 +121,7 @@ class EventSourcedBehaviorFailureSpec "A typed persistent actor (failures)" must { "signal RecoveryFailure when replay fails" in { - EventFilter[JournalFailureException](occurrences = 1).intercept { + LoggingEventFilter.error[JournalFailureException].intercept { val probe = TestProbe[String]() val excProbe = TestProbe[Throwable]() spawn(failingPersistentActor(PersistenceId("fail-recovery"), probe.ref, { @@ -166,7 +159,7 @@ class EventSourcedBehaviorFailureSpec probe.expectMessage("malicious") probe.expectMessage("persisted") - EventFilter[JournalFailureException](occurrences = 1).intercept { + LoggingEventFilter.error[JournalFailureException].intercept { // start again and then the event handler will throw spawn(failingPersistentActor(pid, probe.ref, { case (_, RecoveryFailed(t)) => @@ -180,7 +173,7 @@ class EventSourcedBehaviorFailureSpec "fail recovery if exception from RecoveryCompleted signal handler" in { val probe = TestProbe[String]() - EventFilter[JournalFailureException](occurrences = 1).intercept { + LoggingEventFilter.error[JournalFailureException].intercept { spawn( Behaviors .supervise(failingPersistentActor(PersistenceId("recovery-ok"), probe.ref, { @@ -257,7 +250,7 @@ class EventSourcedBehaviorFailureSpec } "stop (default supervisor strategy) if command handler throws" in { - EventFilter[TestException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { val probe = TestProbe[String]() val behav = failingPersistentActor(PersistenceId("wrong-command-1"), probe.ref) val c = spawn(behav) @@ -268,7 +261,7 @@ class EventSourcedBehaviorFailureSpec } "restart supervisor strategy if command handler throws" in { - EventFilter[TestException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { val probe = TestProbe[String]() val behav = Behaviors .supervise(failingPersistentActor(PersistenceId("wrong-command-2"), probe.ref)) @@ -281,7 +274,7 @@ class EventSourcedBehaviorFailureSpec } "stop (default supervisor strategy) if side effect callback throws" in { - EventFilter[TestException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { val probe = TestProbe[String]() val behav = failingPersistentActor(PersistenceId("wrong-command-3"), probe.ref) val c = spawn(behav) @@ -296,7 +289,7 @@ class EventSourcedBehaviorFailureSpec "stop (default supervisor strategy) if signal handler throws" in { case object SomeSignal extends Signal - EventFilter[TestException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { val probe = TestProbe[String]() val behav = failingPersistentActor(PersistenceId("wrong-signal-handler"), probe.ref, { case (_, SomeSignal) => throw TestException("from signal") @@ -309,7 +302,7 @@ class EventSourcedBehaviorFailureSpec } "not accept wrong event, before persisting it" in { - EventFilter[TestException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { val probe = TestProbe[String]() val behav = failingPersistentActor(PersistenceId("wrong-event-2"), probe.ref) val c = spawn(behav) diff --git a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorInterceptorSpec.scala b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorInterceptorSpec.scala index 2bcaa41512..0d6bcf62a1 100644 --- a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorInterceptorSpec.scala +++ b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorInterceptorSpec.scala @@ -13,8 +13,6 @@ import akka.actor.typed.BehaviorInterceptor import akka.actor.typed.TypedActorContext import akka.actor.typed.scaladsl.Behaviors import akka.persistence.typed.PersistenceId -import akka.testkit.EventFilter -import akka.testkit.TestEvent.Mute import com.typesafe.config.Config import com.typesafe.config.ConfigFactory import org.scalatest.WordSpecLike @@ -25,7 +23,6 @@ object EventSourcedBehaviorInterceptorSpec { def config: Config = ConfigFactory.parseString(s""" akka.loglevel = INFO - akka.loggers = [akka.testkit.TestEventListener] akka.persistence.journal.plugin = "akka.persistence.journal.inmem" akka.persistence.journal.inmem.test-serialization = on """) @@ -47,19 +44,14 @@ object EventSourcedBehaviorInterceptorSpec { class EventSourcedBehaviorInterceptorSpec extends ScalaTestWithActorTestKit(EventSourcedBehaviorTimersSpec.config) - with WordSpecLike { + with WordSpecLike + with LogCapturing { import EventSourcedBehaviorInterceptorSpec._ val pidCounter = new AtomicInteger(0) private def nextPid(): PersistenceId = PersistenceId(s"c${pidCounter.incrementAndGet()})") - import akka.actor.typed.scaladsl.adapter._ - // needed for the classic event filter - private implicit val classicSystem: akka.actor.ActorSystem = system.toClassic - - classicSystem.eventStream.publish(Mute(EventFilter.warning(start = "No default snapshot store", occurrences = 1))) - "EventSourcedBehavior interceptor" must { "be possible to combine with another interceptor" in { @@ -101,10 +93,11 @@ class EventSourcedBehaviorInterceptorSpec val probe = createTestProbe[String]() val pid = nextPid() val ref = spawn(Behaviors.setup[String] { _ => - Behaviors - .withMdc(staticMdc = Map("pid" -> pid), mdcForMessage = (msg: String) => Map("msg" -> msg.toUpperCase())) { - testBehavior(pid, probe.ref) - } + Behaviors.withMdc( + staticMdc = Map("pid" -> pid.toString), + mdcForMessage = (msg: String) => Map("msg" -> msg.toUpperCase())) { + testBehavior(pid, probe.ref) + } }) ref ! "a" diff --git a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorRecoveryTimeoutSpec.scala b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorRecoveryTimeoutSpec.scala index afe397a90f..95d50a00fa 100644 --- a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorRecoveryTimeoutSpec.scala +++ b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorRecoveryTimeoutSpec.scala @@ -17,8 +17,6 @@ import akka.persistence.journal.SteppingInmemJournal import akka.persistence.typed.PersistenceId import akka.persistence.typed.RecoveryFailed import akka.persistence.typed.internal.JournalFailureException -import akka.testkit.EventFilter -import akka.testkit.TestEvent.Mute import com.typesafe.config.Config import com.typesafe.config.ConfigFactory import org.scalatest.WordSpecLike @@ -35,7 +33,6 @@ object EventSourcedBehaviorRecoveryTimeoutSpec { """)) .withFallback(ConfigFactory.parseString(s""" akka.loglevel = INFO - akka.loggers = [akka.testkit.TestEventListener] """)) def testBehavior(persistenceId: PersistenceId, probe: ActorRef[AnyRef]): Behavior[String] = @@ -54,7 +51,8 @@ object EventSourcedBehaviorRecoveryTimeoutSpec { class EventSourcedBehaviorRecoveryTimeoutSpec extends ScalaTestWithActorTestKit(EventSourcedBehaviorRecoveryTimeoutSpec.config) - with WordSpecLike { + with WordSpecLike + with LogCapturing { import EventSourcedBehaviorRecoveryTimeoutSpec._ @@ -65,8 +63,6 @@ class EventSourcedBehaviorRecoveryTimeoutSpec // needed for SteppingInmemJournal.step private implicit val classicSystem: akka.actor.ActorSystem = system.toClassic - classicSystem.eventStream.publish(Mute(EventFilter.warning(start = "No default snapshot store", occurrences = 1))) - "The recovery timeout" must { "fail recovery if timeout is not met when recovering" in { @@ -89,7 +85,9 @@ class EventSourcedBehaviorRecoveryTimeoutSpec // now replay, but don't give the journal any tokens to replay events // so that we cause the timeout to trigger - EventFilter[JournalFailureException](pattern = "Exception during recovery.*Replay timed out", occurrences = 1) + LoggingEventFilter + .error[JournalFailureException] + .withMessageRegex("Exception during recovery.*Replay timed out") .intercept { val replaying = spawn(testBehavior(pid, probe.ref)) diff --git a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorReplySpec.scala b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorReplySpec.scala index 86920fbf8d..3551097fa5 100644 --- a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorReplySpec.scala +++ b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorReplySpec.scala @@ -76,7 +76,8 @@ object EventSourcedBehaviorReplySpec { class EventSourcedBehaviorReplySpec extends ScalaTestWithActorTestKit(EventSourcedBehaviorReplySpec.conf) - with WordSpecLike { + with WordSpecLike + with LogCapturing { import EventSourcedBehaviorReplySpec._ diff --git a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorRetentionSpec.scala b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorRetentionSpec.scala index 3b1691d125..3a8b6bcde9 100644 --- a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorRetentionSpec.scala +++ b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorRetentionSpec.scala @@ -29,8 +29,6 @@ import akka.persistence.typed.SnapshotFailed import akka.persistence.typed.SnapshotMetadata import akka.persistence.typed.SnapshotSelectionCriteria import akka.serialization.jackson.CborSerializable -import akka.testkit.EventFilter -import akka.testkit.TestEvent.Mute import akka.util.unused import com.typesafe.config.Config import com.typesafe.config.ConfigFactory @@ -40,7 +38,6 @@ object EventSourcedBehaviorRetentionSpec { def conf: Config = ConfigFactory.parseString(s""" akka.loglevel = INFO - akka.loggers = [akka.testkit.TestEventListener] # akka.persistence.typed.log-stashing = on akka.persistence.journal.leveldb.dir = "target/typed-persistence-${UUID.randomUUID().toString}" akka.persistence.journal.plugin = "akka.persistence.journal.leveldb" @@ -147,20 +144,14 @@ object EventSourcedBehaviorRetentionSpec { class EventSourcedBehaviorRetentionSpec extends ScalaTestWithActorTestKit(EventSourcedBehaviorRetentionSpec.conf) - with WordSpecLike { + with WordSpecLike + with LogCapturing { import EventSourcedBehaviorRetentionSpec._ - import akka.actor.typed.scaladsl.adapter._ - - // needed for the classic event filter - implicit val actorSystem = system.toClassic val pidCounter = new AtomicInteger(0) private def nextPid(): PersistenceId = PersistenceId(s"c${pidCounter.incrementAndGet()})") - actorSystem.eventStream.publish(Mute(EventFilter.info(pattern = ".*was not delivered.*", occurrences = 100))) - actorSystem.eventStream.publish(Mute(EventFilter.warning(pattern = ".*received dead letter.*", occurrences = 100))) - "EventSourcedBehavior with retention" must { "snapshot every N sequence nrs" in { diff --git a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorSpec.scala b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorSpec.scala index 991367b2ee..d9b90a56fd 100644 --- a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorSpec.scala +++ b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorSpec.scala @@ -42,8 +42,6 @@ import akka.persistence.{ SnapshotMetadata => ClassicSnapshotMetadata } import akka.persistence.{ SnapshotSelectionCriteria => ClassicSnapshotSelectionCriteria } import akka.serialization.jackson.CborSerializable import akka.stream.scaladsl.Sink -import akka.testkit.EventFilter -import akka.testkit.TestEvent.Mute import com.typesafe.config.Config import com.typesafe.config.ConfigFactory import org.scalatest.WordSpecLike @@ -80,7 +78,6 @@ object EventSourcedBehaviorSpec { // also used from PersistentActorTest def conf: Config = ConfigFactory.parseString(s""" akka.loglevel = INFO - akka.loggers = [akka.testkit.TestEventListener] # akka.persistence.typed.log-stashing = on akka.persistence.journal.leveldb.dir = "target/typed-persistence-${UUID.randomUUID().toString}" akka.persistence.journal.plugin = "akka.persistence.journal.leveldb" @@ -283,7 +280,10 @@ object EventSourcedBehaviorSpec { } } -class EventSourcedBehaviorSpec extends ScalaTestWithActorTestKit(EventSourcedBehaviorSpec.conf) with WordSpecLike { +class EventSourcedBehaviorSpec + extends ScalaTestWithActorTestKit(EventSourcedBehaviorSpec.conf) + with WordSpecLike + with LogCapturing { import EventSourcedBehaviorSpec._ import akka.actor.typed.scaladsl.adapter._ @@ -291,15 +291,9 @@ class EventSourcedBehaviorSpec extends ScalaTestWithActorTestKit(EventSourcedBeh val queries: LeveldbReadJournal = PersistenceQuery(system.toClassic).readJournalFor[LeveldbReadJournal](LeveldbReadJournal.Identifier) - // needed for the classic event filter - implicit val actorSystem = system.toClassic - val pidCounter = new AtomicInteger(0) private def nextPid(): PersistenceId = PersistenceId(s"c${pidCounter.incrementAndGet()})") - actorSystem.eventStream.publish(Mute(EventFilter.info(pattern = ".*was not delivered.*", occurrences = 100))) - actorSystem.eventStream.publish(Mute(EventFilter.warning(pattern = ".*received dead letter.*", occurrences = 100))) - "A typed persistent actor" must { "persist an event" in { @@ -505,7 +499,7 @@ class EventSourcedBehaviorSpec extends ScalaTestWithActorTestKit(EventSourcedBeh } "fail after recovery timeout" in { - EventFilter.error(start = "Persistence failure when replaying snapshot", occurrences = 1).intercept { + LoggingEventFilter.error("Persistence failure when replaying snapshot").intercept { val c = spawn( Behaviors.setup[Command](ctx => counter(ctx, nextPid) @@ -531,7 +525,7 @@ class EventSourcedBehaviorSpec extends ScalaTestWithActorTestKit(EventSourcedBeh c ! StopIt probe.expectTerminated(c) - EventFilter[TestException](occurrences = 1).intercept { + LoggingEventFilter.error[TestException].intercept { val c2 = spawn(Behaviors.setup[Command](counter(_, pid))) c2 ! Fail probe.expectTerminated(c2) // should fail @@ -543,14 +537,20 @@ class EventSourcedBehaviorSpec extends ScalaTestWithActorTestKit(EventSourcedBeh PersistenceId(null) } val probe = TestProbe[AnyRef] - EventFilter[ActorInitializationException](start = "persistenceId must not be null", occurrences = 1).intercept { - val ref = spawn(Behaviors.setup[Command](counter(_, persistenceId = PersistenceId(null)))) - probe.expectTerminated(ref) - } - EventFilter[ActorInitializationException](start = "persistenceId must not be null", occurrences = 1).intercept { - val ref = spawn(Behaviors.setup[Command](counter(_, persistenceId = null))) - probe.expectTerminated(ref) - } + LoggingEventFilter + .error[ActorInitializationException] + .withMessageContains("persistenceId must not be null") + .intercept { + val ref = spawn(Behaviors.setup[Command](counter(_, persistenceId = PersistenceId(null)))) + probe.expectTerminated(ref) + } + LoggingEventFilter + .error[ActorInitializationException] + .withMessageContains("persistenceId must not be null") + .intercept { + val ref = spawn(Behaviors.setup[Command](counter(_, persistenceId = null))) + probe.expectTerminated(ref) + } } "fail fast if persistenceId is empty" in { @@ -558,26 +558,27 @@ class EventSourcedBehaviorSpec extends ScalaTestWithActorTestKit(EventSourcedBeh PersistenceId("") } val probe = TestProbe[AnyRef] - EventFilter[ActorInitializationException](start = "persistenceId must not be empty", occurrences = 1).intercept { - val ref = spawn(Behaviors.setup[Command](counter(_, persistenceId = PersistenceId("")))) - probe.expectTerminated(ref) - } + LoggingEventFilter + .error[ActorInitializationException] + .withMessageContains("persistenceId must not be empty") + .intercept { + val ref = spawn(Behaviors.setup[Command](counter(_, persistenceId = PersistenceId("")))) + probe.expectTerminated(ref) + } } "fail fast if default journal plugin is not defined" in { // new ActorSystem without persistence config - val testkit2 = ActorTestKit( - ActorTestKitBase.testNameFromCallStack(), - ConfigFactory.parseString(""" - akka.loggers = [akka.testkit.TestEventListener] - """)) + val testkit2 = ActorTestKit(ActorTestKitBase.testNameFromCallStack(), ConfigFactory.parseString("")) try { - EventFilter[ActorInitializationException](start = "Default journal plugin is not configured", occurrences = 1) + LoggingEventFilter + .error[ActorInitializationException] + .withMessageContains("Default journal plugin is not configured") .intercept { val ref = testkit2.spawn(Behaviors.setup[Command](counter(_, nextPid()))) val probe = testkit2.createTestProbe() probe.expectTerminated(ref) - }(testkit2.system.toClassic) + }(testkit2.system) } finally { testkit2.shutdownTestKit() } @@ -585,19 +586,16 @@ class EventSourcedBehaviorSpec extends ScalaTestWithActorTestKit(EventSourcedBeh "fail fast if given journal plugin is not defined" in { // new ActorSystem without persistence config - val testkit2 = ActorTestKit( - ActorTestKitBase.testNameFromCallStack(), - ConfigFactory.parseString(""" - akka.loggers = [akka.testkit.TestEventListener] - """)) + val testkit2 = ActorTestKit(ActorTestKitBase.testNameFromCallStack(), ConfigFactory.parseString("")) try { - EventFilter[ActorInitializationException]( - start = "Journal plugin [missing] configuration doesn't exist", - occurrences = 1).intercept { - val ref = testkit2.spawn(Behaviors.setup[Command](counter(_, nextPid()).withJournalPluginId("missing"))) - val probe = testkit2.createTestProbe() - probe.expectTerminated(ref) - }(testkit2.system.toClassic) + LoggingEventFilter + .error[ActorInitializationException] + .withMessageContains("Journal plugin [missing] configuration doesn't exist") + .intercept { + val ref = testkit2.spawn(Behaviors.setup[Command](counter(_, nextPid()).withJournalPluginId("missing"))) + val probe = testkit2.createTestProbe() + probe.expectTerminated(ref) + }(testkit2.system) } finally { testkit2.shutdownTestKit() } @@ -608,20 +606,19 @@ class EventSourcedBehaviorSpec extends ScalaTestWithActorTestKit(EventSourcedBeh val testkit2 = ActorTestKit( ActorTestKitBase.testNameFromCallStack(), ConfigFactory.parseString(s""" - akka.loggers = [akka.testkit.TestEventListener] akka.persistence.journal.leveldb.dir = "target/typed-persistence-${UUID.randomUUID().toString}" akka.persistence.journal.plugin = "akka.persistence.journal.leveldb" """)) try { - EventFilter - .warning(start = "No default snapshot store configured", occurrences = 1) + LoggingEventFilter + .warn("No default snapshot store configured") .intercept { val ref = testkit2.spawn(Behaviors.setup[Command](counter(_, nextPid()))) val probe = testkit2.createTestProbe[State]() // verify that it's not terminated ref ! GetValue(probe.ref) probe.expectMessage(State(0, Vector.empty)) - }(testkit2.system.toClassic) + }(testkit2.system) } finally { testkit2.shutdownTestKit() } @@ -632,18 +629,18 @@ class EventSourcedBehaviorSpec extends ScalaTestWithActorTestKit(EventSourcedBeh val testkit2 = ActorTestKit( ActorTestKitBase.testNameFromCallStack(), ConfigFactory.parseString(s""" - akka.loggers = [akka.testkit.TestEventListener] akka.persistence.journal.leveldb.dir = "target/typed-persistence-${UUID.randomUUID().toString}" akka.persistence.journal.plugin = "akka.persistence.journal.leveldb" """)) try { - EventFilter[ActorInitializationException]( - start = "Snapshot store plugin [missing] configuration doesn't exist", - occurrences = 1).intercept { - val ref = testkit2.spawn(Behaviors.setup[Command](counter(_, nextPid()).withSnapshotPluginId("missing"))) - val probe = testkit2.createTestProbe() - probe.expectTerminated(ref) - }(testkit2.system.toClassic) + LoggingEventFilter + .error[ActorInitializationException] + .withMessageContains("Snapshot store plugin [missing] configuration doesn't exist") + .intercept { + val ref = testkit2.spawn(Behaviors.setup[Command](counter(_, nextPid()).withSnapshotPluginId("missing"))) + val probe = testkit2.createTestProbe() + probe.expectTerminated(ref) + }(testkit2.system) } finally { testkit2.shutdownTestKit() } diff --git a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorStashSpec.scala b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorStashSpec.scala index bbc3c6aa01..edf43a60ae 100644 --- a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorStashSpec.scala +++ b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorStashSpec.scala @@ -16,8 +16,10 @@ import akka.actor.testkit.typed.scaladsl._ import akka.actor.typed.ActorRef import akka.actor.typed.Behavior import akka.actor.Dropped +import akka.actor.UnhandledMessage import akka.actor.typed.PostStop import akka.actor.typed.SupervisorStrategy +import akka.actor.typed.eventstream.EventStream import akka.actor.typed.internal.PoisonPill import akka.actor.typed.javadsl.StashOverflowException import akka.actor.typed.scaladsl.Behaviors @@ -25,8 +27,6 @@ import akka.actor.typed.scaladsl.adapter._ import akka.persistence.typed.ExpectingReply import akka.persistence.typed.PersistenceId import akka.persistence.typed.RecoveryCompleted -import akka.testkit.EventFilter -import akka.testkit.TestEvent.Mute import com.typesafe.config.Config import com.typesafe.config.ConfigFactory import org.scalatest.WordSpecLike @@ -34,7 +34,6 @@ import org.scalatest.WordSpecLike object EventSourcedBehaviorStashSpec { def conf: Config = ConfigFactory.parseString(s""" #akka.loglevel = DEBUG - akka.loggers = [akka.testkit.TestEventListener] #akka.persistence.typed.log-stashing = on akka.persistence.journal.plugin = "akka.persistence.journal.inmem" akka.persistence.journal.plugin = "failure-journal" @@ -168,18 +167,14 @@ object EventSourcedBehaviorStashSpec { class EventSourcedBehaviorStashSpec extends ScalaTestWithActorTestKit(EventSourcedBehaviorStashSpec.conf) - with WordSpecLike { + with WordSpecLike + with LogCapturing { import EventSourcedBehaviorStashSpec._ val pidCounter = new AtomicInteger(0) private def nextPid(): PersistenceId = PersistenceId(s"c${pidCounter.incrementAndGet()})") - // Needed for the classic event filter - implicit val classic = system.toClassic - - classic.eventStream.publish(Mute(EventFilter.warning(start = "No default snapshot store", occurrences = 1))) - "A typed persistent actor that is stashing commands" must { "stash and unstash" in { @@ -280,6 +275,8 @@ class EventSourcedBehaviorStashSpec val ackProbe = TestProbe[Ack] val stateProbe = TestProbe[State] val notUsedProbe = TestProbe[NotUsed] + val unhandledProbe = createTestProbe[UnhandledMessage]() + system.eventStream ! EventStream.Subscribe(unhandledProbe.ref) (1 to 100).foreach { n => c ! Increment(s"inc-1-$n", ackProbe.ref) @@ -295,42 +292,42 @@ class EventSourcedBehaviorStashSpec c ! Increment(s"inc-3-$n", ackProbe.ref) } - EventFilter.warning(start = "unhandled message", occurrences = 10).intercept { - c ! GetValue(stateProbe.ref) + c ! GetValue(stateProbe.ref) - (1 to 5).foreach { n => - c ! UpdateValue(s"upd-4-$n", n * 1000, ackProbe.ref) - } - - (1 to 3).foreach { n => - c ! Activate(s"act-5-$n", ackProbe.ref) - } - - (1 to 100).foreach { n => - c ! Increment(s"inc-6-$n", ackProbe.ref) - } - - c ! GetValue(stateProbe.ref) - - (6 to 8).foreach { n => - c ! UpdateValue(s"upd-7-$n", n * 1000, ackProbe.ref) - } - - (1 to 3).foreach { n => - c ! Deactivate(s"deact-8-$n", ackProbe.ref) - } - - (1 to 100).foreach { n => - c ! Increment(s"inc-9-$n", ackProbe.ref) - } - - (1 to 3).foreach { n => - c ! Activate(s"act-10-$n", ackProbe.ref) - } - - c ! GetValue(stateProbe.ref) + (1 to 5).foreach { n => + c ! UpdateValue(s"upd-4-$n", n * 1000, ackProbe.ref) } + (1 to 3).foreach { n => + c ! Activate(s"act-5-$n", ackProbe.ref) + } + + (1 to 100).foreach { n => + c ! Increment(s"inc-6-$n", ackProbe.ref) + } + + c ! GetValue(stateProbe.ref) + + (6 to 8).foreach { n => + c ! UpdateValue(s"upd-7-$n", n * 1000, ackProbe.ref) + } + + (1 to 3).foreach { n => + c ! Deactivate(s"deact-8-$n", ackProbe.ref) + } + + (1 to 100).foreach { n => + c ! Increment(s"inc-9-$n", ackProbe.ref) + } + + (1 to 3).foreach { n => + c ! Activate(s"act-10-$n", ackProbe.ref) + } + + c ! GetValue(stateProbe.ref) + + unhandledProbe.receiveMessages(10) + val value1 = stateProbe.expectMessageType[State](5.seconds).value val value2 = stateProbe.expectMessageType[State](5.seconds).value val value3 = stateProbe.expectMessageType[State](5.seconds).value @@ -547,7 +544,7 @@ class EventSourcedBehaviorStashSpec c ! "start-stashing" val limit = system.settings.config.getInt("akka.persistence.typed.stash-capacity") - EventFilter.warning(start = "Stash buffer is full, dropping message").intercept { + LoggingEventFilter.warn("Stash buffer is full, dropping message").intercept { (0 to limit).foreach { n => c ! s"cmd-$n" // limit triggers overflow } @@ -572,8 +569,6 @@ class EventSourcedBehaviorStashSpec ConfigFactory .parseString("akka.persistence.typed.stash-overflow-strategy=fail") .withFallback(EventSourcedBehaviorStashSpec.conf)) - failStashTestKit.system.toClassic.eventStream - .publish(Mute(EventFilter.warning(start = "No default snapshot store", occurrences = 1))) try { val probe = failStashTestKit.createTestProbe[AnyRef]() val behavior = @@ -591,13 +586,15 @@ class EventSourcedBehaviorStashSpec c ! "ping" probe.expectMessage("pong") - EventFilter[StashOverflowException](occurrences = 1).intercept { - val limit = system.settings.config.getInt("akka.persistence.typed.stash-capacity") - (0 to limit).foreach { n => - c ! s"cmd-$n" // limit triggers overflow - } - probe.expectTerminated(c, 10.seconds) - }(failStashTestKit.system.toClassic) + LoggingEventFilter + .error[StashOverflowException] + .intercept { + val limit = system.settings.config.getInt("akka.persistence.typed.stash-capacity") + (0 to limit).foreach { n => + c ! s"cmd-$n" // limit triggers overflow + } + probe.expectTerminated(c, 10.seconds) + }(failStashTestKit.system) } finally { failStashTestKit.shutdownTestKit() } @@ -624,6 +621,8 @@ class EventSourcedBehaviorStashSpec "stop from PoisonPill after unstashing completed" in { val c = spawn(counter(nextPid())) val ackProbe = TestProbe[Ack] + val unhandledProbe = createTestProbe[UnhandledMessage]() + system.eventStream ! EventStream.Subscribe(unhandledProbe.ref) c ! Increment("1", ackProbe.ref) ackProbe.expectMessage(Ack("1")) @@ -635,20 +634,20 @@ class EventSourcedBehaviorStashSpec c ! Increment("3", ackProbe.ref) c ! Increment("4", ackProbe.ref) - EventFilter.warning(start = "unhandled message", occurrences = 1).intercept { - // start unstashing - c ! Activate("5", ackProbe.ref) - c.toClassic ! PoisonPill - // 6 shouldn't make it, already stopped - c ! Increment("6", ackProbe.ref) + // start unstashing + c ! Activate("5", ackProbe.ref) + c.toClassic ! PoisonPill + // 6 shouldn't make it, already stopped + c ! Increment("6", ackProbe.ref) - ackProbe.expectMessage(Ack("5")) - // not stopped before 3 and 4 were processed - ackProbe.expectMessage(Ack("3")) - ackProbe.expectMessage(Ack("4")) + ackProbe.expectMessage(Ack("5")) + // not stopped before 3 and 4 were processed + ackProbe.expectMessage(Ack("3")) + ackProbe.expectMessage(Ack("4")) - ackProbe.expectTerminated(c) - } + ackProbe.expectTerminated(c) + + unhandledProbe.receiveMessage() // 6 shouldn't make it, already stopped ackProbe.expectNoMessage(100.millis) @@ -666,11 +665,6 @@ class EventSourcedBehaviorStashSpec ackProbe.expectMessage(Ack("2")) ackProbe.expectMessage(Ack("3")) - // silence some dead letters that may, or may not, occur depending on timing - system.toClassic.eventStream.publish(Mute(EventFilter.warning(start = "unhandled message", occurrences = 100))) - system.toClassic.eventStream.publish(Mute(EventFilter.warning(start = "received dead letter", occurrences = 100))) - system.toClassic.eventStream.publish(Mute(EventFilter.info(pattern = ".*was not delivered.*", occurrences = 100))) - val signalProbe = TestProbe[String] val c2 = spawn(counter(pid, Some(signalProbe.ref))) // this PoisonPill will most likely be received in RequestingRecoveryPermit since it's sent immediately diff --git a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorTimersSpec.scala b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorTimersSpec.scala index d83be80e36..1a5b3a8414 100644 --- a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorTimersSpec.scala +++ b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedBehaviorTimersSpec.scala @@ -14,8 +14,6 @@ import akka.actor.typed.ActorRef import akka.actor.typed.Behavior import akka.actor.typed.scaladsl.Behaviors import akka.persistence.typed.PersistenceId -import akka.testkit.EventFilter -import akka.testkit.TestEvent.Mute import com.typesafe.config.Config import com.typesafe.config.ConfigFactory import org.scalatest.WordSpecLike @@ -26,7 +24,6 @@ object EventSourcedBehaviorTimersSpec { def config: Config = ConfigFactory.parseString(s""" akka.loglevel = INFO - akka.loggers = [akka.testkit.TestEventListener] akka.persistence.journal.leveldb.dir = "target/typed-persistence-${UUID.randomUUID().toString}" akka.persistence.journal.plugin = "akka.persistence.journal.leveldb" """) @@ -77,19 +74,14 @@ object EventSourcedBehaviorTimersSpec { class EventSourcedBehaviorTimersSpec extends ScalaTestWithActorTestKit(EventSourcedBehaviorTimersSpec.config) - with WordSpecLike { + with WordSpecLike + with LogCapturing { import EventSourcedBehaviorTimersSpec._ val pidCounter = new AtomicInteger(0) private def nextPid(): PersistenceId = PersistenceId(s"c${pidCounter.incrementAndGet()})") - import akka.actor.typed.scaladsl.adapter._ - // needed for the classic event filter - private implicit val classicSystem: akka.actor.ActorSystem = system.toClassic - - classicSystem.eventStream.publish(Mute(EventFilter.warning(start = "No default snapshot store", occurrences = 1))) - "EventSourcedBehavior withTimers" must { "be able to schedule message" in { diff --git a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedEventAdapterSpec.scala b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedEventAdapterSpec.scala index 83f9a0f7ed..d81aac7ee6 100644 --- a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedEventAdapterSpec.scala +++ b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedEventAdapterSpec.scala @@ -9,6 +9,7 @@ import java.util.concurrent.atomic.AtomicInteger import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.testkit.typed.scaladsl.TestProbe +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.ActorRef import akka.actor.typed.scaladsl.Behaviors import akka.persistence.query.EventEnvelope @@ -20,16 +21,13 @@ import akka.persistence.typed.EventSeq import akka.persistence.typed.PersistenceId import akka.serialization.jackson.CborSerializable import akka.stream.scaladsl.Sink -import akka.testkit.EventFilter import akka.testkit.JavaSerializable -import akka.testkit.TestEvent.Mute import com.typesafe.config.ConfigFactory import org.scalatest.WordSpecLike object EventSourcedEventAdapterSpec { private val conf = ConfigFactory.parseString(s""" - akka.loggers = [akka.testkit.TestEventListener] akka.persistence.journal.leveldb.dir = "target/typed-persistence-${UUID.randomUUID().toString}" akka.persistence.journal.plugin = "akka.persistence.journal.leveldb" """) @@ -84,7 +82,8 @@ object EventSourcedEventAdapterSpec { class EventSourcedEventAdapterSpec extends ScalaTestWithActorTestKit(EventSourcedEventAdapterSpec.conf) - with WordSpecLike { + with WordSpecLike + with LogCapturing { import EventSourcedEventAdapterSpec._ import EventSourcedBehaviorSpec.{ counter, @@ -98,7 +97,6 @@ class EventSourcedEventAdapterSpec } import akka.actor.typed.scaladsl.adapter._ - system.toClassic.eventStream.publish(Mute(EventFilter.warning(start = "No default snapshot store", occurrences = 1))) val pidCounter = new AtomicInteger(0) private def nextPid(): PersistenceId = PersistenceId(s"c${pidCounter.incrementAndGet()})") diff --git a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedSequenceNumberSpec.scala b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedSequenceNumberSpec.scala index 0e2f031e4d..eed8d2a67e 100644 --- a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedSequenceNumberSpec.scala +++ b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedSequenceNumberSpec.scala @@ -4,20 +4,18 @@ package akka.persistence.typed.scaladsl +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.testkit.typed.scaladsl.{ ScalaTestWithActorTestKit, TestProbe } import akka.actor.typed.{ ActorRef, Behavior } import akka.actor.typed.scaladsl.Behaviors import akka.persistence.typed.PersistenceId import akka.persistence.typed.RecoveryCompleted -import akka.testkit.EventFilter -import akka.testkit.TestEvent.Mute import com.typesafe.config.ConfigFactory import org.scalatest.WordSpecLike object EventSourcedSequenceNumberSpec { private val conf = ConfigFactory.parseString(s""" - akka.loggers = [akka.testkit.TestEventListener] akka.persistence.journal.plugin = "akka.persistence.journal.inmem" akka.persistence.journal.inmem.test-serialization = on """) @@ -26,10 +24,8 @@ object EventSourcedSequenceNumberSpec { class EventSourcedSequenceNumberSpec extends ScalaTestWithActorTestKit(EventSourcedSequenceNumberSpec.conf) - with WordSpecLike { - - import akka.actor.typed.scaladsl.adapter._ - system.toClassic.eventStream.publish(Mute(EventFilter.warning(start = "No default snapshot store", occurrences = 1))) + with WordSpecLike + with LogCapturing { private def behavior(pid: PersistenceId, probe: ActorRef[String]): Behavior[String] = Behaviors.setup(ctx => diff --git a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedSnapshotAdapterSpec.scala b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedSnapshotAdapterSpec.scala index a8ac972b42..73705d7e8b 100644 --- a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedSnapshotAdapterSpec.scala +++ b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/EventSourcedSnapshotAdapterSpec.scala @@ -7,6 +7,7 @@ package akka.persistence.typed.scaladsl import java.util.UUID import java.util.concurrent.atomic.AtomicInteger +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.testkit.typed.scaladsl.TestProbe import akka.actor.typed.ActorRef @@ -34,7 +35,8 @@ object EventSourcedSnapshotAdapterSpec { class EventSourcedSnapshotAdapterSpec extends ScalaTestWithActorTestKit(EventSourcedSnapshotAdapterSpec.conf) - with WordSpecLike { + with WordSpecLike + with LogCapturing { import EventSourcedSnapshotAdapterSpec._ import akka.actor.typed.scaladsl.adapter._ diff --git a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/LoggerSourceSpec.scala b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/LoggerSourceSpec.scala index 34796705cb..5e1a396e5a 100644 --- a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/LoggerSourceSpec.scala +++ b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/LoggerSourceSpec.scala @@ -5,29 +5,24 @@ package akka.persistence.typed.scaladsl import java.util.concurrent.atomic.AtomicInteger +import akka.actor.testkit.typed.scaladsl.LoggingEventFilter import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.Behavior import akka.actor.typed.scaladsl.Behaviors -import akka.actor.typed.scaladsl.adapter._ -import akka.event.Logging.LogEvent import akka.persistence.typed.PersistenceId import akka.persistence.typed.RecoveryCompleted import akka.persistence.typed.SnapshotCompleted import akka.persistence.typed.SnapshotFailed -import akka.testkit.EventFilter -import com.typesafe.config.ConfigFactory import org.scalatest.WordSpecLike +import org.slf4j.event.Level // Note that the spec name here is important since there are heuristics in place to avoid names // starting with EventSourcedBehavior class LoggerSourceSpec - extends ScalaTestWithActorTestKit( - ConfigFactory - .parseString("akka.loggers = [akka.testkit.TestEventListener]") - .withFallback(EventSourcedBehaviorSpec.conf)) - with WordSpecLike { - - implicit val classic = system.toClassic // FIXME #24348: eventfilter support in testkit + extends ScalaTestWithActorTestKit(EventSourcedBehaviorSpec.conf) + with WordSpecLike + with LogCapturing { private val pidCounter = new AtomicInteger(0) private def nextPid(): PersistenceId = PersistenceId(s"c${pidCounter.incrementAndGet()})") @@ -53,7 +48,7 @@ class LoggerSourceSpec // one test case leaks to another, the actual log class is what is tested in each individual case "log from setup" in { - EventFilter.info("recovery-completed", occurrences = 1).intercept { + LoggingEventFilter.info("recovery-completed").intercept { eventFilterFor("setting-up-behavior").intercept { spawn(behavior) } @@ -62,7 +57,7 @@ class LoggerSourceSpec } "log from recovery completed" in { - EventFilter.info("setting-up-behavior", occurrences = 1).intercept { + LoggingEventFilter.info("setting-up-behavior").intercept { eventFilterFor("recovery-completed").intercept { spawn(behavior) } @@ -70,16 +65,22 @@ class LoggerSourceSpec } "log from command handler" in { - EventFilter.info(pattern = "(setting-up-behavior|recovery-completed|event-received)", occurrences = 3).intercept { - eventFilterFor("command-received").intercept { - spawn(behavior) ! "cmd" + LoggingEventFilter.empty + .withLogLevel(Level.INFO) + .withMessageRegex("(setting-up-behavior|recovery-completed|event-received)") + .withOccurrences(3) + .intercept { + eventFilterFor("command-received").intercept { + spawn(behavior) ! "cmd" + } } - } } "log from event handler" in { - EventFilter - .info(pattern = "(setting-up-behavior|recovery-completed|command-received)", occurrences = 3) + LoggingEventFilter.empty + .withLogLevel(Level.INFO) + .withMessageRegex("(setting-up-behavior|recovery-completed|command-received)") + .withOccurrences(3) .intercept { eventFilterFor("event-received").intercept { spawn(behavior) ! "cmd" @@ -89,13 +90,8 @@ class LoggerSourceSpec } def eventFilterFor(logMsg: String) = - EventFilter.custom( - { - case l: LogEvent if l.message == logMsg => - if (l.logClass == classOf[LoggerSourceSpec]) true - else fail(s"Unexpected log source: ${l.logClass} for message ${l.message}") - case _ => false - }, - occurrences = 1) + LoggingEventFilter.custom { logEvent => + logEvent.message == logMsg && logEvent.loggerName == classOf[LoggerSourceSpec].getName + } } diff --git a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/NullEmptyStateSpec.scala b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/NullEmptyStateSpec.scala index e7d5e00faf..69bd82f828 100644 --- a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/NullEmptyStateSpec.scala +++ b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/NullEmptyStateSpec.scala @@ -10,27 +10,24 @@ import akka.actor.typed.ActorRef import akka.actor.typed.Behavior import akka.persistence.typed.PersistenceId import akka.persistence.typed.RecoveryCompleted -import akka.testkit.EventFilter -import akka.testkit.TestEvent.Mute import com.typesafe.config.ConfigFactory import org.scalatest.WordSpecLike object NullEmptyStateSpec { private val conf = ConfigFactory.parseString(s""" - akka.loggers = [akka.testkit.TestEventListener] akka.persistence.journal.plugin = "akka.persistence.journal.inmem" akka.persistence.journal.inmem.test-serialization = on """) } -class NullEmptyStateSpec extends ScalaTestWithActorTestKit(NullEmptyStateSpec.conf) with WordSpecLike { +class NullEmptyStateSpec + extends ScalaTestWithActorTestKit(NullEmptyStateSpec.conf) + with WordSpecLike + with LogCapturing { implicit val testSettings = TestKitSettings(system) - import akka.actor.typed.scaladsl.adapter._ - system.toClassic.eventStream.publish(Mute(EventFilter.warning(start = "No default snapshot store", occurrences = 1))) - def primitiveState(persistenceId: PersistenceId, probe: ActorRef[String]): Behavior[String] = EventSourcedBehavior[String, String, String]( persistenceId, diff --git a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/OptionalSnapshotStoreSpec.scala b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/OptionalSnapshotStoreSpec.scala index 26932df94c..7721e969b0 100644 --- a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/OptionalSnapshotStoreSpec.scala +++ b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/OptionalSnapshotStoreSpec.scala @@ -6,13 +6,13 @@ package akka.persistence.typed.scaladsl import java.util.UUID +import akka.actor.testkit.typed.scaladsl.LoggingEventFilter import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.testkit.typed.scaladsl.TestProbe -import akka.actor.typed.scaladsl.adapter.TypedActorSystemOps +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.persistence.typed.PersistenceId import akka.persistence.typed.scaladsl.EventSourcedBehavior.CommandHandler import akka.serialization.jackson.CborSerializable -import akka.testkit.EventFilter import org.scalatest.WordSpecLike object OptionalSnapshotStoreSpec { @@ -42,23 +42,19 @@ object OptionalSnapshotStoreSpec { } class OptionalSnapshotStoreSpec extends ScalaTestWithActorTestKit(s""" - akka.loggers = [akka.testkit.TestEventListener] akka.persistence.publish-plugin-commands = on akka.persistence.journal.plugin = "akka.persistence.journal.inmem" akka.persistence.journal.inmem.test-serialization = on # snapshot store plugin is NOT defined, things should still work akka.persistence.snapshot-store.local.dir = "target/snapshots-${classOf[OptionalSnapshotStoreSpec].getName}/" - """) with WordSpecLike { + """) with WordSpecLike with LogCapturing { import OptionalSnapshotStoreSpec._ - // Needed for the classic event filter - implicit val classic = system.toClassic - "Persistence extension" must { "initialize properly even in absence of configured snapshot store" in { - EventFilter.warning(start = "No default snapshot store configured", occurrences = 1).intercept { + LoggingEventFilter.warn("No default snapshot store configured").intercept { val stateProbe = TestProbe[State]() spawn(persistentBehavior(stateProbe)) stateProbe.expectNoMessage() @@ -66,8 +62,8 @@ class OptionalSnapshotStoreSpec extends ScalaTestWithActorTestKit(s""" } "fail if PersistentActor tries to saveSnapshot without snapshot-store available" in { - EventFilter.error(pattern = ".*No snapshot store configured.*", occurrences = 1).intercept { - EventFilter.warning(pattern = ".*Failed to save snapshot.*", occurrences = 1).intercept { + LoggingEventFilter.error("No snapshot store configured").intercept { + LoggingEventFilter.warn("Failed to save snapshot").intercept { val stateProbe = TestProbe[State]() val persistentActor = spawn(persistentBehavior(stateProbe)) persistentActor ! AnyCommand diff --git a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/PerformanceSpec.scala b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/PerformanceSpec.scala index e54f56194f..f39a82aa11 100644 --- a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/PerformanceSpec.scala +++ b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/PerformanceSpec.scala @@ -11,6 +11,7 @@ import scala.concurrent.duration._ import akka.actor.testkit.typed.TestException import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.testkit.typed.scaladsl.TestProbe +import akka.actor.testkit.typed.scaladsl.LogCapturing import akka.actor.typed.ActorRef import akka.actor.typed.SupervisorStrategy import akka.actor.typed.scaladsl.Behaviors @@ -118,7 +119,7 @@ class PerformanceSpec extends ScalaTestWithActorTestKit(ConfigFactory.parseStrin akka.persistence.snapshot-store.plugin = "akka.persistence.snapshot-store.local" akka.persistence.snapshot-store.local.dir = "target/snapshots-PerformanceSpec/" akka.test.single-expect-default = 10s - """).withFallback(ConfigFactory.parseString(PerformanceSpec.config))) with WordSpecLike { + """).withFallback(ConfigFactory.parseString(PerformanceSpec.config))) with WordSpecLike with LogCapturing { import PerformanceSpec._ diff --git a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/PrimitiveStateSpec.scala b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/PrimitiveStateSpec.scala index ac18b6a923..05fe00c18f 100644 --- a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/PrimitiveStateSpec.scala +++ b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/PrimitiveStateSpec.scala @@ -9,24 +9,21 @@ import akka.actor.typed.ActorRef import akka.actor.typed.Behavior import akka.persistence.typed.PersistenceId import akka.persistence.typed.RecoveryCompleted -import akka.testkit.EventFilter -import akka.testkit.TestEvent.Mute import com.typesafe.config.ConfigFactory import org.scalatest.WordSpecLike object PrimitiveStateSpec { private val conf = ConfigFactory.parseString(s""" - akka.loggers = [akka.testkit.TestEventListener] akka.persistence.journal.plugin = "akka.persistence.journal.inmem" akka.persistence.journal.inmem.test-serialization = on """) } -class PrimitiveStateSpec extends ScalaTestWithActorTestKit(PrimitiveStateSpec.conf) with WordSpecLike { - - import akka.actor.typed.scaladsl.adapter._ - system.toClassic.eventStream.publish(Mute(EventFilter.warning(start = "No default snapshot store", occurrences = 1))) +class PrimitiveStateSpec + extends ScalaTestWithActorTestKit(PrimitiveStateSpec.conf) + with WordSpecLike + with LogCapturing { def primitiveState(persistenceId: PersistenceId, probe: ActorRef[String]): Behavior[Int] = EventSourcedBehavior[Int, Int, Int]( diff --git a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/SnapshotMutableStateSpec.scala b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/SnapshotMutableStateSpec.scala index 9c0039cd25..1b8a21a2a5 100644 --- a/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/SnapshotMutableStateSpec.scala +++ b/akka-persistence-typed/src/test/scala/akka/persistence/typed/scaladsl/SnapshotMutableStateSpec.scala @@ -117,7 +117,10 @@ object SnapshotMutableStateSpec { } -class SnapshotMutableStateSpec extends ScalaTestWithActorTestKit(SnapshotMutableStateSpec.conf) with WordSpecLike { +class SnapshotMutableStateSpec + extends ScalaTestWithActorTestKit(SnapshotMutableStateSpec.conf) + with WordSpecLike + with LogCapturing { import SnapshotMutableStateSpec._ diff --git a/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/BasicPersistentBehaviorCompileOnly.scala b/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/BasicPersistentBehaviorCompileOnly.scala index 925ba8d93a..87d087a081 100644 --- a/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/BasicPersistentBehaviorCompileOnly.scala +++ b/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/BasicPersistentBehaviorCompileOnly.scala @@ -9,6 +9,7 @@ import scala.concurrent.duration._ import akka.actor.typed.Behavior import akka.actor.typed.SupervisorStrategy import akka.actor.typed.scaladsl.Behaviors +import akka.actor.typed.scaladsl.LoggerOps import akka.persistence.typed.DeleteEventsFailed import akka.persistence.typed.DeleteSnapshotsFailed import akka.persistence.typed.EventAdapter @@ -123,7 +124,7 @@ object BasicPersistentBehaviorCompileOnly { val debugAlwaysSnapshot: Behavior[Command] = Behaviors.setup { context => samplePersistentBehavior.snapshotWhen((state, _, _) => { - context.log.info("Snapshot actor {} => state: {}", context.self.path.name, state) + context.log.info2("Snapshot actor {} => state: {}", context.self.path.name, state) true }) } diff --git a/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/PersistentFsmToTypedMigrationSpec.scala b/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/PersistentFsmToTypedMigrationSpec.scala index 6ffb2d2b78..3128d81141 100644 --- a/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/PersistentFsmToTypedMigrationSpec.scala +++ b/akka-persistence-typed/src/test/scala/docs/akka/persistence/typed/PersistentFsmToTypedMigrationSpec.scala @@ -32,9 +32,10 @@ import akka.persistence.typed.SnapshotAdapter import com.typesafe.config.ConfigFactory import org.scalatest.WordSpec import org.scalatest.concurrent.ScalaFutures - import scala.concurrent.duration._ +import akka.actor.testkit.typed.scaladsl.LogCapturing + object PersistentFsmToTypedMigrationSpec { val config = ConfigFactory.parseString(s""" akka.actor.allow-java-serialization = on @@ -205,7 +206,7 @@ object ShoppingCartBehavior { } -class PersistentFsmToTypedMigrationSpec extends WordSpec with ScalaFutures { +class PersistentFsmToTypedMigrationSpec extends WordSpec with ScalaFutures with LogCapturing { import akka.persistence.fsm.PersistentFSMSpec._ diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 26b1da8176..5074a28701 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -97,6 +97,8 @@ object Dependencies { val protobufRuntime = "com.google.protobuf" % "protobuf-java" % "3.9.0" + val logback = "ch.qos.logback" % "logback-classic" % "1.2.3" // EPL 1.0 + object Docs { val sprayJson = "io.spray" %% "spray-json" % "1.3.5" % "test" val gson = "com.google.code.gson" % "gson" % "2.8.5" % "test" @@ -107,7 +109,7 @@ object Dependencies { val commonsIo = "commons-io" % "commons-io" % "2.6" % "test" // ApacheV2 val commonsCodec = "commons-codec" % "commons-codec" % "1.11" % "test" // ApacheV2 val junit = "junit" % "junit" % junitVersion % "test" // Common Public License 1.0 - val logback = "ch.qos.logback" % "logback-classic" % "1.2.3" % "test" // EPL 1.0 / LGPL 2.1 + val logback = Compile.logback % "test" // EPL 1.0 val mockito = "org.mockito" % "mockito-core" % "2.19.1" % "test" // MIT // changing the scalatest dependency must be reflected in akka-docs/rst/dev/multi-jvm-testing.rst val scalatest = Def.setting { "org.scalatest" %% "scalatest" % scalaTestVersion.value % "test" } // ApacheV2 @@ -154,6 +156,8 @@ object Dependencies { val scalatest = Def.setting { "org.scalatest" %% "scalatest" % scalaTestVersion.value % "optional;provided;test" } // ApacheV2 + val logback = Compile.logback % "optional;provided;test" // EPL 1.0 + } } @@ -170,7 +174,7 @@ object Dependencies { val coordination = l ++= Seq(Test.junit, Test.scalatest.value) - val testkit = l ++= Seq(Test.logback, Test.junit, Test.scalatest.value) ++ Test.metricsAll + val testkit = l ++= Seq(Test.junit, Test.scalatest.value) ++ Test.metricsAll val actorTests = l ++= Seq( Test.junit, @@ -183,7 +187,7 @@ object Dependencies { Provided.activation // dockerClient needs javax.activation.DataSource in JDK 11+ ) - val actorTestkitTyped = l ++= Seq(Provided.junit, Provided.scalatest.value) + val actorTestkitTyped = l ++= Seq(Provided.logback, Provided.junit, Provided.scalatest.value) val remoteDependencies = Seq(netty, aeronDriver, aeronClient) val remoteOptionalDependencies = remoteDependencies.map(_ % "optional")