* Reference docs for SL4J logging in Typed, #27648 * mention package implicit for LoggerOps * reference docs of log testing utilities * cleanup classic logging.md * most of it is still relevant, and different enough from typed/logging.md to keep it separate * use ThresholdFilter instead of LevelFilter Co-Authored-By: Will Sargent <will.sargent@gmail.com>
This commit is contained in:
parent
ea7704928b
commit
8cb2721c33
22 changed files with 998 additions and 30 deletions
|
|
@ -8,20 +8,24 @@ import static jdocs.akka.actor.testkit.typed.javadsl.AsyncTestingExampleTest.Pin
|
|||
import static jdocs.akka.actor.testkit.typed.javadsl.AsyncTestingExampleTest.Pong;
|
||||
import static jdocs.akka.actor.testkit.typed.javadsl.AsyncTestingExampleTest.echoActor;
|
||||
|
||||
// #junit-integration
|
||||
import akka.actor.testkit.typed.javadsl.LogCapturing;
|
||||
import org.junit.Rule;
|
||||
|
||||
// #junit-integration
|
||||
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();
|
||||
|
||||
// #junit-integration
|
||||
// this is shown in LogCapturingExampleTest
|
||||
@Rule public final LogCapturing logCapturing = new LogCapturing();
|
||||
// #junit-integration
|
||||
|
||||
@Test
|
||||
public void testSomething() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.akka.actor.testkit.typed.javadsl;
|
||||
|
||||
import static jdocs.akka.actor.testkit.typed.javadsl.AsyncTestingExampleTest.*;
|
||||
|
||||
// #log-capturing
|
||||
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 LogCapturingExampleTest {
|
||||
|
||||
@ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource();
|
||||
|
||||
@Rule public final LogCapturing logCapturing = new LogCapturing();
|
||||
|
||||
@Test
|
||||
public void testSomething() {
|
||||
ActorRef<Ping> pinger = testKit.spawn(echoActor(), "ping");
|
||||
TestProbe<Pong> probe = testKit.createTestProbe();
|
||||
pinger.tell(new Ping("hello", probe.ref()));
|
||||
probe.expectMessage(new Pong("hello"));
|
||||
}
|
||||
}
|
||||
// #log-capturing
|
||||
|
|
@ -4,9 +4,8 @@
|
|||
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>DEBUG</level>
|
||||
<onMatch>DENY</onMatch>
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>%date{ISO8601} %-5level %logger %X{akkaSource} %X{sourceThread} - %msg%n</pattern>
|
||||
|
|
|
|||
|
|
@ -7,11 +7,15 @@ package docs.akka.actor.testkit.typed.scaladsl
|
|||
import com.github.ghik.silencer.silent
|
||||
import docs.akka.actor.testkit.typed.scaladsl.AsyncTestingExampleSpec.{ echoActor, Ping, Pong }
|
||||
|
||||
//#log-capturing
|
||||
import akka.actor.testkit.typed.scaladsl.LogCapturing
|
||||
//#scalatest-integration
|
||||
import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit
|
||||
import org.scalatest.WordSpecLike
|
||||
|
||||
//#scalatest-integration
|
||||
//#log-capturing
|
||||
|
||||
@silent
|
||||
//#scalatest-integration
|
||||
class ScalaTestIntegrationExampleSpec extends ScalaTestWithActorTestKit with WordSpecLike {
|
||||
|
|
@ -26,3 +30,17 @@ class ScalaTestIntegrationExampleSpec extends ScalaTestWithActorTestKit with Wor
|
|||
}
|
||||
}
|
||||
//#scalatest-integration
|
||||
|
||||
//#log-capturing
|
||||
class LogCapturingExampleSpec extends ScalaTestWithActorTestKit with WordSpecLike with LogCapturing {
|
||||
|
||||
"Something" must {
|
||||
"behave correctly" in {
|
||||
val pinger = testKit.spawn(echoActor, "ping")
|
||||
val probe = testKit.createTestProbe[Pong]()
|
||||
pinger ! Ping("hello", probe.ref)
|
||||
probe.expectMessage(Pong("hello"))
|
||||
}
|
||||
}
|
||||
}
|
||||
//#log-capturing
|
||||
|
|
|
|||
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* Copyright (C) 2019 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.akka.typed;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import akka.actor.typed.ActorRef;
|
||||
import akka.actor.typed.ActorSystem;
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.javadsl.AbstractBehavior;
|
||||
import akka.actor.typed.javadsl.ActorContext;
|
||||
import akka.actor.typed.javadsl.Behaviors;
|
||||
import akka.actor.typed.javadsl.Receive;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
// #logMessages
|
||||
import akka.actor.typed.LogOptions;
|
||||
import org.slf4j.event.Level;
|
||||
|
||||
// #logMessages
|
||||
|
||||
// #test-logging
|
||||
import akka.actor.testkit.typed.javadsl.LoggingEventFilter;
|
||||
|
||||
// #test-logging
|
||||
|
||||
public interface LoggingDocExamples {
|
||||
|
||||
// #context-log
|
||||
public class MyLoggingBehavior extends AbstractBehavior<String> {
|
||||
|
||||
public static Behavior<String> create() {
|
||||
return Behaviors.setup(MyLoggingBehavior::new);
|
||||
}
|
||||
|
||||
private final ActorContext<String> context;
|
||||
|
||||
private MyLoggingBehavior(ActorContext<String> context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive<String> createReceive() {
|
||||
return newReceiveBuilder().onMessage(String.class, this::onReceive).build();
|
||||
}
|
||||
|
||||
private Behavior<String> onReceive(String message) {
|
||||
context.getLog().info("Received message: {}", message);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
// #context-log
|
||||
|
||||
// #logger-name
|
||||
public class BackendManager extends AbstractBehavior<String> {
|
||||
|
||||
public static Behavior<String> create() {
|
||||
return Behaviors.setup(
|
||||
context -> {
|
||||
context.setLoggerName(BackendManager.class);
|
||||
context.getLog().info("Starting up");
|
||||
return new BackendManager(context);
|
||||
});
|
||||
}
|
||||
|
||||
private final ActorContext<String> context;
|
||||
|
||||
private BackendManager(ActorContext<String> context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive<String> createReceive() {
|
||||
return newReceiveBuilder().onMessage(String.class, this::onReceive).build();
|
||||
}
|
||||
|
||||
private Behavior<String> onReceive(String message) {
|
||||
context.getLog().debug("Received message: {}", message);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
// #logger-name
|
||||
|
||||
// #logger-factory
|
||||
class BackendTask {
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
void run() {
|
||||
CompletableFuture<String> task =
|
||||
CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
// some work
|
||||
return "result";
|
||||
});
|
||||
task.whenComplete(
|
||||
(result, exc) -> {
|
||||
if (exc == null) log.error("Task failed", exc);
|
||||
else log.info("Task completed: {}", result);
|
||||
});
|
||||
}
|
||||
}
|
||||
// #logger-factory
|
||||
|
||||
static void logMessages() {
|
||||
// #logMessages
|
||||
Behaviors.logMessages(LogOptions.create().withLevel(Level.TRACE), BackendManager.create());
|
||||
// #logMessages
|
||||
}
|
||||
|
||||
public class BackendManager2 extends AbstractBehavior<BackendManager2.Command> {
|
||||
|
||||
interface Command {
|
||||
String identifier();
|
||||
}
|
||||
|
||||
public static Behavior<Command> create() {
|
||||
return Behaviors.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive<Command> createReceive() {
|
||||
return newReceiveBuilder().build();
|
||||
}
|
||||
}
|
||||
|
||||
static void withMdc() {
|
||||
ActorSystem<Void> system = ActorSystem.create(Behaviors.empty(), "notUsed");
|
||||
|
||||
// #withMdc
|
||||
Map<String, String> staticMdc = new HashMap<>();
|
||||
staticMdc.put("startTime", String.valueOf(system.startTime()));
|
||||
|
||||
Behaviors.withMdc(
|
||||
BackendManager2.Command.class,
|
||||
staticMdc,
|
||||
message -> {
|
||||
Map<String, String> msgMdc = new HashMap<>();
|
||||
msgMdc.put("identifier", message.identifier());
|
||||
msgMdc.put("upTime", String.valueOf(system.uptime()));
|
||||
return msgMdc;
|
||||
},
|
||||
BackendManager2.create());
|
||||
// #withMdc
|
||||
}
|
||||
|
||||
static class Message {
|
||||
final String s;
|
||||
|
||||
public Message(String s) {
|
||||
this.s = s;
|
||||
}
|
||||
}
|
||||
|
||||
static void logging() {
|
||||
ActorSystem<Void> system = ActorSystem.create(Behaviors.empty(), "notUsed");
|
||||
ActorRef<Message> ref = null;
|
||||
|
||||
// #test-logging
|
||||
LoggingEventFilter.info("Received message")
|
||||
.intercept(
|
||||
system,
|
||||
() -> {
|
||||
ref.tell(new Message("hello"));
|
||||
return null;
|
||||
});
|
||||
// #test-logging
|
||||
|
||||
// #test-logging-criteria
|
||||
LoggingEventFilter.error(IllegalArgumentException.class)
|
||||
.withMessageRegex(".*was rejected.*expecting ascii input.*")
|
||||
.withCustom(
|
||||
event ->
|
||||
event.getMarker().isPresent()
|
||||
&& event.getMarker().get().getName().equals("validation"))
|
||||
.withOccurrences(2)
|
||||
.intercept(
|
||||
system,
|
||||
() -> {
|
||||
ref.tell(new Message("hellö"));
|
||||
ref.tell(new Message("hejdå"));
|
||||
return null;
|
||||
});
|
||||
// #test-logging-criteria
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>[%date{ISO8601}] [%-5level] [%logger] [%thread] [%X{akkaSource}] - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
||||
<file>target/myapp-dev.log</file>
|
||||
<encoder>
|
||||
<pattern>[%date{ISO8601}] [%-5level] [%logger] [%thread] [%X{akkaSource}] - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<queueSize>1024</queueSize>
|
||||
<neverBlock>true</neverBlock>
|
||||
<appender-ref ref="FILE" />
|
||||
</appender>
|
||||
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="ASYNC"/>
|
||||
</root>
|
||||
</configuration>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>myapp.log</file>
|
||||
<immediateFlush>false</immediateFlush>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>myapp_%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>[%date{ISO8601}] [%-5level] [%logger] [%thread] [%X{akkaSource}] - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<queueSize>1024</queueSize>
|
||||
<neverBlock>true</neverBlock>
|
||||
<appender-ref ref="FILE" />
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="ASYNC"/>
|
||||
</root>
|
||||
</configuration>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>[%date{ISO8601}] [%-5level] [%logger] [%thread] [%X{akkaSource}] - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!--
|
||||
Logging from tests are 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.
|
||||
-->
|
||||
<appender name="CapturingAppender" class="akka.actor.testkit.typed.internal.CapturingAppender" />
|
||||
|
||||
<!--
|
||||
The appenders defined for this CapturingAppenderDelegate logger are used
|
||||
when there is a test failure and all logging events from the test are
|
||||
flushed to these appenders.
|
||||
-->
|
||||
<logger name="akka.actor.testkit.typed.internal.CapturingAppenderDelegate" >
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</logger>
|
||||
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="CapturingAppender"/>
|
||||
</root>
|
||||
</configuration>
|
||||
|
|
@ -4,9 +4,8 @@
|
|||
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>DEBUG</level>
|
||||
<onMatch>DENY</onMatch>
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>%date{ISO8601} %-5level %logger %X{akkaSource} %X{sourceThread} - %msg%n</pattern>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Copyright (C) 2019 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.akka.typed
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
import scala.util.Failure
|
||||
import scala.util.Success
|
||||
|
||||
import akka.actor.typed.ActorRef
|
||||
import akka.actor.typed.ActorSystem
|
||||
import akka.actor.typed.Behavior
|
||||
import akka.actor.typed.scaladsl.Behaviors
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
object LoggingDocExamples {
|
||||
|
||||
object BackendManager {
|
||||
sealed trait Command {
|
||||
def identifier: String
|
||||
}
|
||||
def apply(): Behavior[Command] = Behaviors.empty
|
||||
}
|
||||
|
||||
def howToUse(): Unit = {
|
||||
|
||||
//#context-log
|
||||
Behaviors.receive[String] { (context, message) =>
|
||||
context.log.info("Received message: {}", message)
|
||||
Behaviors.same
|
||||
}
|
||||
//#context-log
|
||||
|
||||
//#logger-name
|
||||
Behaviors.setup[String] { context =>
|
||||
context.setLoggerName("com.myservice.BackendManager")
|
||||
context.log.info("Starting up")
|
||||
|
||||
Behaviors.receiveMessage { message =>
|
||||
context.log.debug("Received message: {}", message)
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
||||
//#logger-name
|
||||
|
||||
//#logger-factory
|
||||
val log = LoggerFactory.getLogger("com.myservice.BackendTask")
|
||||
|
||||
Future {
|
||||
// some work
|
||||
"result"
|
||||
}.onComplete {
|
||||
case Success(result) => log.info("Task completed: {}", result)
|
||||
case Failure(exc) => log.error("Task failed", exc)
|
||||
}
|
||||
//#logger-factory
|
||||
|
||||
}
|
||||
|
||||
def placeholders(): Unit = {
|
||||
//#info2
|
||||
import akka.actor.typed.scaladsl.LoggerOps
|
||||
|
||||
Behaviors.receive[String] { (context, message) =>
|
||||
context.log.info2("{} received message: {}", context.self.path.name, message)
|
||||
Behaviors.same
|
||||
}
|
||||
//#info2
|
||||
|
||||
//#infoN
|
||||
import akka.actor.typed.scaladsl.LoggerOps
|
||||
|
||||
Behaviors.receive[String] { (context, message) =>
|
||||
context.log.infoN(
|
||||
"{} received message of size {} starting with: {}",
|
||||
context.self.path.name,
|
||||
message.length,
|
||||
message.take(10))
|
||||
Behaviors.same
|
||||
}
|
||||
//#infoN
|
||||
|
||||
}
|
||||
|
||||
def logMessages(): Unit = {
|
||||
//#logMessages
|
||||
import akka.actor.typed.LogOptions
|
||||
import org.slf4j.event.Level
|
||||
|
||||
Behaviors.logMessages(LogOptions().withLevel(Level.TRACE), BackendManager())
|
||||
//#logMessages
|
||||
}
|
||||
|
||||
def withMdc(): Unit = {
|
||||
val system: ActorSystem[_] = ???
|
||||
|
||||
//#withMdc
|
||||
val staticMdc = Map("startTime" -> system.startTime.toString)
|
||||
Behaviors.withMdc[BackendManager.Command](
|
||||
staticMdc,
|
||||
mdcForMessage =
|
||||
(msg: BackendManager.Command) => Map("identifier" -> msg.identifier, "upTime" -> system.uptime.toString)) {
|
||||
BackendManager()
|
||||
}
|
||||
//#withMdc
|
||||
}
|
||||
|
||||
def logging(): Unit = {
|
||||
implicit val system: ActorSystem[_] = ???
|
||||
final case class Message(s: String)
|
||||
val ref: ActorRef[Message] = ???
|
||||
|
||||
//#test-logging
|
||||
import akka.actor.testkit.typed.scaladsl.LoggingEventFilter
|
||||
|
||||
// implicit ActorSystem is needed, but that is given by ScalaTestWithActorTestKit
|
||||
//implicit val system: ActorSystem[_]
|
||||
|
||||
LoggingEventFilter.info("Received message").intercept {
|
||||
ref ! Message("hello")
|
||||
}
|
||||
//#test-logging
|
||||
|
||||
//#test-logging-criteria
|
||||
LoggingEventFilter
|
||||
.error[IllegalArgumentException]
|
||||
.withMessageRegex(".*was rejected.*expecting ascii input.*")
|
||||
.withCustom { event =>
|
||||
event.marker match {
|
||||
case Some(m) => m.getName == "validation"
|
||||
case None => false
|
||||
}
|
||||
}
|
||||
.withOccurrences(2)
|
||||
.intercept {
|
||||
ref ! Message("hellö")
|
||||
ref ! Message("hejdå")
|
||||
}
|
||||
//#test-logging-criteria
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright (C) 2019 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.akka.typed
|
||||
|
||||
//#loggerops-package-implicit
|
||||
import scala.language.implicitConversions
|
||||
import akka.actor.typed.scaladsl.LoggerOps
|
||||
import org.slf4j.Logger
|
||||
|
||||
package object myapp {
|
||||
|
||||
implicit def loggerOps(logger: Logger): LoggerOps =
|
||||
LoggerOps(logger)
|
||||
|
||||
}
|
||||
//#loggerops-package-implicit
|
||||
|
|
@ -4,9 +4,8 @@
|
|||
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>DEBUG</level>
|
||||
<onMatch>DENY</onMatch>
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>%date{ISO8601} %-5level %logger %X{akkaSource} %X{sourceThread} - %msg%n</pattern>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,8 @@
|
|||
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>DEBUG</level>
|
||||
<onMatch>DENY</onMatch>
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>%date{ISO8601} %-5level %logger %X{akkaSource} %X{sourceThread} { "persistenceId": "%X{persistenceId}", "persistencePhase": "%X{persistencePhase}" } - %msg%n</pattern>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,8 @@
|
|||
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>DEBUG</level>
|
||||
<onMatch>DENY</onMatch>
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>%date{ISO8601} %-5level %logger %X{akkaSource} %X{sourceThread} - %msg%n</pattern>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
@@@ index
|
||||
|
||||
* [logging](logging.md)
|
||||
* [logging](typed/logging.md)
|
||||
* [common/circuitbreaker](common/circuitbreaker.md)
|
||||
|
||||
@@@
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
# Logging
|
||||
# Classic Logging
|
||||
|
||||
@@include[includes.md](includes.md) { #actor-api }
|
||||
For the new API see @ref[Logging](typed/logging.md).
|
||||
|
||||
## Dependency
|
||||
|
||||
|
|
@ -336,7 +339,7 @@ It has a single dependency: the slf4j-api jar. In your runtime, you also need a
|
|||
version="$akka.version$"
|
||||
group2="ch.qos.logback"
|
||||
artifact2="logback-classic"
|
||||
version2="1.2.3"
|
||||
version2="$logback_version$"
|
||||
}
|
||||
|
||||
You need to enable the Slf4jLogger in the `loggers` element in
|
||||
|
|
@ -352,6 +355,9 @@ If you set the `loglevel` to a higher level than "DEBUG", any DEBUG events will
|
|||
out already at the source and will never reach the logging backend, regardless of how the backend
|
||||
is configured.
|
||||
|
||||
You can enable `DEBUG` level for `akka.loglevel` and control the actual level in the SLF4J backend
|
||||
without any significant overhead, also for production.
|
||||
|
||||
@@@
|
||||
|
||||
```ruby
|
||||
|
|
@ -397,9 +403,31 @@ while the underlying infrastructure writes the log statements.
|
|||
|
||||
This can be avoided by configuring the logging implementation to use
|
||||
a non-blocking appender. Logback provides [AsyncAppender](http://logback.qos.ch/manual/appenders.html#AsyncAppender)
|
||||
that does this. It also contains a feature which will drop `INFO` and `DEBUG` messages if the logging
|
||||
that does this.
|
||||
|
||||
### Logback configuration
|
||||
|
||||
Logback has flexible configuration options and details can be found in the
|
||||
[Logback manual](https://logback.qos.ch/manual/configuration.html) and other external resources.
|
||||
|
||||
One part that is important to highlight is the importance of configuring an [AsyncAppender](http://logback.qos.ch/manual/appenders.html#AsyncAppender),
|
||||
because it offloads rendering of logging events to a background thread, increasing performance. It doesn't block
|
||||
the threads of the `ActorSystem` while the underlying infrastructure writes the log messages to disk or other configured
|
||||
destination. It also contains a feature which will drop `INFO` and `DEBUG` messages if the logging
|
||||
load is high.
|
||||
|
||||
A starting point for configuration of `logback.xml` for production:
|
||||
|
||||
@@snip [logback.xml](/akka-actor-typed-tests/src/test/resources/logback-doc-prod.xml)
|
||||
|
||||
For development you might want to log to standard out, but also have all debug level logging to file, like
|
||||
in this example:
|
||||
|
||||
@@snip [logback.xml](/akka-actor-typed-tests/src/test/resources/logback-doc-dev.xml)
|
||||
|
||||
Place the `logback.xml` file in `src/main/resources/logback.xml`. For tests you can define different
|
||||
logging configuration in `src/test/resources/logback-test.xml`.
|
||||
|
||||
### Logging Thread, Akka Source and Actor System in MDC
|
||||
|
||||
Since the logging is done asynchronously the thread in which the logging was performed is captured in
|
||||
|
|
@ -458,7 +486,7 @@ If you want to more accurately output the timestamp, use the MDC attribute `akka
|
|||
```
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%X{akkaTimestamp} %-5level %logger{36} %X{akkaSource} - %msg%n</pattern>
|
||||
<pattern>%X{akkaTimestamp} %-5level %logger{36} %X{akkaTimestamp} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
```
|
||||
|
|
|
|||
393
akka-docs/src/main/paradox/typed/logging.md
Normal file
393
akka-docs/src/main/paradox/typed/logging.md
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
# Logging
|
||||
|
||||
## Dependency
|
||||
|
||||
To use Logging, you must at least use the Akka actors dependency in your project, and configure logging
|
||||
via the SLF4J backend, such as Logback configuration.
|
||||
|
||||
@@dependency[sbt,Maven,Gradle] {
|
||||
group="com.typesafe.akka"
|
||||
artifact="akka-actor-typed_$scala.binary_version$"
|
||||
version="$akka.version$"
|
||||
}
|
||||
|
||||
## Introduction
|
||||
|
||||
[SLF4J](http://www.slf4j.org/) is used for logging and Akka provides access to an `org.slf4j.Logger` for a specific
|
||||
actor via the `ActorContext`. You may also retrieve a `Logger` with the ordinary `org.slf4j.LoggerFactory`.
|
||||
|
||||
To ensure that logging has minimal performance impact it's important that you configure an
|
||||
asynchronous appender for the SLF4J backend. Logging generally means IO and locks,
|
||||
which can slow down the operations of your code if it was performed synchronously.
|
||||
|
||||
## How to log
|
||||
|
||||
The `ActorContext` provides access to an `org.slf4j.Logger` for a specific actor.
|
||||
|
||||
Scala
|
||||
: @@snip [LoggingDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/LoggingDocExamples.scala) { #context-log }
|
||||
|
||||
Java
|
||||
: @@snip [LoggingDocExamples.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/LoggingDocExamples.java) { #context-log }
|
||||
|
||||
The `Logger` via the `ActorContext` will automatically have a name that corresponds to the `Behavior` of the
|
||||
actor when the log is accessed the first time. The class name when using `AbstractBehavior` or the class @scala[or object]
|
||||
name where the `Behavior` is defined when using the functional style. You can set a custom logger name
|
||||
with the `setLoggerName` of the `ActorContext`.
|
||||
|
||||
Scala
|
||||
: @@snip [LoggingDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/LoggingDocExamples.scala) { #logger-name }
|
||||
|
||||
Java
|
||||
: @@snip [LoggingDocExamples.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/LoggingDocExamples.java) { #logger-name }
|
||||
|
||||
The convention is to use logger names like fully qualified class names. The parameter to `setLoggerName`
|
||||
can be a `String` or a `Class`, where the latter is convenience for the class name.
|
||||
|
||||
When logging via the `ActorContext` the path of the actor will automatically be added as `akkaSource`
|
||||
Mapped Diagnostic Context (MDC) value. MDC is typically implemented with a `ThreadLocal` by the SLF4J backend.
|
||||
To reduce performance impact this MDC value is set when you access the @scala[`log`]@java[`getLog`] method so
|
||||
you shouldn't cache the returned `Logger` in your own field. That is handled by `ActorContext` and retrieving
|
||||
the `Logger` repeatedly with the @scala[`log`]@java[`getLog`] method has low overhead.
|
||||
The MDC is cleared automatically after processing of current message has finished.
|
||||
|
||||
@@@ note
|
||||
|
||||
The `Logger` is thread-safe but the @scala[`log`]@java[`getLog`] method in `ActorContext` is not
|
||||
thread-safe and should not be accessed from threads other than the ordinary actor message processing
|
||||
thread, such as @scala[`Future`]@java[`CompletionStage`] callbacks.
|
||||
|
||||
@@@
|
||||
|
||||
It's also perfectly fine to use a `Logger` retrieved via `org.slf4j.LoggerFactory`, but then the logging
|
||||
events will not include the `akkaSource` MDC value. This is the recommend way when logging outside
|
||||
of an actor, including logging from @scala[`Future`]@java[`CompletionStage`] callbacks.
|
||||
|
||||
Scala
|
||||
: @@snip [LoggingDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/LoggingDocExamples.scala) { #logger-factory }
|
||||
|
||||
Java
|
||||
: @@snip [LoggingDocExamples.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/LoggingDocExamples.java) { #logger-factory }
|
||||
|
||||
### Placeholder arguments
|
||||
|
||||
The log message may contain argument placeholders `{}`, which will be substituted if the log level is enabled.
|
||||
Compared to constructing a full string for the log message this has the advantage of avoiding superfluous
|
||||
string concatenation and object allocations when the log level is disabled. Some logging backends
|
||||
may also use these message templates before argument substitution to group and filter logging events.
|
||||
|
||||
It can be good to know that 3 or more arguments will result in the relatively small cost of allocating
|
||||
an array (vararg parameter) also when the log level is disabled. The methods with 1 or 2 arguments
|
||||
don't allocate the vararg array.
|
||||
|
||||
@@@ div { .group-scala }
|
||||
|
||||
When using the methods for 2 argument placeholders the compiler will often not be able to select the
|
||||
right method and report compiler error "ambiguous reference to overloaded definition". To work around this
|
||||
problem you can use the `trace2`, `debug2`, `info2`, `warn2` or `error2` extension methods that are added
|
||||
by `import akka.actor.typed.scaladsl.LoggerOps` or `import akka.actor.typed.scaladsl._`.
|
||||
|
||||
Scala
|
||||
: @@snip [LoggingDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/LoggingDocExamples.scala) { #info2 }
|
||||
|
||||
When using the methods for 3 or more argument placeholders, the compiler will not be able to convert
|
||||
the method parameters to the vararg array when they contain primitive values such as `Int`,
|
||||
and report compiler error "overloaded method value info with alternatives".
|
||||
To work around this problem you can use the `traceN`, `debugN`, `infoN`, `warnN` or `errorN` extension
|
||||
methods that are added by the same `LoggerOps` import.
|
||||
|
||||
Scala
|
||||
: @@snip [LoggingDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/LoggingDocExamples.scala) { #infoN }
|
||||
|
||||
If you find it tedious to add the import of `LoggerOps` at many places you can make those additional methods
|
||||
available with a single implicit conversion placed in a root package object of your code:
|
||||
|
||||
Scala
|
||||
: @@snip [package.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/myapp/package.scala) { #loggerops-package-implicit }
|
||||
|
||||
@@@
|
||||
|
||||
### Behaviors.logMessages
|
||||
|
||||
If you want very detailed logging of messages and signals you can decorate a `Behavior`
|
||||
with `Behaviors.logMessages`.
|
||||
|
||||
Scala
|
||||
: @@snip [LoggingDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/LoggingDocExamples.scala) { #logMessages }
|
||||
|
||||
Java
|
||||
: @@snip [LoggingDocExamples.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/LoggingDocExamples.java) { #logMessages }
|
||||
|
||||
|
||||
## Behaviors.withMdc
|
||||
|
||||
To include [MDC](http://logback.qos.ch/manual/mdc.html) attributes in logging events from an actor
|
||||
you can decorate a `Behavior` with `Behaviors.withMdc` or use the `org.slf4j.MDC` API directly.
|
||||
|
||||
The `Behaviors.withMdc` decorator has two parts. A static `Map` of MDC attributes that are not changed,
|
||||
and a dynamic `Map` that can be constructed for each message.
|
||||
|
||||
Scala
|
||||
: @@snip [LoggingDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/LoggingDocExamples.scala) { #withMdc }
|
||||
|
||||
Java
|
||||
: @@snip [LoggingDocExamples.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/LoggingDocExamples.java) { #withMdc }
|
||||
|
||||
If you use the MDC API directly, be aware of that MDC is typically implemented with a `ThreadLocal` by the SLF4J backend.
|
||||
Akka clears the MDC if logging is performed via the @scala[`log`]@java[`getLog`] of the `ActorContext` the MDC is cleared
|
||||
automatically after processing of current message has finished, but only if you accessed @scala[`log`]@java[`getLog`].
|
||||
The entire MDC is cleared, including attributes that you add yourself to the MDC.
|
||||
MDC is not cleared automatically if you use a `Logger` via `LoggerFactory` or not touch @scala[`log`]@java[`getLog`]
|
||||
in the `ActorContext`.
|
||||
|
||||
## SLF4J backend
|
||||
|
||||
To ensure that logging has minimal performance impact it's important that you configure an
|
||||
asynchronous appender for the SLF4J backend. Logging generally means IO and locks,
|
||||
which can slow down the operations of your code if it was performed synchronously.
|
||||
|
||||
@@@ warning
|
||||
|
||||
For production the SLF4J backend should be configured with an asynchronous appender as described here.
|
||||
Otherwise there is a risk of reduced performance and thread starvation problems of the dispatchers
|
||||
that are running actors and other tasks.
|
||||
|
||||
@@@
|
||||
|
||||
### Logback
|
||||
|
||||
`akka-actor-typed` includes a dependency to the `slf4j-api`. In your runtime, you also need a SLF4J backend.
|
||||
We recommend [Logback](http://logback.qos.ch/):
|
||||
|
||||
@@dependency[sbt,Maven,Gradle] {
|
||||
group="ch.qos.logback"
|
||||
artifact="logback-classic"
|
||||
version="$logback_version$"
|
||||
}
|
||||
|
||||
Logback has flexible configuration options and details can be found in the
|
||||
[Logback manual](https://logback.qos.ch/manual/configuration.html) and other external resources.
|
||||
|
||||
One part that is important to highlight is the importance of configuring an [AsyncAppender](http://logback.qos.ch/manual/appenders.html#AsyncAppender),
|
||||
because it offloads rendering of logging events to a background thread, increasing performance. It doesn't block
|
||||
the threads of the `ActorSystem` while the underlying infrastructure writes the log messages to disk or other configured
|
||||
destination. It also contains a feature which will drop `INFO` and `DEBUG` messages if the logging
|
||||
load is high.
|
||||
|
||||
A starting point for configuration of `logback.xml` for production:
|
||||
|
||||
@@snip [logback.xml](/akka-actor-typed-tests/src/test/resources/logback-doc-prod.xml)
|
||||
|
||||
For development you might want to log to standard out, but also have all debug level logging to file, like
|
||||
in this example:
|
||||
|
||||
@@snip [logback.xml](/akka-actor-typed-tests/src/test/resources/logback-doc-dev.xml)
|
||||
|
||||
Place the `logback.xml` file in `src/main/resources/logback.xml`. For tests you can define different
|
||||
logging configuration in `src/test/resources/logback-test.xml`.
|
||||
|
||||
#### MDC values
|
||||
|
||||
When logging via the @scala[`log`]@java[`getLog`] of the `ActorContext` as described in
|
||||
@ref:[How to log](#how-to-log) Akka captures the actor's path and includes that in MDC with
|
||||
attribute name `akkaSource`.
|
||||
|
||||
This can be included in the log output with `%X{akkaSource}` specifier within the pattern layout configuration:
|
||||
|
||||
```
|
||||
<encoder>
|
||||
<pattern>%date{ISO8601} %-5level %logger{36} %X{akkaSource} - %msg%n</pattern>
|
||||
</encoder>
|
||||
```
|
||||
|
||||
## Internal logging by Akka
|
||||
|
||||
### Event bus
|
||||
|
||||
For historical reasons logging by the Akka internals and by classic actors are performed asynchronously
|
||||
through an event bus. Such log events are processed by an event handler actor, which then emits them to
|
||||
SLF4J or directly to standard out.
|
||||
|
||||
When `akka-actor-typed` is on the classpath this event handler actor will emit the events to SLF4J.
|
||||
The `akka.event.slf4j.Slf4jLogger` and `akka.event.slf4j.Slf4jLoggingFilter` are enabled automatically
|
||||
without additional configuration. This can be disabled by `akka.use-slf4j=off` configuration property.
|
||||
|
||||
In other words, you don't have to do anything for the Akka internal logging to end up in your configured
|
||||
SLF4J backend.
|
||||
|
||||
### Log level
|
||||
|
||||
Ultimately the log level defined in the SLF4J backend is used. For the Akka internal logging it will
|
||||
also check the level defined by the SLF4J backend before constructing the final log message and
|
||||
emitting it to the event bus.
|
||||
|
||||
However, there is an additional `akka.loglevel` configuration property that defines if logging events
|
||||
with lower log level should be discarded immediately without consulting the SLF4J backend. By default
|
||||
this is at `INFO` level, which means that `DEBUG` level logging from the Akka internals will not
|
||||
reach the SLF4J backend even if `DEBUG` is enabled in the backend.
|
||||
|
||||
You can enable `DEBUG` level for `akka.loglevel` and control the actual level in the SLF4j backend
|
||||
without any significant overhead, also for production.
|
||||
|
||||
```
|
||||
akka.loglevel = "DEBUG"
|
||||
```
|
||||
|
||||
To turn off all Akka internal logging (not recommended) you can configure the log levels to be
|
||||
`OFF` like this.
|
||||
|
||||
```
|
||||
akka {
|
||||
stdout-loglevel = "OFF"
|
||||
loglevel = "OFF"
|
||||
}
|
||||
```
|
||||
|
||||
The `stdout-loglevel` is only in effect during system startup and shutdown, and setting
|
||||
it to `OFF` as well, ensures that nothing gets logged during system startup or shutdown.
|
||||
|
||||
### Logging to stdout during startup and shutdown
|
||||
|
||||
When the actor system is starting up and shutting down the configured `loggers` are not used.
|
||||
Instead log messages are printed to stdout (System.out). The default log level for this
|
||||
stdout logger is `WARNING` and it can be silenced completely by setting
|
||||
`akka.stdout-loglevel=OFF`.
|
||||
|
||||
### Logging of Dead Letters
|
||||
|
||||
By default messages sent to dead letters are logged at info level. Existence of dead letters
|
||||
does not necessarily indicate a problem, but they are logged by default for the sake of caution.
|
||||
After a few messages this logging is turned off, to avoid flooding the logs.
|
||||
You can disable this logging completely or adjust how many dead letters are
|
||||
logged. During system shutdown it is likely that you see dead letters, since pending
|
||||
messages in the actor mailboxes are sent to dead letters. You can also disable logging
|
||||
of dead letters during shutdown.
|
||||
|
||||
```
|
||||
akka {
|
||||
log-dead-letters = 10
|
||||
log-dead-letters-during-shutdown = on
|
||||
}
|
||||
```
|
||||
|
||||
To customize the logging further or take other actions for dead letters you can subscribe
|
||||
to the @ref:[Event Stream](../event-bus.md#event-stream).
|
||||
|
||||
### Auxiliary logging options
|
||||
|
||||
Akka has a few configuration options for very low level debugging. These make more sense in development than in production.
|
||||
|
||||
You almost definitely need to have logging set to DEBUG to use any of the options below:
|
||||
|
||||
```
|
||||
akka {
|
||||
loglevel = "DEBUG"
|
||||
}
|
||||
```
|
||||
|
||||
This config option is very good if you want to know what config settings are loaded by Akka:
|
||||
|
||||
```
|
||||
akka {
|
||||
# Log the complete configuration at INFO level when the actor system is started.
|
||||
# This is useful when you are uncertain of what configuration is used.
|
||||
log-config-on-start = on
|
||||
}
|
||||
```
|
||||
|
||||
If you want unhandled messages logged at DEBUG:
|
||||
|
||||
```
|
||||
akka {
|
||||
actor {
|
||||
debug {
|
||||
# enable DEBUG logging of unhandled messages
|
||||
unhandled = on
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you want to monitor subscriptions (subscribe/unsubscribe) on the ActorSystem.eventStream:
|
||||
|
||||
```
|
||||
akka {
|
||||
actor {
|
||||
debug {
|
||||
# enable DEBUG logging of subscription changes on the eventStream
|
||||
event-stream = on
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<a id="logging-remote"></a>
|
||||
### Auxiliary remote logging options
|
||||
|
||||
If you want to see all messages that are sent through remoting at DEBUG log level, use the following config option.
|
||||
Note that this logs the messages as they are sent by the transport layer, not by an actor.
|
||||
|
||||
```
|
||||
akka.remote.artery {
|
||||
# If this is "on", Akka will log all outbound messages at DEBUG level,
|
||||
# if off then they are not logged
|
||||
log-sent-messages = on
|
||||
}
|
||||
```
|
||||
|
||||
If you want to see all messages that are received through remoting at DEBUG log level, use the following config option.
|
||||
Note that this logs the messages as they are received by the transport layer, not by an actor.
|
||||
|
||||
```
|
||||
akka.remote.artery {
|
||||
# If this is "on", Akka will log all inbound messages at DEBUG level,
|
||||
# if off then they are not logged
|
||||
log-received-messages = on
|
||||
}
|
||||
```
|
||||
|
||||
### MDC values from Akka internal logging
|
||||
|
||||
Since the logging is done asynchronously the thread in which the logging was performed is captured in
|
||||
MDC with attribute name `sourceThread`.
|
||||
With Logback the thread name is available with `%X{sourceThread}` specifier within the pattern layout configuration:
|
||||
|
||||
```
|
||||
<encoder>
|
||||
<pattern>%date{ISO8601} %-5level %logger{36} %X{sourceThread} - %msg%n</pattern>
|
||||
</encoder>
|
||||
```
|
||||
|
||||
The path of the actor in which the logging was performed is available in the MDC with attribute name `akkaSource`,
|
||||
With Logback the actor path is available with `%X{akkaSource}` specifier within the pattern layout configuration:
|
||||
```
|
||||
<encoder>
|
||||
<pattern>%date{ISO8601} %-5level %logger{36} %X{akkaSource} - %msg%n</pattern>
|
||||
</encoder>
|
||||
```
|
||||
|
||||
The actor system in which the logging was performed is available in the MDC with attribute name `sourceActorSystem`,
|
||||
but that is typically also included in the `akkaSource` attribute.
|
||||
With Logback the ActorSystem name is available with `%X{sourceActorSystem}` specifier within the pattern layout configuration:
|
||||
|
||||
```
|
||||
<encoder>
|
||||
<pattern>%date{ISO8601} %-5level %logger{36} %X{sourceActorSystem} - %msg%n</pattern>
|
||||
</encoder>
|
||||
```
|
||||
|
||||
Akka's internal logging is asynchronous which means that the timestamp of a log entry is taken from
|
||||
when the underlying logger implementation is called, which can be surprising at first.
|
||||
If you want to more accurately output the timestamp, use the MDC attribute `akkaTimestamp`.
|
||||
With Logback the timestamp is available with `%X{akkaTimestamp}` specifier within the pattern layout configuration:
|
||||
|
||||
```
|
||||
<encoder>
|
||||
<pattern>%X{akkaTimestamp} %-5level %logger{36} %X{akkaTimestamp} - %msg%n</pattern>
|
||||
</encoder>
|
||||
```
|
||||
|
||||
|
||||
## Logging in tests
|
||||
|
||||
Testing utilities are described in @ref:[Testing](testing.md#test-of-logging).
|
||||
|
|
@ -225,6 +225,65 @@ Java
|
|||
: @@snip [ManualTimerExampleTest.scala](/akka-actor-testkit-typed/src/test/java/jdocs/akka/actor/testkit/typed/javadsl/ManualTimerExampleTest.java) { #manual-scheduling-simple }
|
||||
|
||||
|
||||
### Test of logging
|
||||
|
||||
To verify that certain @ref:[logging](logging.md) events are emitted there is a utility called `LoggingEventFilter`.
|
||||
You define a criteria of the expected logging events and it will assert that the given number of occurrences
|
||||
of matching logging events are emitted within a block of code.
|
||||
|
||||
@@@ note
|
||||
|
||||
The `LoggingEventFilter` implementation @ref:[requires Logback dependency](logging.md#logback).
|
||||
|
||||
@@@
|
||||
|
||||
For example, a criteria that verifies that an `INFO` level event with a message containing "Received message" is logged:
|
||||
|
||||
Scala
|
||||
: @@snip [LoggingDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/LoggingDocExamples.scala) { #test-logging }
|
||||
|
||||
Java
|
||||
: @@snip [LoggingDocExamples.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/LoggingDocExamples.java) { #test-logging }
|
||||
|
||||
More advanced criteria can be built by chaining conditions that all must be satisfied for a matching event.
|
||||
|
||||
Scala
|
||||
: @@snip [LoggingDocExamples.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/LoggingDocExamples.scala) { #test-logging-criteria }
|
||||
|
||||
Java
|
||||
: @@snip [LoggingDocExamples.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/LoggingDocExamples.java) { #test-logging-criteria }
|
||||
|
||||
See @apidoc[typed.*.LoggingEventFilter] for more details.
|
||||
|
||||
### Silence logging output from tests
|
||||
|
||||
When running tests, it's typically preferred to have the output to standard out, together with the output from the
|
||||
testing framework (@scala[ScalaTest]@java[JUnit]). On one hand you want the output to be clean without logging noise,
|
||||
but on the other hand you want as much information as possible if there is a test failure (for example in CI builds).
|
||||
|
||||
The Akka TestKit provides a `LogCapturing` utility to support this with ScalaTest or JUnit. It will buffer log events instead
|
||||
of emitting them to the `ConsoleAppender` immediately (or whatever Logback appender that is configured). When
|
||||
there is a test failure the buffered events are flushed to the target appenders, typically a `ConsoleAppender`.
|
||||
|
||||
@@@ note
|
||||
|
||||
The `LogCapturing` utility @ref:[requires Logback dependency](logging.md#logback).
|
||||
|
||||
@@@
|
||||
|
||||
@scala[Mix `LogCapturing` trait into the ScalaTest]@java[Add a `LogCapturing` `@Rule` in the JUnit test] like this:
|
||||
|
||||
Scala
|
||||
: @@snip [ScalaTestIntegrationExampleSpec.scala](/akka-actor-testkit-typed/src/test/scala/docs/akka/actor/testkit/typed/scaladsl/ScalaTestIntegrationExampleSpec.scala) { #log-capturing }
|
||||
|
||||
Java
|
||||
: @@snip [LogCapturingExampleTest.java](/akka-actor-testkit-typed/src/test/java/jdocs/akka/actor/testkit/typed/javadsl/LogCapturingExampleTest.java) { #log-capturing }
|
||||
|
||||
Then you also need to configure the `CapturingAppender` and `CapturingAppenderDelegate` in
|
||||
`src/test/resources/logback-test.xml`:
|
||||
|
||||
@@snip [logback-test.xml](/akka-actor-typed-tests/src/test/resources/logback-doc-test.xml)
|
||||
|
||||
## Synchronous behavior testing
|
||||
|
||||
The `BehaviorTestKit` provides a very nice way of unit testing a `Behavior` in a deterministic way, but it has
|
||||
|
|
|
|||
|
|
@ -4,9 +4,8 @@
|
|||
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>DEBUG</level>
|
||||
<onMatch>DENY</onMatch>
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>%date{ISO8601} %-5level %logger %X{akkaSource} %X{sourceThread} - %msg%n</pattern>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,8 @@
|
|||
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>DEBUG</level>
|
||||
<onMatch>DENY</onMatch>
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>%date{ISO8601} %-5level %logger %X{akkaSource} %X{sourceThread} { "persistenceId": "%X{persistenceId}", "persistencePhase": "%X{persistencePhase}" } - %msg%n</pattern>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ object Dependencies {
|
|||
val jacksonVersion = "2.9.9"
|
||||
val jacksonDatabindVersion = "2.9.9.3"
|
||||
val protobufJavaVersion = "3.9.1"
|
||||
val logbackVersion = "1.2.3"
|
||||
|
||||
val scala212Version = "2.12.9"
|
||||
val scala213Version = "2.13.0"
|
||||
|
|
@ -98,7 +99,7 @@ object Dependencies {
|
|||
|
||||
val protobufRuntime = "com.google.protobuf" % "protobuf-java" % protobufJavaVersion
|
||||
|
||||
val logback = "ch.qos.logback" % "logback-classic" % "1.2.3" // EPL 1.0
|
||||
val logback = "ch.qos.logback" % "logback-classic" % logbackVersion // EPL 1.0
|
||||
|
||||
object Docs {
|
||||
val sprayJson = "io.spray" %% "spray-json" % "1.3.5" % "test"
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ object Paradox {
|
|||
"fiddle.code.base_dir" -> (sourceDirectory in Test).value.getAbsolutePath,
|
||||
"fiddle.akka.base_dir" -> (baseDirectory in ThisBuild).value.getAbsolutePath,
|
||||
"aeron_version" -> Dependencies.aeronVersion,
|
||||
"netty_version" -> Dependencies.nettyVersion))
|
||||
"netty_version" -> Dependencies.nettyVersion,
|
||||
"logback_version" -> Dependencies.logbackVersion))
|
||||
|
||||
val rootsSettings = Seq(
|
||||
paradoxRoots := List(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue