Logging for typed #23326

This commit is contained in:
Johan Andrén 2018-01-31 10:43:56 +01:00 committed by GitHub
parent 08b0d34a4c
commit ac64a7bb98
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 1449 additions and 307 deletions

View file

@ -0,0 +1,74 @@
/**
* Copyright (C) 2009-2018 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.actor.typed.javadsl;
import akka.actor.typed.Behavior;
import akka.event.Logging;
import akka.japi.pf.PFBuilder;
import akka.testkit.CustomEventFilter;
import akka.testkit.typed.TestKit;
import com.typesafe.config.ConfigFactory;
import org.junit.AfterClass;
import org.junit.Test;
import org.scalatest.junit.JUnitSuite;
import scala.concurrent.duration.FiniteDuration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class ActorLoggingTest extends JUnitSuite {
private final static TestKit testKit = new TestKit("ActorLoggingTest",
ConfigFactory.parseString(
"akka.loglevel = INFO\n" +
"akka.loggers = [\"akka.testkit.TestEventListener\"]"
));
@AfterClass
public static void tearDown() {
testKit.shutdown();
}
interface Protocol {
String getTransactionId();
}
static class Message implements Protocol {
public final String transactionId;
public Message(String transactionId) {
this.transactionId = transactionId;
}
public String getTransactionId() {
return transactionId;
}
}
@Test
public void loggingProvidesMDC() {
Behavior<Protocol> behavior = Behaviors.withMdc(
Protocol.class,
(msg) -> {
Map<String, Object> mdc = new HashMap<>();
mdc.put("txId", msg.getTransactionId());
return mdc;
},
Behaviors.immutable(Protocol.class)
.onMessage(Message.class, (ctx, msg) -> {
ctx.getLog().info(msg.toString());
return Behaviors.same();
}).build()
);
CustomEventFilter eventFilter = new CustomEventFilter(new PFBuilder<Logging.LogEvent, Object>()
.match(Logging.LogEvent.class, (event) ->
event.getMDC().containsKey("txId"))
.build(),
1);
testKit.spawn(behavior);
eventFilter.awaitDone(FiniteDuration.create(3, TimeUnit.SECONDS));
}
}

View file

@ -308,14 +308,15 @@ abstract class ActorContextSpec extends TypedAkkaSpec {
import ActorContextSpec._
val config = ConfigFactory.parseString(
"""|akka {
| loglevel = WARNING
| actor.debug {
| lifecycle = off
| autoreceive = off
| }
| typed.loggers = ["akka.testkit.typed.TestEventListener"]
|}""".stripMargin)
"""
akka {
loglevel = WARNING
loggers = ["akka.testkit.TestEventListener"]
actor.debug {
lifecycle = off
autoreceive = off
}
}""")
implicit lazy val system: ActorSystem[GuardianCommand] =
ActorSystem(guardian(), AkkaSpec.getCallerName(classOf[ActorContextSpec]), config = Some(config withFallback AkkaSpec.testConf))
@ -379,7 +380,8 @@ abstract class ActorContextSpec extends TypedAkkaSpec {
pattern: String = null,
occurrences: Int = Int.MaxValue)(implicit system: ActorSystem[GuardianCommand]): EventFilter = {
val filter = EventFilter(message, source, start, pattern, occurrences)
system.eventStream.publish(Mute(filter))
import scaladsl.adapter._
system.toUntyped.eventStream.publish(Mute(filter))
filter
}

View file

@ -0,0 +1,228 @@
/**
* Copyright (C) 2009-2018 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.actor.typed.scaladsl
import akka.actor.typed.{ LogMarker, TestException, TypedAkkaSpec, scaladsl }
import akka.testkit.EventFilter
import akka.testkit.typed.TestKit
import com.typesafe.config.ConfigFactory
import akka.actor.typed.scaladsl.adapter._
import akka.event.Logging
import akka.event.Logging.{ LogEventWithCause, LogEventWithMarker }
import scala.util.control.NoStackTrace
class ActorLoggingSpec extends TestKit(ConfigFactory.parseString(
"""
akka.loglevel = DEBUG
akka.loggers = ["akka.testkit.TestEventListener"]
""")) with TypedAkkaSpec {
val marker = LogMarker("marker")
val cause = new TestException("böö")
implicit val untyped = system.toUntyped
"Logging in a typed actor" must {
"be conveniently available from the ctx" in {
val actor = EventFilter.info("Started", source = "akka://ActorLoggingSpec/user/the-actor", occurrences = 1).intercept {
spawn(Behaviors.deferred[String] { ctx
ctx.log.info("Started")
Behaviors.immutable { (ctx, msg)
ctx.log.info("got message {}", msg)
Behaviors.same
}
}, "the-actor")
}
EventFilter.info("got message Hello", source = "akka://ActorLoggingSpec/user/the-actor", occurrences = 1).intercept {
actor ! "Hello"
}
}
"pass markers to the log" in {
EventFilter.custom({
case event: LogEventWithMarker if event.marker == marker true
}, occurrences = 5).intercept(
spawn(Behaviors.deferred[Any] { ctx
ctx.log.debug(marker, "whatever")
ctx.log.info(marker, "whatever")
ctx.log.warning(marker, "whatever")
ctx.log.error(marker, "whatever")
ctx.log.error(marker, cause, "whatever")
Behaviors.stopped
})
)
}
"pass cause with warning" in {
EventFilter.custom({
case event: LogEventWithCause if event.cause == cause true
}, occurrences = 2).intercept(
spawn(Behaviors.deferred[Any] { ctx
ctx.log.warning(cause, "whatever")
ctx.log.warning(marker, cause, "whatever")
Behaviors.stopped
})
)
}
"provide a whole bunch of logging overloads" in {
// 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 = 72).intercept {
spawn(Behaviors.deferred[String] { ctx
ctx.log.debug("message")
ctx.log.debug("{}", "arg1")
ctx.log.debug("{} {}", "arg1", "arg2")
ctx.log.debug("{} {} {}", "arg1", "arg2", "arg3")
ctx.log.debug("{} {} {} {}", "arg1", "arg2", "arg3", "arg4")
ctx.log.debug("{} {} {} {} {}", Array("arg1", "arg2", "arg3", "arg4", "arg5"))
ctx.log.debug(marker, "message")
ctx.log.debug(marker, "{}", "arg1")
ctx.log.debug(marker, "{} {}", "arg1", "arg2")
ctx.log.debug(marker, "{} {} {}", "arg1", "arg2", "arg3")
ctx.log.debug(marker, "{} {} {} {}", "arg1", "arg2", "arg3", "arg4")
ctx.log.debug(marker, "{} {} {} {} {}", Array("arg1", "arg2", "arg3", "arg4", "arg5"))
ctx.log.info("message")
ctx.log.info("{}", "arg1")
ctx.log.info("{} {}", "arg1", "arg2")
ctx.log.info("{} {} {}", "arg1", "arg2", "arg3")
ctx.log.info("{} {} {} {}", "arg1", "arg2", "arg3", "arg4")
ctx.log.info("{} {} {} {} {}", Array("arg1", "arg2", "arg3", "arg4", "arg5"))
ctx.log.info(marker, "message")
ctx.log.info(marker, "{}", "arg1")
ctx.log.info(marker, "{} {}", "arg1", "arg2")
ctx.log.info(marker, "{} {} {}", "arg1", "arg2", "arg3")
ctx.log.info(marker, "{} {} {} {}", "arg1", "arg2", "arg3", "arg4")
ctx.log.info(marker, "{} {} {} {} {}", Array("arg1", "arg2", "arg3", "arg4", "arg5"))
ctx.log.warning("message")
ctx.log.warning("{}", "arg1")
ctx.log.warning("{} {}", "arg1", "arg2")
ctx.log.warning("{} {} {}", "arg1", "arg2", "arg3")
ctx.log.warning("{} {} {} {}", "arg1", "arg2", "arg3", "arg4")
ctx.log.warning("{} {} {} {} {}", Array("arg1", "arg2", "arg3", "arg4", "arg5"))
ctx.log.warning(marker, "message")
ctx.log.warning(marker, "{}", "arg1")
ctx.log.warning(marker, "{} {}", "arg1", "arg2")
ctx.log.warning(marker, "{} {} {}", "arg1", "arg2", "arg3")
ctx.log.warning(marker, "{} {} {} {}", "arg1", "arg2", "arg3", "arg4")
ctx.log.warning(marker, "{} {} {} {} {}", Array("arg1", "arg2", "arg3", "arg4", "arg5"))
ctx.log.warning(cause, "message")
ctx.log.warning(cause, "{}", "arg1")
ctx.log.warning(cause, "{} {}", "arg1", "arg2")
ctx.log.warning(cause, "{} {} {}", "arg1", "arg2", "arg3")
ctx.log.warning(cause, "{} {} {} {}", "arg1", "arg2", "arg3", "arg4")
ctx.log.warning(cause, "{} {} {} {} {}", Array("arg1", "arg2", "arg3", "arg4", "arg5"))
ctx.log.warning(marker, cause, "message")
ctx.log.warning(marker, cause, "{}", "arg1")
ctx.log.warning(marker, cause, "{} {}", "arg1", "arg2")
ctx.log.warning(marker, cause, "{} {} {}", "arg1", "arg2", "arg3")
ctx.log.warning(marker, cause, "{} {} {} {}", "arg1", "arg2", "arg3", "arg4")
ctx.log.warning(marker, cause, "{} {} {} {} {}", Array("arg1", "arg2", "arg3", "arg4", "arg5"))
ctx.log.error("message")
ctx.log.error("{}", "arg1")
ctx.log.error("{} {}", "arg1", "arg2")
ctx.log.error("{} {} {}", "arg1", "arg2", "arg3")
ctx.log.error("{} {} {} {}", "arg1", "arg2", "arg3", "arg4")
ctx.log.error("{} {} {} {} {}", Array("arg1", "arg2", "arg3", "arg4", "arg5"))
ctx.log.error(marker, "message")
ctx.log.error(marker, "{}", "arg1")
ctx.log.error(marker, "{} {}", "arg1", "arg2")
ctx.log.error(marker, "{} {} {}", "arg1", "arg2", "arg3")
ctx.log.error(marker, "{} {} {} {}", "arg1", "arg2", "arg3", "arg4")
ctx.log.error(marker, "{} {} {} {} {}", Array("arg1", "arg2", "arg3", "arg4", "arg5"))
ctx.log.error(cause, "message")
ctx.log.error(cause, "{}", "arg1")
ctx.log.error(cause, "{} {}", "arg1", "arg2")
ctx.log.error(cause, "{} {} {}", "arg1", "arg2", "arg3")
ctx.log.error(cause, "{} {} {} {}", "arg1", "arg2", "arg3", "arg4")
ctx.log.error(cause, "{} {} {} {} {}", Array("arg1", "arg2", "arg3", "arg4", "arg5"))
ctx.log.error(marker, cause, "message")
ctx.log.error(marker, cause, "{}", "arg1")
ctx.log.error(marker, cause, "{} {}", "arg1", "arg2")
ctx.log.error(marker, cause, "{} {} {}", "arg1", "arg2", "arg3")
ctx.log.error(marker, cause, "{} {} {} {}", "arg1", "arg2", "arg3", "arg4")
ctx.log.error(marker, cause, "{} {} {} {} {}", Array("arg1", "arg2", "arg3", "arg4", "arg5"))
Behaviors.stopped
})
}
}
}
trait Protocol {
def transactionId: Long
}
case class Message(transactionId: Long, message: String) extends Protocol
"Logging with MDC for a typed actor" must {
"provide the MDC values in the log" in {
val behaviors = Behaviors.withMdc[Protocol](
{ (msg)
if (msg.transactionId == 1)
Map(
"txId" -> msg.transactionId,
"first" -> true
)
else Map("txId" -> msg.transactionId)
},
Behaviors.deferred { ctx
ctx.log.info("Starting")
Behaviors.immutable { (ctx, msg)
ctx.log.info("Got message!")
Behaviors.same
}
}
)
// 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).intercept {
spawn(behaviors)
}
// mdc on message
EventFilter.custom({
case logEvent if logEvent.level == Logging.InfoLevel
logEvent.message should ===("Got message!")
logEvent.mdc should ===(Map("txId" -> 1L, "first" -> true))
true
case other system.log.error(s"Unexpected log event: {}", other); false
}, occurrences = 1).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("txId" -> 2L))
true
case other system.log.error(s"Unexpected log event: {}", other); false
}, occurrences = 1).intercept {
ref ! Message(2, "second")
}
}
}
}

View file

@ -19,9 +19,6 @@ object StashSpec {
final case class GetProcessed(replyTo: ActorRef[Vector[String]]) extends Command
final case class GetStashSize(replyTo: ActorRef[Int]) extends Command
// FIXME replace when we get the logging in place, #23326
def log(ctx: ActorContext[_]): LoggingAdapter = ctx.system.log
def active(processed: Vector[String]): Behavior[Command] =
Behaviors.immutable { (ctx, cmd)
cmd match {
@ -57,13 +54,13 @@ object StashSpec {
case UnstashAll
buffer.unstashAll(ctx, active(processed))
case Unstash
log(ctx).debug(s"Unstash ${buffer.size}")
ctx.log.debug(s"Unstash ${buffer.size}")
if (buffer.isEmpty)
active(processed)
else {
ctx.self ! Unstash // continue unstashing until buffer is empty
val numberOfMessages = 2
log(ctx).debug(s"Unstash $numberOfMessages of ${buffer.size}, starting with ${buffer.head}")
ctx.log.debug(s"Unstash $numberOfMessages of ${buffer.size}, starting with ${buffer.head}")
buffer.unstash(ctx, unstashing(buffer.drop(numberOfMessages), processed), numberOfMessages, Unstashed)
}
case Stash
@ -77,28 +74,28 @@ object StashSpec {
Behaviors.immutable { (ctx, cmd)
cmd match {
case Unstashed(msg: Msg)
log(ctx).debug(s"unstashed $msg")
ctx.log.debug(s"unstashed $msg")
unstashing(buffer, processed :+ msg.s)
case Unstashed(GetProcessed(replyTo))
log(ctx).debug(s"unstashed GetProcessed")
ctx.log.debug(s"unstashed GetProcessed")
replyTo ! processed
Behaviors.same
case msg: Msg
log(ctx).debug(s"got $msg in unstashing")
ctx.log.debug(s"got $msg in unstashing")
unstashing(buffer :+ msg, processed)
case get: GetProcessed
log(ctx).debug(s"got GetProcessed in unstashing")
ctx.log.debug(s"got GetProcessed in unstashing")
unstashing(buffer :+ get, processed)
case Stash
stashing(buffer, processed)
case Unstash
if (buffer.isEmpty) {
log(ctx).debug(s"unstashing done")
ctx.log.debug(s"unstashing done")
active(processed)
} else {
ctx.self ! Unstash // continue unstashing until buffer is empty
val numberOfMessages = 2
log(ctx).debug(s"Unstash $numberOfMessages of ${buffer.size}, starting with ${buffer.head}")
ctx.log.debug(s"Unstash $numberOfMessages of ${buffer.size}, starting with ${buffer.head}")
buffer.unstash(ctx, unstashing(buffer.drop(numberOfMessages), processed), numberOfMessages, Unstashed)
}
case GetStashSize(replyTo)
@ -147,15 +144,15 @@ object StashSpec {
} else {
ctx.self ! Unstash // continue unstashing until buffer is empty
val numberOfMessages = 2
log(ctx).debug(s"Unstash $numberOfMessages of ${buffer.size}, starting with ${buffer.head}")
ctx.log.debug(s"Unstash $numberOfMessages of ${buffer.size}, starting with ${buffer.head}")
buffer.unstash(ctx, this, numberOfMessages, Unstashed)
}
case Unstashed(msg: Msg)
log(ctx).debug(s"unstashed $msg")
ctx.log.debug(s"unstashed $msg")
processed :+= msg.s
this
case Unstashed(GetProcessed(replyTo))
log(ctx).debug(s"unstashed GetProcessed")
ctx.log.debug(s"unstashed GetProcessed")
replyTo ! processed
Behaviors.same
case _: Unstashed

View file

@ -4,14 +4,11 @@
package docs.akka.typed
//#imports
import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.{ ActorSystem, Logger, PostStop }
import scala.concurrent.Await
import scala.concurrent.duration._
import akka.actor.typed.ActorSystem
import akka.actor.typed.PostStop
import akka.actor.typed.scaladsl.Behaviors
import akka.event.LoggingAdapter
//#imports
import akka.actor.typed.TypedAkkaSpecWithShutdown
@ -27,16 +24,16 @@ object GracefulStopDocSpec {
final case object GracefulShutdown extends JobControlLanguage
// Predefined cleanup operation
def cleanup(log: LoggingAdapter): Unit = log.info("Cleaning up!")
def cleanup(log: Logger): Unit = log.info("Cleaning up!")
val mcpa = Behaviors.immutable[JobControlLanguage] { (ctx, msg)
msg match {
case SpawnJob(jobName)
ctx.system.log.info("Spawning job {}!", jobName)
ctx.log.info("Spawning job {}!", jobName)
ctx.spawn(Job.job(jobName), name = jobName)
Behaviors.same
case GracefulShutdown
ctx.system.log.info("Initiating graceful shutdown...")
ctx.log.info("Initiating graceful shutdown...")
// perform graceful stop, executing cleanup before final system termination
// behavior executing cleanup is passed as a parameter to Actor.stopped
Behaviors.stopped {
@ -49,7 +46,7 @@ object GracefulStopDocSpec {
}
}.onSignal {
case (ctx, PostStop)
ctx.system.log.info("MCPA stopped")
ctx.log.info("MCPA stopped")
Behaviors.same
}
}
@ -62,7 +59,7 @@ object GracefulStopDocSpec {
def job(name: String) = Behaviors.onSignal[JobControlLanguage] {
case (ctx, PostStop)
ctx.system.log.info("Worker {} stopped", name)
ctx.log.info("Worker {} stopped", name)
Behaviors.same
}
}

View file

@ -4,7 +4,6 @@
package akka.actor.typed
import akka.annotation.InternalApi
import akka.event.typed.EventStream
import akka.{ actor a }
import scala.annotation.unchecked.uncheckedVariance

View file

@ -19,7 +19,6 @@ import java.util.Optional
import akka.actor.BootstrapSetup
import akka.actor.typed.receptionist.Receptionist
import akka.event.typed.EventStream
/**
* An ActorSystem is home to a hierarchy of Actors. It is created using
@ -48,20 +47,12 @@ abstract class ActorSystem[-T] extends ActorRef[T] with Extensions {
def logConfiguration(): Unit
/**
* A reference to this systems logFilter, which filters usage of the [[log]]
* [[akka.event.LoggingAdapter]] such that only permissible messages are sent
* via the [[eventStream]]. The default implementation will just test that
* the message is suitable for the current log level.
*/
def logFilter: e.LoggingFilter
/**
* A [[akka.event.LoggingAdapter]] that can be used to emit log messages
* A [[akka.actor.typed.Logger]] that can be used to emit log messages
* without specifying a more detailed source. Typically it is desirable to
* construct a dedicated LoggingAdapter within each Actor from that Actors
* [[ActorRef]] in order to identify the log messages.
* use the dedicated `Logger` available from each Actors [[ActorContext]]
* as that ties the log entries to the actor.
*/
def log: e.LoggingAdapter
def log: Logger
/**
* Start-up time in milliseconds since the epoch.
@ -94,11 +85,6 @@ abstract class ActorSystem[-T] extends ActorRef[T] with Extensions {
*/
def scheduler: a.Scheduler
/**
* Main event bus of this actor system, used for example for logging.
*/
def eventStream: EventStream
/**
* Facilities for lookup up thread-pools from configuration.
*/

View file

@ -0,0 +1,559 @@
/**
* Copyright (C) 2009-2018 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.actor.typed
import akka.annotation.{ DoNotInherit, InternalApi }
/**
* A log marker is an additional metadata tag supported by some logging backends to identify "special" log events.
* In the Akka internal actors for example the "SECURITY" marker is used for warnings related to security.
*
* Not for user extension, create instances using factory methods
*/
@DoNotInherit
sealed trait LogMarker {
def name: String
}
/**
* Factories for log markers
*/
object LogMarker {
/**
* INTERNAL API
*/
@InternalApi
private final class LogMarkerImpl(name: String) extends akka.event.LogMarker(name) with LogMarker
/**
* Scala API: Create a new log marker with the given name
*/
def apply(name: String): LogMarker = new LogMarkerImpl(name)
/**
* Scala API: Create a new log marker with the given name
*/
def create(name: String): LogMarker = apply(name)
}
/**
* Logging API provided inside of actors through the actor context.
*
* All log-level methods support simple interpolation templates with up to four
* arguments placed by using <code>{}</code> within the template (first string
* argument):
*
* {{{
* ctx.log.error(exception, "Exception while processing {} in state {}", msg, state)
* }}}
*
* More than four arguments can be defined by using an `Array` with the method with
* one argument parameter.
*
* *Rationale for an Akka-specific logging API:*
* Provided rather than a specific logging library logging API to not enforce a specific logging library on users but
* still providing a convenient, performant, asynchronous and testable logging solution. Additionally it allows unified
* logging for both user implemented actors and built in Akka actors where the actual logging backend can be selected
* by the user. This logging facade is also used by Akka internally, without having to depend on specific logging frameworks.
*
* The [[Logger]] of an actor is tied to the actor path and should not be shared with other threads outside of the actor.
*
* Not for user extension
*/
@DoNotInherit
abstract class Logger private[akka] () {
/**
* Whether error logging is enabled on the actor system level, may not represent the setting all the way to the
* logger implementation, but when it does it allows avoiding unnecessary resource usage for log entries that
* will not actually end up in any logger output.
*/
def isErrorEnabled: Boolean
/**
* Whether error logging is enabled on the actor system level, may not represent the setting all the way to the
* logger implementation, but when it does it allows avoiding unnecessary resource usage for log entries that
* will not actually end up in any logger output.
*/
def isWarningEnabled: Boolean
/**
* Whether info logging is enabled on the actor system level, may not represent the setting all the way to the
* logger implementation, but when it does it allows avoiding unnecessary resource usage for log entries that
* will not actually end up in any logger output.
*/
def isInfoEnabled: Boolean
/**
* Whether debug logging is enabled on the actor system level, may not represent the setting all the way to the
* logger implementation, but when it does it allows avoiding unnecessary resource usage for log entries that
* will not actually end up in any logger output.
*/
def isDebugEnabled: Boolean
// message only error logging
/**
* Log message at error level, without providing the exception that caused the error.
*
* @see [[Logger]]
*/
def error(message: String): Unit
/**
* Message template with 1 replacement argument.
*
* If `arg1` is an `Array` it will be expanded into replacement arguments, which is useful when
* there are more than four arguments.
*
* @see [[Logger]]
*/
def error(template: String, arg1: Any): Unit
/**
* Message template with 2 replacement arguments.
*
* @see [[Logger]]
*/
def error(template: String, arg1: Any, arg2: Any): Unit
/**
* Message template with 3 replacement arguments.
*
* @see [[Logger]]
*/
def error(template: String, arg1: Any, arg2: Any, arg3: Any): Unit
/**
* Message template with 4 replacement arguments. For more parameters see the single replacement version of this method.
*
* @see [[Logger]]
*/
def error(template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit
// exception error logging
/**
* Log message at error level, including the exception that caused the error.
*
* @see [[Logger]]
*/
def error(cause: Throwable, message: String): Unit
/**
* Message template with 1 replacement argument.
*
* If `arg1` is an `Array` it will be expanded into replacement arguments, which is useful when
* there are more than four arguments.
*
* @see [[Logger]]
*/
def error(cause: Throwable, template: String, arg1: Any): Unit
/**
* Message template with 2 replacement arguments.
*
* @see [[Logger]]
*/
def error(cause: Throwable, template: String, arg1: Any, arg2: Any): Unit
/**
* Message template with 3 replacement arguments.
*
* @see [[Logger]]
*/
def error(cause: Throwable, template: String, arg1: Any, arg2: Any, arg3: Any): Unit
/**
* Message template with 4 replacement arguments. For more parameters see the single replacement version of this method.
*
* @see [[Logger]]
*/
def error(cause: Throwable, template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit
// marker error logging
/**
* Log message at error level, including the exception that caused the error.
*
* @see [[Logger]]
*/
def error(marker: LogMarker, cause: Throwable, message: String): Unit
/**
* Message template with 1 replacement argument.
*
* If `arg1` is an `Array` it will be expanded into replacement arguments, which is useful when
* there are more than four arguments.
*
* @see [[Logger]]
*/
def error(marker: LogMarker, cause: Throwable, template: String, arg1: Any): Unit
/**
* Message template with 2 replacement arguments.
*
* @see [[Logger]]
*/
def error(marker: LogMarker, cause: Throwable, template: String, arg1: Any, arg2: Any): Unit
/**
* Message template with 3 replacement arguments.
*
* @see [[Logger]]
*/
def error(marker: LogMarker, cause: Throwable, template: String, arg1: Any, arg2: Any, arg3: Any): Unit
/**
* Message template with 4 replacement arguments. For more parameters see the single replacement version of this method.
*
* @see [[Logger]]
*/
def error(marker: LogMarker, cause: Throwable, template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit
/**
* Log message at error level, without providing the exception that caused the error.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*
* @see [[Logger]]
*/
def error(marker: LogMarker, message: String): Unit
/**
* Message template with 1 replacement argument.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*
* If `arg1` is an `Array` it will be expanded into replacement arguments, which is useful when
* there are more than four arguments.
*
* @see [[Logger]]
*/
def error(marker: LogMarker, template: String, arg1: Any): Unit
/**
* Message template with 2 replacement arguments.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*
* @see [[Logger]]
*/
def error(marker: LogMarker, template: String, arg1: Any, arg2: Any): Unit
/**
* Message template with 3 replacement arguments.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*
* @see [[Logger]]
*/
def error(marker: LogMarker, template: String, arg1: Any, arg2: Any, arg3: Any): Unit
/**
* Message template with 4 replacement arguments. For more parameters see the single replacement version of this method.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*
* @see [[Logger]]
*/
def error(marker: LogMarker, template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit
// message only warning logging
/**
* Log message at warning level.
*/
def warning(message: String): Unit
/**
* Message template with 1 replacement argument.
*
* If `arg1` is an `Array` it will be expanded into replacement arguments, which is useful when
* there are more than four arguments.
*
* @see [[Logger]]
*/
def warning(template: String, arg1: Any): Unit
/**
* Message template with 2 replacement arguments.
*
* @see [[Logger]]
*/
def warning(template: String, arg1: Any, arg2: Any): Unit
/**
* Message template with 3 replacement arguments.
*
* @see [[Logger]]
*/
def warning(template: String, arg1: Any, arg2: Any, arg3: Any): Unit
/**
* Message template with 4 replacement arguments. For more parameters see the single replacement version of this method.
*
* @see [[Logger]]
*/
def warning(template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit
/**
* Log message at warning level.
*/
def warning(cause: Throwable, message: String): Unit
/**
* Message template with 1 replacement argument.
*
* If `arg1` is an `Array` it will be expanded into replacement arguments, which is useful when
* there are more than four arguments.
*
* @see [[Logger]]
*/
def warning(cause: Throwable, template: String, arg1: Any): Unit
/**
* Message template with 2 replacement arguments.
* @see [[Logger]]
*/
def warning(cause: Throwable, template: String, arg1: Any, arg2: Any): Unit
/**
* Message template with 3 replacement arguments.
* @see [[Logger]]
*/
def warning(cause: Throwable, template: String, arg1: Any, arg2: Any, arg3: Any): Unit
/**
* Message template with 4 replacement arguments. For more parameters see the single replacement version of this method.
* @see [[Logger]]
*/
def warning(cause: Throwable, template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit
// marker warning logging
/**
* Log message at warning level.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*/
def warning(marker: LogMarker, message: String): Unit
/**
* Message template with 1 replacement argument.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*
* If `arg1` is an `Array` it will be expanded into replacement arguments, which is useful when
* there are more than four arguments.
*
* @see [[Logger]]
*/
def warning(marker: LogMarker, template: String, arg1: Any): Unit
/**
* Message template with 2 replacement arguments.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*
* @see [[Logger]]
*/
def warning(marker: LogMarker, template: String, arg1: Any, arg2: Any): Unit
/**
* Message template with 3 replacement arguments.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*
* @see [[Logger]]
*/
def warning(marker: LogMarker, template: String, arg1: Any, arg2: Any, arg3: Any): Unit
/**
* Message template with 4 replacement arguments. For more parameters see the single replacement version of this method.
*
* @see [[Logger]]
*/
def warning(marker: LogMarker, template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit
/**
* Log message at warning level.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
* @see [[Logger]]
*/
def warning(marker: LogMarker, cause: Throwable, message: String): Unit
/**
* Message template with 1 replacement argument.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*
* If `arg1` is an `Array` it will be expanded into replacement arguments, which is useful when
* there are more than four arguments.
*
* @see [[Logger]]
*/
def warning(marker: LogMarker, cause: Throwable, template: String, arg1: Any): Unit
/**
* Message template with 2 replacement arguments.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*
* @see [[Logger]]
*/
def warning(marker: LogMarker, cause: Throwable, template: String, arg1: Any, arg2: Any): Unit
/**
* Message template with 3 replacement arguments.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*
* @see [[Logger]]
*/
def warning(marker: LogMarker, cause: Throwable, template: String, arg1: Any, arg2: Any, arg3: Any): Unit
/**
* Message template with 4 replacement arguments. For more parameters see the single replacement version of this method.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*
* @see [[Logger]]
*/
def warning(marker: LogMarker, cause: Throwable, template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit
// message only info logging
/**
* Log message at info level.
*
* @see [[Logger]]
*/
def info(message: String): Unit
/**
* Message template with 1 replacement argument.
*
* If `arg1` is an `Array` it will be expanded into replacement arguments, which is useful when
* there are more than four arguments.
*
* @see [[Logger]]
*/
def info(template: String, arg1: Any): Unit
/**
* Message template with 2 replacement arguments.
*
* @see [[Logger]]
*/
def info(template: String, arg1: Any, arg2: Any): Unit
/**
* Message template with 3 replacement arguments.
*
* @see [[Logger]]
*/
def info(template: String, arg1: Any, arg2: Any, arg3: Any): Unit
/**
* Message template with 4 replacement arguments. For more parameters see the single replacement version of this method.
*
* @see [[Logger]]
*/
def info(template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit
// marker info logging
/**
* Log message at info level.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*
* @see [[Logger]]
*/
def info(marker: LogMarker, message: String): Unit
/**
* Message template with 1 replacement argument.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*
* If `arg1` is an `Array` it will be expanded into replacement arguments, which is useful when
* there are more than four arguments.
*
* @see [[Logger]]
*/
def info(marker: LogMarker, template: String, arg1: Any): Unit
/**
* Message template with 2 replacement arguments.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*
* @see [[Logger]]
*/
def info(marker: LogMarker, template: String, arg1: Any, arg2: Any): Unit
/**
* Message template with 3 replacement arguments.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*
* @see [[Logger]]
*/
def info(marker: LogMarker, template: String, arg1: Any, arg2: Any, arg3: Any): Unit
/**
* Message template with 4 replacement arguments. For more parameters see the single replacement version of this method.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*
* @see [[Logger]]
*/
def info(marker: LogMarker, template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit
// message only debug logging
/**
* Log message at debug level.
*
* @see [[Logger]]
*/
def debug(message: String): Unit
/**
* Message template with 1 replacement argument.
*
* If `arg1` is an `Array` it will be expanded into replacement arguments, which is useful when
* there are more than four arguments.
*
* @see [[Logger]]
*/
def debug(template: String, arg1: Any): Unit
/**
* Message template with 2 replacement arguments.
*
* @see [[Logger]]
*/
def debug(template: String, arg1: Any, arg2: Any): Unit
/**
* Message template with 3 replacement arguments.
*
* @see [[Logger]]
*/
def debug(template: String, arg1: Any, arg2: Any, arg3: Any): Unit
/**
* Message template with 4 replacement arguments. For more parameters see the single replacement version of this method.
*
* @see [[Logger]]
*/
def debug(template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit
// marker debug logging
/**
* Log message at debug level.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*
* @see [[Logger]]
*/
def debug(marker: LogMarker, message: String): Unit
/**
* Message template with 1 replacement argument.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*
* If `arg1` is an `Array` it will be expanded into replacement arguments, which is useful when
* there are more than four arguments.
*
* @see [[Logger]]
*/
def debug(marker: LogMarker, template: String, arg1: Any): Unit
/**
* Message template with 2 replacement arguments.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*
* @see [[Logger]]
*/
def debug(marker: LogMarker, template: String, arg1: Any, arg2: Any): Unit
/**
* Message template with 3 replacement arguments.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*
* @see [[Logger]]
*/
def debug(marker: LogMarker, template: String, arg1: Any, arg2: Any, arg3: Any): Unit
/**
* Message template with 4 replacement arguments. For more parameters see the single replacement version of this method.
*
* The marker argument can be picked up by various logging frameworks such as slf4j to mark this log statement as "special".
*
* @see [[Logger]]
*/
def debug(marker: LogMarker, template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit
}

View file

@ -18,6 +18,7 @@ import scala.util.Try
import akka.annotation.InternalApi
import akka.util.OptionVal
import akka.event.LoggingAdapter
import akka.util.Timeout
/**
@ -58,6 +59,8 @@ import akka.util.Timeout
override def getSystem: akka.actor.typed.ActorSystem[Void] =
system.asInstanceOf[ActorSystem[Void]]
override def getLog: Logger = log
override def spawn[U](behavior: akka.actor.typed.Behavior[U], name: String): akka.actor.typed.ActorRef[U] =
spawn(behavior, name, Props.empty)

View file

@ -0,0 +1,43 @@
/**
* Copyright (C) 2009-2018 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.actor.typed.internal
import akka.actor.typed.Behavior
import akka.actor.typed.internal.adapter.LoggerAdapterImpl
import akka.annotation.InternalApi
import scala.reflect.ClassTag
/**
* INTERNAL API
*
* Regular logging is supported directly on the ActorContext, but more advanced logging needs are covered here
*/
@InternalApi object LoggingBehaviorImpl {
/**
* MDC logging support
*
* @param mdcForMessage Is invoked before each message is handled, allowing to setup MDC, MDC is cleared after
* each message processing by the inner behavior is done.
* @param behavior The actual behavior handling the messages, the MDC is used for the log entries logged through
* `ActorContext.log`
*/
def withMdc[T: ClassTag](mdcForMessage: T Map[String, Any], behavior: Behavior[T]): Behavior[T] =
BehaviorImpl.intercept[T, T](
beforeMessage = { (ctx, msg)
ctx.log.asInstanceOf[LoggerAdapterImpl].mdc = mdcForMessage(msg)
msg
},
beforeSignal = (_, _) true,
afterMessage = { (ctx, _, behavior)
ctx.log.asInstanceOf[LoggerAdapterImpl].mdc = Map.empty
behavior
},
afterSignal = (_, _, behavior) behavior,
behavior = behavior,
toStringPrefix = "withMdc"
)
}

View file

@ -70,8 +70,7 @@ import akka.actor.typed.scaladsl.Behaviors
protected def restart(ctx: ActorContext[T], initialBehavior: Behavior[T], startedBehavior: Behavior[T]): Supervisor[T, Thr] = {
try Behavior.interpretSignal(startedBehavior, ctx, PreRestart) catch {
case NonFatal(ex) publish(ctx, Logging.Error(ex, ctx.asScala.self.path.toString, behavior.getClass,
"failure during PreRestart"))
case NonFatal(ex) ctx.asScala.log.error(ex, "failure during PreRestart")
}
// no need to canonicalize, it's done in the calling methods
wrap(Supervisor.initialUndefer(ctx, initialBehavior), afterException = true)
@ -96,11 +95,8 @@ import akka.actor.typed.scaladsl.Behaviors
protected def log(ctx: ActorContext[T], ex: Thr): Unit = {
if (loggingEnabled)
publish(ctx, Logging.Error(ex, ctx.asScala.self.toString, behavior.getClass, ex.getMessage))
ctx.asScala.log.error(ex, ex.getMessage)
}
protected final def publish(ctx: ActorContext[T], e: Logging.LogEvent): Unit =
try ctx.asScala.system.eventStream.publish(e) catch { case NonFatal(_) }
}
/**
@ -228,7 +224,8 @@ import akka.actor.typed.scaladsl.Behaviors
override def receiveSignal(ctx: ActorContext[Any], signal: Signal): Behavior[Any] = {
if (blackhole) {
ctx.asScala.system.eventStream.publish(Dropped(signal, ctx.asScala.self))
import scaladsl.adapter._
ctx.asScala.system.toUntyped.eventStream.publish(Dropped(signal, ctx.asScala.self))
Behavior.same
} else
super.receiveSignal(ctx, signal)
@ -249,7 +246,8 @@ import akka.actor.typed.scaladsl.Behaviors
Behavior.same
case _
if (blackhole) {
ctx.asScala.system.eventStream.publish(Dropped(msg, ctx.asScala.self))
import scaladsl.adapter._
ctx.asScala.system.toUntyped.eventStream.publish(Dropped(msg, ctx.asScala.self))
Behavior.same
} else
super.receiveMessage(ctx, msg)
@ -261,8 +259,7 @@ import akka.actor.typed.scaladsl.Behaviors
log(ctx, ex)
// actual restart happens after the scheduled backoff delay
try Behavior.interpretSignal(behavior, ctx, PreRestart) catch {
case NonFatal(ex2) publish(ctx, Logging.Error(ex2, ctx.asScala.self.path.toString, behavior.getClass,
"failure during PreRestart"))
case NonFatal(ex2) ctx.asScala.log.error(ex2, "failure during PreRestart")
}
val restartDelay = calculateDelay(restartCount, strategy.minBackoff, strategy.maxBackoff, strategy.randomFactor)
ctx.asScala.schedule(restartDelay, ctx.asScala.self, ScheduledRestart)

View file

@ -41,8 +41,6 @@ import scala.reflect.ClassTag
extends scaladsl.TimerScheduler[T] with javadsl.TimerScheduler[T] {
import TimerSchedulerImpl._
// FIXME change to a class specific logger, see issue #21219
private val log = ctx.system.log
private var timers: Map[Any, Timer[T]] = Map.empty
private val timerGen = Iterator from 1
@ -71,7 +69,7 @@ import scala.reflect.ClassTag
}(ExecutionContexts.sameThreadExecutionContext)
val nextTimer = Timer(key, msg, repeat, nextGen, task)
log.debug("Start timer [{}] with generation [{}]", key, nextGen)
ctx.log.debug("Start timer [{}] with generation [{}]", key, nextGen)
timers = timers.updated(key, nextTimer)
}
@ -86,13 +84,13 @@ import scala.reflect.ClassTag
}
private def cancelTimer(timer: Timer[T]): Unit = {
log.debug("Cancel timer [{}] with generation [{}]", timer.key, timer.generation)
ctx.log.debug("Cancel timer [{}] with generation [{}]", timer.key, timer.generation)
timer.task.cancel()
timers -= timer.key
}
override def cancelAll(): Unit = {
log.debug("Cancel all timers")
ctx.log.debug("Cancel all timers")
timers.valuesIterator.foreach { timer
timer.task.cancel()
}
@ -103,12 +101,12 @@ import scala.reflect.ClassTag
timers.get(timerMsg.key) match {
case None
// it was from canceled timer that was already enqueued in mailbox
log.debug("Received timer [{}] that has been removed, discarding", timerMsg.key)
ctx.log.debug("Received timer [{}] that has been removed, discarding", timerMsg.key)
null.asInstanceOf[T] // message should be ignored
case Some(t)
if (timerMsg.owner ne this) {
// after restart, it was from an old instance that was enqueued in mailbox before canceled
log.debug("Received timer [{}] from old restarted instance, discarding", timerMsg.key)
ctx.log.debug("Received timer [{}] from old restarted instance, discarding", timerMsg.key)
null.asInstanceOf[T] // message should be ignored
} else if (timerMsg.generation == t.generation) {
// valid timer
@ -117,7 +115,7 @@ import scala.reflect.ClassTag
t.msg
} else {
// it was from an old timer that was enqueued in mailbox before canceled
log.debug(
ctx.log.debug(
"Received timer [{}] from from old generation [{}], expected generation [{}], discarding",
timerMsg.key, timerMsg.generation, t.generation)
null.asInstanceOf[T] // message should be ignored

View file

@ -5,20 +5,25 @@ package akka.actor.typed
package internal
package adapter
import akka.actor.ExtendedActorSystem
import akka.actor.typed.Behavior.UntypedBehavior
import akka.annotation.InternalApi
import akka.util.OptionVal
import akka.{ ConfigurationException, actor a }
import scala.concurrent.duration._
import scala.concurrent.ExecutionContextExecutor
import akka.annotation.InternalApi
import akka.actor.typed.Behavior.UntypedBehavior
import scala.concurrent.duration._
/**
* INTERNAL API. Wrapping an [[akka.actor.ActorContext]] as an [[ActorContext]].
*/
@InternalApi private[akka] class ActorContextAdapter[T](val untyped: a.ActorContext) extends ActorContextImpl[T] {
@InternalApi private[akka] final class ActorContextAdapter[T](val untyped: a.ActorContext) extends ActorContextImpl[T] {
import ActorRefAdapter.toUntyped
// lazily initialized
private var actorLogger: OptionVal[Logger] = OptionVal.None
override def self = ActorRefAdapter(untyped.self)
override val system = ActorSystemAdapter(untyped.system)
override def mailboxCapacity = 1 << 29 // FIXME
@ -65,6 +70,20 @@ import akka.actor.typed.Behavior.UntypedBehavior
val ref = cell.addFunctionRef((_, msg) untyped.self ! AdaptMessage[U, T](msg.asInstanceOf[U], f), _name)
ActorRefAdapter[U](ref)
}
override def log: Logger = {
actorLogger match {
case OptionVal.Some(logger) logger
case OptionVal.None
import scala.language.existentials
val logSource = self.path.toString
val logClass = classOf[Behavior[_]] // FIXME figure out a better class somehow
val system = untyped.system.asInstanceOf[ExtendedActorSystem]
val logger = new LoggerAdapterImpl(system.eventStream, logClass, logSource, system.logFilter)
actorLogger = OptionVal.Some(logger)
logger
}
}
}
/**

View file

@ -17,7 +17,6 @@ import akka.util.Timeout
import scala.concurrent.Future
import akka.annotation.InternalApi
import akka.event.typed.EventStream
import scala.compat.java8.FutureConverters
@ -57,11 +56,9 @@ import scala.compat.java8.FutureConverters
override def shutdown(): Unit = () // there was no shutdown in untyped Akka
}
override def dynamicAccess: a.DynamicAccess = untyped.dynamicAccess
override def eventStream: EventStream = new EventStreamAdapter(untyped.eventStream)
implicit override def executionContext: scala.concurrent.ExecutionContextExecutor = untyped.dispatcher
override def log: akka.event.LoggingAdapter = untyped.log
override val log: Logger = new LoggerAdapterImpl(untyped.eventStream, getClass, name, untyped.logFilter)
override def logConfiguration(): Unit = untyped.logConfiguration()
override def logFilter: akka.event.LoggingFilter = untyped.logFilter
override def name: String = untyped.name
override def scheduler: akka.actor.Scheduler = untyped.scheduler
override def settings: Settings = new Settings(untyped.settings)

View file

@ -1,43 +0,0 @@
/**
* Copyright (C) 2016-2018 Lightbend Inc. <http://www.lightbend.com/>
*/
package akka.actor.typed
package internal
package adapter
import akka.{ event e }
import akka.annotation.InternalApi
import akka.event.typed.EventStream
/**
* INTERNAL API
*/
@InternalApi private[typed] class EventStreamAdapter(untyped: e.EventStream) extends EventStream {
def logLevel: e.Logging.LogLevel = untyped.logLevel
def publish[T](event: T): Unit = untyped.publish(event.asInstanceOf[AnyRef])
def setLogLevel(loglevel: e.Logging.LogLevel): Unit = untyped.setLogLevel(loglevel)
def subscribe[T](subscriber: ActorRef[T], to: Class[T]): Boolean =
subscriber match {
case adapter: ActorRefAdapter[_] untyped.subscribe(adapter.untyped, to)
case _
throw new UnsupportedOperationException("Cannot subscribe native typed ActorRef")
}
def unsubscribe[T](subscriber: ActorRef[T]): Unit =
subscriber match {
case adapter: ActorRefAdapter[_] untyped.unsubscribe(adapter.untyped)
case _
throw new UnsupportedOperationException("Cannot unsubscribe native typed ActorRef")
}
def unsubscribe[T](subscriber: ActorRef[T], from: Class[T]): Boolean =
subscriber match {
case adapter: ActorRefAdapter[_] untyped.unsubscribe(adapter.untyped, from)
case _
throw new UnsupportedOperationException("Cannot unsubscribe native typed ActorRef")
}
}

View file

@ -0,0 +1,347 @@
/**
* Copyright (C) 2009-2018 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.actor.typed.internal.adapter
import akka.actor.typed.{ LogMarker, Logger }
import akka.annotation.InternalApi
import akka.event.Logging.{ Debug, Error, Info, Warning }
import akka.event.{ LoggingBus, LoggingFilter, LogMarker UntypedLM }
import akka.util.OptionVal
/**
* INTERNAL API
*/
@InternalApi
private[akka] class LoggerAdapterImpl(bus: LoggingBus, logClass: Class[_], logSource: String, loggingFilter: LoggingFilter) extends Logger {
private[akka] var mdc: Map[String, Any] = Map.empty
override def isErrorEnabled = loggingFilter.isErrorEnabled(logClass, logSource)
override def isWarningEnabled = loggingFilter.isWarningEnabled(logClass, logSource)
override def isInfoEnabled = loggingFilter.isInfoEnabled(logClass, logSource)
override def isDebugEnabled = loggingFilter.isDebugEnabled(logClass, logSource)
override def error(message: String): Unit = {
if (isErrorEnabled) notifyError(message, OptionVal.None, OptionVal.None)
}
override def error(template: String, arg1: Any): Unit = {
if (isErrorEnabled) notifyError(format(template, arg1), OptionVal.None, OptionVal.None)
}
override def error(template: String, arg1: Any, arg2: Any): Unit = {
if (isErrorEnabled) notifyError(format(template, arg1, arg2), OptionVal.None, OptionVal.None)
}
override def error(template: String, arg1: Any, arg2: Any, arg3: Any): Unit = {
if (isErrorEnabled) notifyError(format(template, arg1, arg2, arg3), OptionVal.None, OptionVal.None)
}
override def error(template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit = {
if (isErrorEnabled) notifyError(format(template, arg1, arg2, arg3, arg4), OptionVal.None, OptionVal.None)
}
override def error(cause: Throwable, message: String): Unit = {
if (isErrorEnabled) notifyError(message, OptionVal.Some(cause), OptionVal.None)
}
override def error(cause: Throwable, template: String, arg1: Any): Unit = {
if (isErrorEnabled) notifyError(format(template, arg1), OptionVal.Some(cause), OptionVal.None)
}
override def error(cause: Throwable, template: String, arg1: Any, arg2: Any): Unit = {
if (isErrorEnabled) notifyError(format(template, arg1, arg2), OptionVal.Some(cause), OptionVal.None)
}
override def error(cause: Throwable, template: String, arg1: Any, arg2: Any, arg3: Any): Unit = {
if (isErrorEnabled) notifyError(format(template, arg1, arg2, arg3), OptionVal.Some(cause), OptionVal.None)
}
override def error(cause: Throwable, template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit = {
if (isErrorEnabled) notifyError(format(template, arg1, arg2, arg3, arg4), OptionVal.Some(cause), OptionVal.None)
}
override def error(marker: LogMarker, cause: Throwable, message: String): Unit = {
if (isErrorEnabled) notifyError(message, OptionVal.Some(cause), OptionVal.Some(marker))
}
override def error(marker: LogMarker, cause: Throwable, template: String, arg1: Any): Unit = {
if (isErrorEnabled) notifyError(format(template, arg1), OptionVal.Some(cause), OptionVal.Some(marker))
}
override def error(marker: LogMarker, cause: Throwable, template: String, arg1: Any, arg2: Any): Unit = {
if (isErrorEnabled) notifyError(format(template, arg1, arg2), OptionVal.Some(cause), OptionVal.Some(marker))
}
override def error(marker: LogMarker, cause: Throwable, template: String, arg1: Any, arg2: Any, arg3: Any): Unit = {
if (isErrorEnabled) notifyError(format(template, arg1, arg2, arg3), OptionVal.Some(cause), OptionVal.Some(marker))
}
override def error(marker: LogMarker, cause: Throwable, template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit = {
if (isErrorEnabled) notifyError(format(template, arg1, arg2, arg3, arg4), OptionVal.Some(cause), OptionVal.Some(marker))
}
override def error(marker: LogMarker, message: String): Unit = {
if (isErrorEnabled) notifyError(message, OptionVal.None, OptionVal.Some(marker))
}
override def error(marker: LogMarker, template: String, arg1: Any): Unit = {
if (isErrorEnabled) notifyError(format(template, arg1), OptionVal.None, OptionVal.Some(marker))
}
override def error(marker: LogMarker, template: String, arg1: Any, arg2: Any): Unit = {
if (isErrorEnabled) notifyError(format(template, arg1, arg2), OptionVal.None, OptionVal.Some(marker))
}
override def error(marker: LogMarker, template: String, arg1: Any, arg2: Any, arg3: Any): Unit = {
if (isErrorEnabled) notifyError(format(template, arg1, arg2, arg3), OptionVal.None, OptionVal.Some(marker))
}
override def error(marker: LogMarker, template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit = {
if (isErrorEnabled) notifyError(format(template, arg1, arg2, arg3, arg4), OptionVal.None, OptionVal.Some(marker))
}
override def warning(message: String): Unit = {
if (isWarningEnabled) notifyWarning(message, OptionVal.None, OptionVal.None)
}
override def warning(template: String, arg1: Any): Unit = {
if (isWarningEnabled) notifyWarning(format(template, arg1), OptionVal.None, OptionVal.None)
}
override def warning(template: String, arg1: Any, arg2: Any): Unit = {
if (isWarningEnabled) notifyWarning(format(template, arg1, arg2), OptionVal.None, OptionVal.None)
}
override def warning(template: String, arg1: Any, arg2: Any, arg3: Any): Unit = {
if (isWarningEnabled) notifyWarning(format(template, arg1, arg2, arg3), OptionVal.None, OptionVal.None)
}
override def warning(template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit = {
if (isWarningEnabled) notifyWarning(format(template, arg1, arg2, arg3, arg4), OptionVal.None, OptionVal.None)
}
override def warning(cause: Throwable, message: String): Unit = {
if (isWarningEnabled) notifyWarning(message, OptionVal.None, OptionVal.Some(cause))
}
override def warning(cause: Throwable, template: String, arg1: Any): Unit = {
if (isWarningEnabled) notifyWarning(format(template, arg1), OptionVal.None, OptionVal.Some(cause))
}
override def warning(cause: Throwable, template: String, arg1: Any, arg2: Any): Unit = {
if (isWarningEnabled) notifyWarning(format(template, arg1, arg2), OptionVal.None, OptionVal.Some(cause))
}
override def warning(cause: Throwable, template: String, arg1: Any, arg2: Any, arg3: Any): Unit = {
if (isWarningEnabled) notifyWarning(format(template, arg1, arg2, arg3), OptionVal.None, OptionVal.Some(cause))
}
override def warning(cause: Throwable, template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit = {
if (isWarningEnabled) notifyWarning(format(template, arg1, arg2, arg3, arg4), OptionVal.None, OptionVal.Some(cause))
}
override def warning(marker: LogMarker, cause: Throwable, template: String, arg1: Any): Unit = {
if (isWarningEnabled) notifyWarning(format(template, arg1), OptionVal.Some(marker), OptionVal.Some(cause))
}
override def warning(marker: LogMarker, cause: Throwable, template: String, arg1: Any, arg2: Any): Unit = {
if (isWarningEnabled) notifyWarning(format(template, arg1, arg2), OptionVal.Some(marker), OptionVal.Some(cause))
}
override def warning(marker: LogMarker, cause: Throwable, template: String, arg1: Any, arg2: Any, arg3: Any): Unit = {
if (isWarningEnabled) notifyWarning(format(template, arg1, arg2, arg3), OptionVal.Some(marker), OptionVal.Some(cause))
}
override def warning(marker: LogMarker, cause: Throwable, template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit = {
if (isWarningEnabled) notifyWarning(format(template, arg1, arg2, arg3, arg4), OptionVal.Some(marker), OptionVal.Some(cause))
}
override def warning(marker: LogMarker, cause: Throwable, message: String): Unit = {
if (isWarningEnabled) notifyWarning(message, OptionVal.Some(marker), OptionVal.Some(cause))
}
override def warning(marker: LogMarker, message: String): Unit = {
if (isWarningEnabled) notifyWarning(message, OptionVal.Some(marker), OptionVal.None)
}
override def warning(marker: LogMarker, template: String, arg1: Any): Unit = {
if (isWarningEnabled) notifyWarning(format(template, arg1), OptionVal.Some(marker), OptionVal.None)
}
override def warning(marker: LogMarker, template: String, arg1: Any, arg2: Any): Unit = {
if (isWarningEnabled) notifyWarning(format(template, arg1, arg2), OptionVal.Some(marker), OptionVal.None)
}
override def warning(marker: LogMarker, template: String, arg1: Any, arg2: Any, arg3: Any): Unit = {
if (isWarningEnabled) notifyWarning(format(template, arg1, arg2, arg3), OptionVal.Some(marker), OptionVal.None)
}
override def warning(marker: LogMarker, template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit = {
if (isWarningEnabled) notifyWarning(format(template, arg1, arg2, arg3, arg4), OptionVal.Some(marker), OptionVal.None)
}
override def info(message: String): Unit = {
if (isInfoEnabled) notifyInfo(message, OptionVal.None)
}
override def info(template: String, arg1: Any): Unit = {
if (isInfoEnabled) notifyInfo(format(template, arg1), OptionVal.None)
}
override def info(template: String, arg1: Any, arg2: Any): Unit = {
if (isInfoEnabled) notifyInfo(format(template, arg1, arg2), OptionVal.None)
}
override def info(template: String, arg1: Any, arg2: Any, arg3: Any): Unit = {
if (isInfoEnabled) notifyInfo(format(template, arg1, arg2, arg3), OptionVal.None)
}
override def info(template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit = {
if (isInfoEnabled) notifyInfo(format(template, arg1, arg2, arg3, arg4), OptionVal.None)
}
override def info(marker: LogMarker, message: String): Unit = {
if (isInfoEnabled) notifyInfo(message, OptionVal.Some(marker))
}
override def info(marker: LogMarker, template: String, arg1: Any): Unit = {
if (isInfoEnabled) notifyInfo(format(template, arg1), OptionVal.Some(marker))
}
override def info(marker: LogMarker, template: String, arg1: Any, arg2: Any): Unit = {
if (isInfoEnabled) notifyInfo(format(template, arg1, arg2), OptionVal.Some(marker))
}
override def info(marker: LogMarker, template: String, arg1: Any, arg2: Any, arg3: Any): Unit = {
if (isInfoEnabled) notifyInfo(format(template, arg1, arg2, arg3), OptionVal.Some(marker))
}
override def info(marker: LogMarker, template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit = {
if (isInfoEnabled) notifyInfo(format(template, arg1, arg2, arg3, arg4), OptionVal.Some(marker))
}
override def debug(message: String): Unit = {
if (isDebugEnabled) notifyDebug(message, OptionVal.None)
}
override def debug(template: String, arg1: Any): Unit = {
if (isDebugEnabled) notifyDebug(format(template, arg1), OptionVal.None)
}
override def debug(template: String, arg1: Any, arg2: Any): Unit = {
if (isDebugEnabled) notifyDebug(format(template, arg1, arg2), OptionVal.None)
}
override def debug(template: String, arg1: Any, arg2: Any, arg3: Any): Unit = {
if (isDebugEnabled) notifyDebug(format(template, arg1, arg2, arg3), OptionVal.None)
}
override def debug(template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit = {
if (isDebugEnabled) notifyDebug(format(template, arg1, arg2, arg3, arg4), OptionVal.None)
}
override def debug(marker: LogMarker, message: String): Unit = {
if (isDebugEnabled) notifyDebug(message, OptionVal.Some(marker))
}
override def debug(marker: LogMarker, template: String, arg1: Any): Unit = {
if (isDebugEnabled) notifyDebug(format(template, arg1), OptionVal.Some(marker))
}
override def debug(marker: LogMarker, template: String, arg1: Any, arg2: Any): Unit = {
if (isDebugEnabled) notifyDebug(format(template, arg1, arg2), OptionVal.Some(marker))
}
override def debug(marker: LogMarker, template: String, arg1: Any, arg2: Any, arg3: Any): Unit = {
if (isDebugEnabled) notifyDebug(format(template, arg1, arg2, arg3), OptionVal.Some(marker))
}
override def debug(marker: LogMarker, template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): Unit = {
if (isDebugEnabled) notifyDebug(format(template, arg1, arg2, arg3, arg4), OptionVal.Some(marker))
}
protected def notifyError(message: String, cause: OptionVal[Throwable], marker: OptionVal[LogMarker]): Unit = {
val error = cause match {
case OptionVal.Some(cause)
marker match {
case OptionVal.Some(m) Error(cause, logSource, logClass, message, mdc, m.asInstanceOf[UntypedLM])
case OptionVal.None Error(cause, logSource, logClass, message, mdc)
}
case OptionVal.None
marker match {
case OptionVal.Some(m) Error(logSource, logClass, message, mdc, m.asInstanceOf[UntypedLM])
case OptionVal.None Error(logSource, logClass, message, mdc)
}
}
bus.publish(error)
}
@Deprecated
@deprecated("Use the 3 argument version instead", since = "2.5.10")
protected def notifyWarning(message: String, marker: OptionVal[LogMarker]): Unit =
notifyWarning(message, marker, OptionVal.None)
protected def notifyWarning(message: String, marker: OptionVal[LogMarker], cause: OptionVal[Throwable]): Unit = {
val warning =
if (cause.isDefined) Warning(cause.get, logSource, logClass, message, mdc, marker.orNull.asInstanceOf[UntypedLM])
else marker match {
case OptionVal.Some(m) Warning(logSource, logClass, message, mdc, m.asInstanceOf[UntypedLM])
case OptionVal.None Warning(logSource, logClass, message, mdc)
}
bus.publish(warning)
}
protected def notifyInfo(message: String, marker: OptionVal[LogMarker]): Unit = {
val info = marker match {
case OptionVal.Some(m) Info(logSource, logClass, message, mdc, m.asInstanceOf[UntypedLM])
case OptionVal.None Info(logSource, logClass, message, mdc)
}
bus.publish(info)
}
protected def notifyDebug(message: String, marker: OptionVal[LogMarker]): Unit = {
val debug = marker match {
case OptionVal.Some(m) Debug(logSource, logClass, message, mdc, m.asInstanceOf[UntypedLM])
case OptionVal.None Debug(logSource, logClass, message, mdc)
}
bus.publish(debug)
}
/**
* If `arg` is an `Array` it will be expanded into replacement arguments, which is useful when
* there are more than four arguments.
*/
private def format(t: String, arg1: Any): String = arg1 match {
case a: Array[_] if !a.getClass.getComponentType.isPrimitive formatArray(t, a: _*)
case a: Array[_] formatArray(t, (a map (_.asInstanceOf[AnyRef]): _*))
case x formatArray(t, x)
}
private def format(t: String, arg1: Any, arg2: Any): String = formatArray(t, arg1, arg2)
private def format(t: String, arg1: Any, arg2: Any, arg3: Any): String = formatArray(t, arg1, arg2, arg3)
private def format(t: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any): String = formatArray(t, arg1, arg2, arg3, arg4)
private def formatArray(t: String, arg: Any*): String = {
val sb = new java.lang.StringBuilder(64)
var p = 0
var startIndex = 0
while (p < arg.length) {
val index = t.indexOf("{}", startIndex)
if (index == -1) {
sb.append(t.substring(startIndex, t.length))
.append(" WARNING arguments left: ")
.append(arg.length - p)
p = arg.length
startIndex = t.length
} else {
sb.append(t.substring(startIndex, index))
.append(arg(p))
startIndex = index + 2
p += 1
}
}
sb.append(t.substring(startIndex, t.length)).toString
}
}

View file

@ -118,7 +118,7 @@ private[akka] object ReceptionistImpl extends ReceptionistBehaviorProvider {
immutable[AllCommands] { (ctx, msg)
msg match {
case Register(key, serviceInstance, replyTo)
ctx.system.log.debug("[{}] Actor was registered: {} {}", ctx.self, key, serviceInstance)
ctx.log.debug("Actor was registered: {} {}", key, serviceInstance)
watchWith(ctx, serviceInstance, RegisteredActorTerminated(key, serviceInstance))
replyTo ! Registered(key, serviceInstance)
externalInterface.onRegister(key, serviceInstance)
@ -132,7 +132,7 @@ private[akka] object ReceptionistImpl extends ReceptionistBehaviorProvider {
case externalInterface.RegistrationsChangedExternally(changes, state)
ctx.system.log.debug("[{}] Registration changed: {}", ctx.self, changes)
ctx.log.debug("Registration changed: {}", changes)
// FIXME: get rid of casts
def makeChanges(registry: LocalServiceRegistry): LocalServiceRegistry =
@ -144,7 +144,7 @@ private[akka] object ReceptionistImpl extends ReceptionistBehaviorProvider {
updateRegistry(changes.keySet, makeChanges) // overwrite all changed keys
case RegisteredActorTerminated(key, serviceInstance)
ctx.system.log.debug("[{}] Registered actor terminated: {} {}", ctx.self, key, serviceInstance)
ctx.log.debug("Registered actor terminated: {} {}", key, serviceInstance)
externalInterface.onUnregister(key, serviceInstance)
updateRegistry(Set(key), _.removed(key)(serviceInstance))

View file

@ -10,6 +10,7 @@ import akka.annotation.ApiMayChange
import akka.actor.typed._
import java.util.Optional
import akka.event.LoggingAdapter
import akka.util.Timeout
import scala.concurrent.duration.FiniteDuration
@ -60,6 +61,11 @@ trait ActorContext[T] {
*/
def getSystem: ActorSystem[Void]
/**
* An actor specific logger
*/
def getLog: Logger
/**
* The list of child Actors created by this Actor during its lifetime that
* are still alive, in no particular order.

View file

@ -16,9 +16,9 @@ import akka.actor.typed.Signal
import akka.actor.typed.ActorRef
import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.scaladsl.{ ActorContext SAC }
import akka.actor.typed.internal.{ BehaviorImpl, Supervisor, TimerSchedulerImpl }
import akka.actor.typed.internal.{ BehaviorImpl, LoggingBehaviorImpl, Supervisor, TimerSchedulerImpl }
import akka.annotation.ApiMayChange
import scala.collection.JavaConverters._
/**
* Factories for [[akka.actor.typed.Behavior]].
*/
@ -316,4 +316,19 @@ object Behaviors {
def receiveMessage(msg: T): Behavior[T]
def receiveSignal(msg: Signal): Behavior[T]
}
/**
* Provide a MDC ("Mapped Diagnostic Context") for logging from the actor.
*
* @param mdcForMessage Is invoked before each message to setup MDC which is then attachd to each logging statement
* done for that message through the [[ActorContext.getLog]]. After the message has been processed
* the MDC is cleared.
* @param behavior The behavior that this should be applied to.
*/
def withMdc[T](
`type`: Class[T],
mdcForMessage: akka.japi.function.Function[T, java.util.Map[String, Object]],
behavior: Behavior[T]): Behavior[T] =
LoggingBehaviorImpl.withMdc[T](message mdcForMessage.apply(message).asScala.toMap, behavior)(ClassTag(`type`))
}

View file

@ -3,16 +3,16 @@
*/
package akka.actor.typed.scaladsl
import akka.actor.typed._
import akka.annotation.{ ApiMayChange, DoNotInherit }
import akka.event.LoggingAdapter
import akka.util.Timeout
import scala.concurrent.ExecutionContextExecutor
import scala.concurrent.duration.FiniteDuration
import scala.reflect.ClassTag
import scala.util.Try
import akka.actor.typed._
import akka.annotation.ApiMayChange
import akka.annotation.DoNotInherit
import akka.annotation.InternalApi
import akka.util.Timeout
/**
* An Actor is given by the combination of a [[Behavior]] and a context in
@ -58,6 +58,11 @@ trait ActorContext[T] { this: akka.actor.typed.javadsl.ActorContext[T] ⇒
*/
def system: ActorSystem[Nothing]
/**
* An actor specific logger
*/
def log: Logger
/**
* The list of child Actors created by this Actor during its lifetime that
* are still alive, in no particular order.

View file

@ -5,7 +5,7 @@ package akka.actor.typed
package scaladsl
import akka.annotation.{ ApiMayChange, InternalApi }
import akka.actor.typed.internal.{ BehaviorImpl, Supervisor, TimerSchedulerImpl }
import akka.actor.typed.internal.{ BehaviorImpl, LoggingBehaviorImpl, Supervisor, TimerSchedulerImpl }
import scala.reflect.ClassTag
import scala.util.control.Exception.Catcher
@ -267,6 +267,19 @@ object Behaviors {
def withTimers[T](factory: TimerScheduler[T] Behavior[T]): Behavior[T] =
TimerSchedulerImpl.withTimers(factory)
/**
* Provide a MDC ("Mapped Diagnostic Context") for logging from the actor.
*
* @param mdcForMessage Is invoked before each message to setup MDC which is then attachd to each logging statement
* done for that message through the [[ActorContext.log]]. After the message has been processed
* the MDC is cleared.
* @param behavior The behavior that this should be applied to.
*/
def withMdc[T: ClassTag](
mdcForMessage: T Map[String, Any],
behavior: Behavior[T]): Behavior[T] =
LoggingBehaviorImpl.withMdc(mdcForMessage, behavior)
// TODO
// final case class Selective[T](timeout: FiniteDuration, selector: PartialFunction[T, Behavior[T]], onTimeout: () Behavior[T])

View file

@ -1,87 +0,0 @@
package akka.event.typed
import akka.actor.typed.ActorRef
import akka.event.Logging.LogLevel
/**
* An EventStream allows local actors to register for certain message types, including
* their subtypes automatically. Publishing events will broadcast them to all
* currently subscribed actors with matching subscriptions for the event type.
*
* IMPORTANT NOTICE
*
* This EventStream is local to the ActorSystem, it does not span a cluster. For
* disseminating messages across a cluster please refer to the DistributedPubSub
* module.
*/
trait EventStream {
/**
* Attempts to register the subscriber to the specified Classifier
* @return true if successful and false if not (because it was already
* subscribed to that Classifier, or otherwise)
*/
def subscribe[T](subscriber: ActorRef[T], to: Class[T]): Boolean
/**
* Attempts to deregister the subscriber from the specified Classifier
* @return true if successful and false if not (because it wasn't subscribed
* to that Classifier, or otherwise)
*/
def unsubscribe[T](subscriber: ActorRef[T], from: Class[T]): Boolean
/**
* Attempts to deregister the subscriber from all Classifiers it may be subscribed to
*/
def unsubscribe[T](subscriber: ActorRef[T]): Unit
/**
* Publishes the specified Event to this bus
*/
def publish[T](event: T): Unit
/**
* Query the current minimum log level.
*/
def logLevel: LogLevel
/**
* Change the current minimum log level.
*/
def setLogLevel(loglevel: LogLevel): Unit
}
import akka.actor.typed.{ ActorRef, Behavior, Settings }
import akka.event.Logging.LogEvent
import akka.{ event e }
abstract class Logger {
def initialBehavior: Behavior[Logger.Command]
}
object Logger {
sealed trait Command
case class Initialize(eventStream: EventStream, replyTo: ActorRef[ActorRef[LogEvent]]) extends Command
// FIXME add Mute/Unmute (i.e. the TestEventListener functionality)
}
class DefaultLoggingFilter(settings: Settings, eventStream: EventStream) extends e.DefaultLoggingFilter(() eventStream.logLevel)
/**
* [[akka.event.LoggingAdapter]] that publishes [[akka.event.Logging.LogEvent]] to event stream.
*/
class BusLogging(val bus: EventStream, val logSource: String, val logClass: Class[_], loggingFilter: e.LoggingFilter)
extends e.LoggingAdapter {
import e.Logging._
def isErrorEnabled = loggingFilter.isErrorEnabled(logClass, logSource)
def isWarningEnabled = loggingFilter.isWarningEnabled(logClass, logSource)
def isInfoEnabled = loggingFilter.isInfoEnabled(logClass, logSource)
def isDebugEnabled = loggingFilter.isDebugEnabled(logClass, logSource)
protected def notifyError(message: String): Unit = bus.publish(Error(logSource, logClass, message, mdc))
protected def notifyError(cause: Throwable, message: String): Unit = bus.publish(Error(cause, logSource, logClass, message, mdc))
protected def notifyWarning(message: String): Unit = bus.publish(Warning(logSource, logClass, message, mdc))
protected def notifyInfo(message: String): Unit = bus.publish(Info(logSource, logClass, message, mdc))
protected def notifyDebug(message: String): Unit = bus.publish(Debug(logSource, logClass, message, mdc))
}

View file

@ -739,17 +739,21 @@ object Logging {
}
trait LogEventWithCause {
def cause: Throwable
}
/**
* For ERROR Logging
*/
case class Error(cause: Throwable, logSource: String, logClass: Class[_], message: Any = "") extends LogEvent {
case class Error(override val cause: Throwable, logSource: String, logClass: Class[_], message: Any = "") extends LogEvent with LogEventWithCause {
def this(logSource: String, logClass: Class[_], message: Any) = this(Error.NoCause, logSource, logClass, message)
override def level = ErrorLevel
}
class Error2(cause: Throwable, logSource: String, logClass: Class[_], message: Any = "", override val mdc: MDC) extends Error(cause, logSource, logClass, message) {
class Error2(override val cause: Throwable, logSource: String, logClass: Class[_], message: Any = "", override val mdc: MDC) extends Error(cause, logSource, logClass, message) {
def this(logSource: String, logClass: Class[_], message: Any, mdc: MDC) = this(Error.NoCause, logSource, logClass, message, mdc)
}
class Error3(cause: Throwable, logSource: String, logClass: Class[_], message: Any, override val mdc: MDC, override val marker: LogMarker)
class Error3(override val cause: Throwable, logSource: String, logClass: Class[_], message: Any, override val mdc: MDC, override val marker: LogMarker)
extends Error2(cause, logSource, logClass, message, mdc) with LogEventWithMarker {
def this(logSource: String, logClass: Class[_], message: Any, mdc: MDC, marker: LogMarker) = this(Error.NoCause, logSource, logClass, message, mdc, marker)
}
@ -784,9 +788,14 @@ object Logging {
class Warning2(logSource: String, logClass: Class[_], message: Any, override val mdc: MDC) extends Warning(logSource, logClass, message)
class Warning3(logSource: String, logClass: Class[_], message: Any, override val mdc: MDC, override val marker: LogMarker)
extends Warning2(logSource, logClass, message, mdc) with LogEventWithMarker
class Warning4(logSource: String, logClass: Class[_], message: Any, override val mdc: MDC, override val marker: LogMarker, override val cause: Throwable)
extends Warning2(logSource, logClass, message, mdc) with LogEventWithMarker with LogEventWithCause
object Warning {
def apply(logSource: String, logClass: Class[_], message: Any, mdc: MDC) = new Warning2(logSource, logClass, message, mdc)
def apply(logSource: String, logClass: Class[_], message: Any, mdc: MDC, marker: LogMarker) = new Warning3(logSource, logClass, message, mdc, marker)
def apply(cause: Throwable, logSource: String, logClass: Class[_], message: Any, mdc: MDC) = new Warning4(logSource, logClass, message, mdc, null, cause)
def apply(cause: Throwable, logSource: String, logClass: Class[_], message: Any, mdc: MDC, marker: LogMarker) = new Warning4(logSource, logClass, message, mdc, marker, cause)
}
/**

View file

@ -72,7 +72,10 @@ class Slf4jLogger extends Actor with SLF4JLogging with RequiresMessageQueue[Logg
case event @ Warning(logSource, logClass, message)
withMdc(logSource, event) {
Logger(logClass, logSource).warn(markerIfPresent(event), "{}", message.asInstanceOf[AnyRef])
event match {
case e: LogEventWithCause Logger(logClass, logSource).warn(markerIfPresent(event), if (message != null) message.toString else null, e.cause)
case _ Logger(logClass, logSource).warn(markerIfPresent(event), if (message != null) message.toString else null)
}
}
case event @ Info(logSource, logClass, message)

View file

@ -7,8 +7,8 @@ package internal
import java.util.concurrent.{ CompletionStage, ThreadFactory }
import akka.annotation.InternalApi
import akka.event.Logging
import akka.event.typed.{ BusLogging, DefaultLoggingFilter, EventStream }
import akka.event.{ BusLogging, DefaultLoggingFilter, Logging }
import akka.testkit.typed.StubbedLogger
import akka.util.Timeout
import akka.{ actor a, event e }
import com.typesafe.config.ConfigFactory
@ -43,16 +43,7 @@ import scala.concurrent._
}
override def dynamicAccess: a.DynamicAccess = new a.ReflectiveDynamicAccess(getClass.getClassLoader)
override def eventStream: EventStream = new EventStream {
override def subscribe[T](subscriber: ActorRef[T], to: Class[T]) = false
override def setLogLevel(loglevel: Logging.LogLevel): Unit = {}
override def logLevel = Logging.InfoLevel
override def unsubscribe[T](subscriber: ActorRef[T], from: Class[T]) = false
override def unsubscribe[T](subscriber: ActorRef[T]): Unit = {}
override def publish[T](event: T): Unit = {}
}
override def logFilter: e.LoggingFilter = new DefaultLoggingFilter(settings, eventStream)
override def log: e.LoggingAdapter = new BusLogging(eventStream, path.parent.toString, getClass, logFilter)
override def logConfiguration(): Unit = log.info(settings.toString)
override def scheduler: a.Scheduler = throw new UnsupportedOperationException("no scheduler")
@ -84,4 +75,6 @@ import scala.concurrent._
def hasExtension(ext: ExtensionId[_ <: Extension]): Boolean =
throw new UnsupportedOperationException("ActorSystemStub cannot register extensions")
def log: Logger = new StubbedLogger
}

View file

@ -7,13 +7,13 @@ import java.util.concurrent.ConcurrentLinkedQueue
import akka.actor.typed.{ ActorRef, Behavior, PostStop, Props, Signal }
import akka.annotation.{ ApiMayChange, InternalApi }
import akka.event.LoggingAdapter
import scala.annotation.tailrec
import scala.collection.immutable
import scala.util.control.Exception.Catcher
import scala.util.control.NonFatal
import scala.concurrent.duration.{ Duration, FiniteDuration }
import scala.language.existentials
/**

View file

@ -3,14 +3,17 @@ package akka.testkit.typed
import akka.actor.InvalidMessageException
import akka.{ actor untyped }
import akka.actor.typed._
import akka.util.Helpers
import akka.util.{ Helpers, OptionVal }
import akka.{ actor a }
import scala.collection.immutable.TreeMap
import scala.concurrent.ExecutionContextExecutor
import scala.concurrent.duration.FiniteDuration
import akka.annotation.InternalApi
import akka.actor.typed.internal.{ ActorContextImpl, ActorRefImpl, ActorSystemStub, SystemMessage }
import akka.actor.typed.internal._
import akka.actor.typed.internal.adapter.LoggerAdapterImpl
import akka.event.Logging.{ Info, LogEvent, LogLevel }
import akka.event.{ Logging, LoggingAdapter }
/**
* A local synchronous ActorRef that invokes the given function for every message send.
@ -32,6 +35,39 @@ private[akka] final class FunctionRef[-T](
override def isLocal = true
}
final case class CapturedLogEvent(logLevel: LogLevel, message: String, cause: OptionVal[Throwable], marker: OptionVal[LogMarker])
/**
* INTERNAL API
*
* Captures log events for test inspection
*/
@InternalApi private[akka] final class StubbedLogger extends LoggerAdapterImpl(null, null, null, null) {
private var logBuffer: List[CapturedLogEvent] = Nil
override def isErrorEnabled: Boolean = true
override def isWarningEnabled: Boolean = true
override def isInfoEnabled: Boolean = true
override def isDebugEnabled: Boolean = true
override protected def notifyError(message: String, cause: OptionVal[Throwable], marker: OptionVal[LogMarker]): Unit =
logBuffer = CapturedLogEvent(Logging.ErrorLevel, message, cause, marker) :: logBuffer
override protected def notifyWarning(message: String, marker: OptionVal[LogMarker], cause: OptionVal[Throwable]): Unit =
logBuffer = CapturedLogEvent(Logging.WarningLevel, message, OptionVal.None, marker) :: logBuffer
override protected def notifyInfo(message: String, marker: OptionVal[LogMarker]): Unit =
logBuffer = CapturedLogEvent(Logging.InfoLevel, message, OptionVal.None, marker) :: logBuffer
override protected def notifyDebug(message: String, marker: OptionVal[LogMarker]): Unit =
logBuffer = CapturedLogEvent(Logging.DebugLevel, message, OptionVal.None, marker) :: logBuffer
def logEntries: List[CapturedLogEvent] = logBuffer.reverse
def clearLog(): Unit = logBuffer = Nil
}
/**
* INTERNAL API
*
@ -56,6 +92,7 @@ private[akka] final class FunctionRef[-T](
private var _children = TreeMap.empty[String, TestInbox[_]]
private val childName = Iterator from 0 map (Helpers.base64(_))
private val loggingAdapter = new StubbedLogger
override def children: Iterable[ActorRef[Nothing]] = _children.values map (_.ref)
def childrenNames: Iterable[String] = _children.keys
@ -138,4 +175,17 @@ private[akka] final class FunctionRef[-T](
def removeChildInbox(child: ActorRef[Nothing]): Unit = _children -= child.path.name
override def toString: String = s"Inbox($self)"
override def log: Logger = loggingAdapter
/**
* The log entries logged through ctx.log.{debug, info, warn, error} are captured and can be inspected through
* this method.
*/
def logEntries: List[CapturedLogEvent] = loggingAdapter.logEntries
/**
* Clear the log entries
*/
def clearLog(): Unit = loggingAdapter.clearLog()
}

View file

@ -1,68 +0,0 @@
package akka.testkit.typed
import akka.event.Logging.{ LogEvent, StdOutLogger }
import akka.testkit.{ EventFilter, TestEvent TE }
import akka.event.typed.Logger.{ Command, Initialize }
import scala.annotation.tailrec
import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.Behavior
import akka.event.typed.Logger
/**
* EventListener for running tests, which allows selectively filtering out
* expected messages. To use it, include something like this into
* your config
*
* <pre><code>
* akka.typed {
* loggers = ["akka.testkit.typed.TestEventListener"]
* }
* </code></pre>
*/
class TestEventListener extends Logger with StdOutLogger {
override val initialBehavior: Behavior[Command] = {
Behaviors.deferred[Command] { _
Behaviors.immutable[Command] {
case (ctx, Initialize(eventStream, replyTo))
val log = ctx.spawn(Behaviors.deferred[AnyRef] { childCtx
var filters: List[EventFilter] = Nil
def filter(event: LogEvent): Boolean = filters exists (f try { f(event) } catch { case e: Exception false })
def addFilter(filter: EventFilter): Unit = filters ::= filter
def removeFilter(filter: EventFilter) {
@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)
}
Behaviors.immutable[AnyRef] {
case (_, TE.Mute(filters))
filters foreach addFilter
Behaviors.same
case (_, TE.UnMute(filters))
filters foreach removeFilter
Behaviors.same
case (_, event: LogEvent)
if (!filter(event)) print(event)
Behaviors.same
case _ Behaviors.unhandled
}
}, "logger")
eventStream.subscribe(log, classOf[TE.Mute])
eventStream.subscribe(log, classOf[TE.UnMute])
ctx.watch(log) // sign death pact
replyTo ! log
Behaviors.empty
}
}
}
}