Skip to content

Latest commit

 

History

History
530 lines (404 loc) · 20.1 KB

logging.md

File metadata and controls

530 lines (404 loc) · 20.1 KB

日志记录

依赖

要使用日志记录(Logging),你必须至少在项目中使用 Akka Actors 依赖项,并且很可能希望通过 SLF4J 模块配置日志记录,或者使用java.util.logging

<!-- Maven -->
<dependency>
  <groupId>com.typesafe.akka</groupId>
  <artifactId>akka-actor_2.12</artifactId>
  <version>2.5.23</version>
</dependency>

<!-- Gradle -->
dependencies {
  compile group: 'com.typesafe.akka', name: 'akka-actor_2.12', version: '2.5.23'
}

<!-- sbt -->
libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.5.23"

简介

在 Akka 中日志记录(Logging)不与特定的日志后端绑定。默认情况下,日志消息打印到 STDOUT,但你可以插入 SLF4J 记录器或自己的记录器。日志记录是异步执行的,以确保日志记录对性能的影响最小。日志记录通常意味着 IO 和锁,如果代码是同步执行的,这会减慢代码的操作速度。

如何记录日志

创建一个LoggingAdapter,并使用errorwarninginfodebug方法,如本例所示:

import akka.actor.*;
import akka.event.Logging;
import akka.event.LoggingAdapter;
class MyActor extends AbstractActor {
  LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);

  @Override
  public void preStart() {
    log.debug("Starting");
  }

  @Override
  public void preRestart(Throwable reason, Optional<Object> message) {
    log.error(
        reason,
        "Restarting due to [{}] when processing [{}]",
        reason.getMessage(),
        message.isPresent() ? message.get() : "");
  }

  @Override
  public Receive createReceive() {
    return receiveBuilder()
        .matchEquals("test", msg -> log.info("Received test"))
        .matchAny(msg -> log.warning("Received unknown message: {}", msg))
        .build();
  }
}

Logging.getLogger的第一个参数也可以是任何LoggingBus;在演示的情况下,Actor 系统的地址包含在日志源的akkaSource表示中(请参阅「Logging Thread, Akka Source and Actor System in MDC」),而在第二种情况下,这不会自动完成。Logging.getLogger的第二个参数是此日志通道的源。源对象根据以下规则转换为字符串:

  • 如果是 Actor 或ActorRef,则使用其路径
  • 如果是String,则按原样使用
  • 在类的情况下,为类的simpleName近似值
  • 在所有其他情况下,为类的simpleName

日志消息可能包含参数占位符{},如果启用了日志级别,则将替换它。提供比占位符更多的参数会导致将警告附加到日志语句(即,在具有相同严重性的同一行上)。可以将数组作为唯一的替换参数传递,以便单独处理其元素:

final Object[] args = new Object[] {"The", "brown", "fox", "jumps", 42};
system.log().debug("five parameters: {}, {}, {}, {}, {}", args);

日志源的 Java Class也包含在生成的LogEvent中。如果是简单字符串,则用markerakka.event.DummyClassForStringSources替换,以便对这种情况进行特殊处理,例如在 SLF4J 事件侦听器中,它将使用字符串而不是类的名称来查找要使用的记录器实例。

死信的记录

默认情况下,发送到死信(dead letters)的消息记录在info级别。死信的存在并不一定表示有问题,但为了谨慎起见,默认情况下会记录这些死信。几条消息后,此日志记录将关闭,以避免日志被淹没。你可以完全禁用此日志记录,或者调整记录的死信数。在系统关闭期间,很可能会看到死信,因为 Actor 邮箱中的挂起消息会发送到死信。你还可以在关机期间禁用死信记录。

akka {
  log-dead-letters = 10
  log-dead-letters-during-shutdown = on
}

要进一步自定义日志记录或对死信采取其他操作,可以订阅事件流。

辅助日志记录选项

Akka 有一些配置选项用于非常低级别的调试。这些在开发中比在生产中更有意义。

你几乎肯定需要将日志设置为DEBUG,才能使用以下任何选项:

akka {
  loglevel = "DEBUG"
}

如果你想知道 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
}

如果你希望对 Actor 处理的所有自动接收的消息进行非常详细的日志记录:

akka {
  actor {
    debug {
      # enable DEBUG logging of all AutoReceiveMessages (Kill, PoisonPill etc.)
      autoreceive = on
    }
  }
}

如果你希望非常详细地记录 Actor 的所有生命周期更改(重启、死亡等):

akka {
  actor {
    debug {
      # enable DEBUG logging of actor lifecycle changes
      lifecycle = on
    }
  }
}

如果希望在DEBUG时记录未处理的消息:

akka {
  actor {
    debug {
      # enable DEBUG logging of unhandled messages
      unhandled = on
    }
  }
}

如果你希望非常详细地记录扩展LoggingFSM的 FSM Actor 的所有事件、转换和计时器,请执行以下操作:

akka {
  actor {
    debug {
      # enable DEBUG logging of all LoggingFSMs for events, transitions and timers
      fsm = on
    }
  }
}

如果要监视ActorSystem.eventStream上的订阅消息(订阅/取消订阅):

akka {
  actor {
    debug {
      # enable DEBUG logging of subscription changes on the eventStream
      event-stream = on
    }
  }
}

辅助远程日志记录选项

如果要查看在DEBUG日志级别通过远程处理发送的所有消息,请使用以下配置选项。请注意,这会记录由传输层而不是由 Actor 发送的消息。

akka {
  remote {
    # If this is "on", Akka will log all outbound messages at DEBUG level,
    # if off then they are not logged
    log-sent-messages = on
  }
}

如果要查看在DEBUG日志级别通过远程处理接收到的所有消息,请使用以下配置选项。请注意,这会记录由传输层而不是由 Actor 接收的消息。

akka {
  remote {
    # If this is "on", Akka will log all inbound messages at DEBUG level,
    # if off then they are not logged
    log-received-messages = on
  }
}

如果要在INFO日志级别查看有效负载大小(字节)大于指定限制的消息类型:

akka {
  remote {
    # Logging of message types with payload size in bytes larger than
    # this value. Maximum detected size per message type is logged once,
    # with an increase threshold of 10%.
    # By default this feature is turned off. Activate it by setting the property to
    # a value in bytes, such as 1000b. Note that for all messages larger than this
    # limit there will be extra performance and scalability cost.
    log-frame-size-exceeding = 1000b
  }
}

详见「TestKit 的日志记录选项」。

关闭日志记录

要关闭日志记录,可以将日志级别配置为OFF

akka {
  stdout-loglevel = "OFF"
  loglevel = "OFF"
}

stdout-loglevel仅在系统启动和关闭期间有效,并将其设置为OFF,确保在系统启动或关闭期间不会记录任何内容。

记录器

日志记录是通过事件总线异步执行的。日志事件由事件处理程序 Actor 处理,该 Actor 接收日志事件的顺序与发出日志事件的顺序相同。

  • 注释:事件处理程序 Actor 有一个无界的收件箱(not have a bounded inbox),并且在默认调度程序上运行。这意味着记录大量数据可能会严重影响应用程序。不过,通过使用异步日志后端可以稍微减轻这一点,具体请参见「直接使用 SLF4J API」。

你可以配置在系统启动时创建哪些事件处理程序,并监听日志记录事件。这是使用「配置」中的loggers元素完成的。在这里,你还可以定义日志级别。基于日志源的更多细粒度筛选可以在自定义LoggingFilter中实现,该过滤器可以在logging-filter配置属性中定义。

akka {
  # Loggers to register at boot time (akka.event.Logging$DefaultLogger logs
  # to STDOUT)
  loggers = ["akka.event.Logging$DefaultLogger"]
  # Options: OFF, ERROR, WARNING, INFO, DEBUG
  loglevel = "DEBUG"
}

默认的日志输出到 STDOUT 并在默认情况下注册。不用于生产。akka-slf4j模块中还提供了一个 SLF4J 记录器。

创建监听器的示例:

import akka.actor.*;
import akka.event.Logging;
import akka.event.LoggingAdapter;

import akka.event.Logging.InitializeLogger;
import akka.event.Logging.Error;
import akka.event.Logging.Warning;
import akka.event.Logging.Info;
import akka.event.Logging.Debug;
class MyEventListener extends AbstractActor {
  @Override
  public Receive createReceive() {
    return receiveBuilder()
        .match(
            InitializeLogger.class,
            msg -> {
              getSender().tell(Logging.loggerInitialized(), getSelf());
            })
        .match(
            Error.class,
            msg -> {
              // ...
            })
        .match(
            Warning.class,
            msg -> {
              // ...
            })
        .match(
            Info.class,
            msg -> {
              // ...
            })
        .match(
            Debug.class,
            msg -> {
              // ...
            })
        .build();
  }
}

在启动和关闭期间记录到 STDOUT

当 Actor 系统启动和关闭时,不使用配置的记录器。相反,日志消息被打印到STDOUT(System.out)。此 STDOUT 记录器的默认日志级别为WARNING,可以通过设置akka.stdout-loglevel=OFF将其禁止。

SLF4J

Akka 为「SLF4J」提供了一个记录器。这个模块可以在akka-slf4j.jar中找到,它只有一个依赖项slf4j-api jar。在运行时,你还需要一个 SLF4J 后端。我们建议使用「Logback」:

<!-- Maven -->
<dependency>
  <groupId>com.typesafe.akka</groupId>
  <artifactId>akka-slf4j_2.12</artifactId>
  <version>2.5.23</version>
</dependency>
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.2.3</version>
</dependency>

<!-- Gradle -->
dependencies {
  compile group: 'com.typesafe.akka', name: 'akka-slf4j_2.12', version: '2.5.23',
  compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3'
}

<!-- sbt -->
libraryDependencies ++= Seq(
  "com.typesafe.akka" %% "akka-slf4j" % "2.5.23",
  "ch.qos.logback" % "logback-classic" % "1.2.3"
)

你需要在「配置」的loggers元素中启用Slf4jLogger。在这里,你还可以定义事件总线的日志级别。可以在 SLF4J 后端的配置中定义更细粒度的日志级别(例如logback.xml)。你还应该在logging-filter配置属性中定义akka.event.slf4j.Slf4jLoggingFilter。它将使用后端配置(例如logback.xml)过滤日志事件,然后将其发布到事件总线。

  • 警告:如果将loglevel设置为比DEBUG更高的级别,则任何调试事件都将在源中被过滤掉,并且永远不会到达日志后端,无论后端是如何配置的。
akka {
  loggers = ["akka.event.slf4j.Slf4jLogger"]
  loglevel = "DEBUG"
  logging-filter = "akka.event.slf4j.Slf4jLoggingFilter"
}

其中一个问题是时间戳是在事件处理程序中属性化的,而不是在实际执行日志记录时。

为每个日志事件选择的 SLF4J 记录器是根据创建LoggingAdapter时指定的日志源的类来选择的,除非直接将其作为字符串提供,在这种情况下使用该字符串,即LoggerFactory.getLogger(Class c)在第一种情况下使用,而LoggerFactory.getLogger(String s)在第二种情况下使用。

  • 注释:注意,如果创建了LoggingAdapter,并将ActorSystem提供给工厂,那么 Actor 系统的名称将附加到String日志源。如果不打算这样做,请提供一个LoggingBus,如下所示:
final LoggingAdapter log = Logging.getLogger(system.eventStream(), "my.string");

直接使用 SLF4J API

如果直接在应用程序中使用 SLF4J API,请记住,日志记录操作将在底层基础结构写入日志语句时阻塞。

这可以通过将日志记录实现配置为使用非阻塞附加器来避免。Logback提供了这样做的「AsyncAppender」。它还包含一个功能,如果日志负载很高,它将删除INFODEBUG消息。

MDC 中的日志线程、Akka 源和 Actor 系统

由于日志记录是异步完成的,因此执行日志记录的线程将捕获在具有属性名sourceThread的映射诊断上下文(MDC)。对于Logback,模式布局配置中的%X{sourceThread}说明符可以使用线程名称:

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  <encoder>
    <pattern>%date{ISO8601} %-5level %logger{36} %X{sourceThread} - %msg%n</pattern>
  </encoder>
</appender>
  • 注释:最好在应用程序的非 Akka 部分也使用sourceThread MDC 值,以便在日志中始终提供此属性。

另一个有用的工具是,Akka 在实例化其中的记录器时捕获 Actor 的地址,这意味着完整的实例标识可用于将日志消息(如与路由器成员关联)关联起来。此信息在属性名为akkaSource的 MDC 中可用:

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  <encoder>
    <pattern>%date{ISO8601} %-5level %logger{36} %X{akkaSource} - %msg%n</pattern>
  </encoder>
</appender>

最后,执行日志记录的 Actor 系统在属性名为sourceActorSystem的 MDC 中可用:

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  <encoder>
    <pattern>%date{ISO8601} %-5level %logger{36} %X{sourceActorSystem} - %msg%n</pattern>
  </encoder>
</appender>

有关此属性还包含哪些非 Actor 的详细信息,请参阅「如何记录日志」。

更精确的MDC日志输出时间戳

Akka 的日志记录是异步的,这意味着当调用底层的记录器实现时,日志条目的时间戳是从中获取的,这一点一开始可能令人惊讶。如果要更准确地输出时间戳,请使用 MDC 属性akkaTimestamp

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  <encoder>
    <pattern>%X{akkaTimestamp} %-5level %logger{36} %X{akkaSource} - %msg%n</pattern>
  </encoder>
</appender>

由应用程序定义的 MDC 值

SLF4J 中的一个有用功能是 MDC,Akka 可以让应用程序指定自定义值,为此,你需要使用专用的LoggingAdapter,即DiagnosticLoggingAdapter。为了获得它,你可以使用工厂,提供一个AbstractActor作为logSource

// Within your AbstractActor
final DiagnosticLoggingAdapter log = Logging.getLogger(this);

一旦你拥有了日志记录器,你就需要在记录某些内容之前添加自定义值。这样,在附加日志之前,这些值将被放入 SLF4J MDC 中,并在之后移除。

  • 注释:清理(删除)应该在 Actor 的末尾完成,否则,如果没有设置为新映射,则下一条消息将使用相同的 MDC 值记录。使用log.clearMDC()
import akka.event.DiagnosticLoggingAdapter;
import java.util.HashMap;
import java.util.Map;
class MdcActor extends AbstractActor {

  final DiagnosticLoggingAdapter log = Logging.getLogger(this);

  @Override
  public Receive createReceive() {
    return receiveBuilder()
        .matchAny(
            msg -> {
              Map<String, Object> mdc;
              mdc = new HashMap<String, Object>();
              mdc.put("requestId", 1234);
              mdc.put("visitorId", 5678);
              log.setMDC(mdc);

              log.info("Starting new request");

              log.clearMDC();
            })
        .build();
  }
}

现在,这些值将在 MDC 中可用,因此你可以在布局模式中使用它们:

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  <encoder>
    <pattern>
      %-5level %logger{36} [req: %X{requestId}, visitor: %X{visitorId}] - %msg%n
    </pattern>
  </encoder>
</appender>

使用标记

除了 MDC 数据之外,一些日志库还允许将所谓的标记(markers)附加到日志语句。这些用于过滤罕见和特殊事件,例如,你可能希望标记检测到某些恶意活动的日志,并用SECURITY标记对其进行标记,并且在你的附加器配置中,立即触发电子邮件和其他通知。

当通过Logging.withMarker获得标记时,也可以通过LoggingAdapters获得标记。传递给所有日志调用的第一个参数应该是akka.event.LogMarker

Akka 在akka-slf4j中提供的slf4j桥将自动获取该标记值,并使其对 SLF4J 可用。例如,你可以这样使用它:

<pattern>%date{ISO8601} [%marker][%level] [%msg]%n</pattern>

更高级的(包括大多数 Akka 添加的信息)示例模式是:

<pattern>%date{ISO8601} level=[%level] marker=[%marker] logger=[%logger] akkaSource=[%X{akkaSource}] sourceActorSystem=[%X{sourceActorSystem}] sourceThread=[%X{sourceThread}] mdc=[ticket-#%X{ticketNumber}: %X{ticketDesc}] - msg=[%msg]%n----%n</pattern>

使用 SLF4J 的标记

使用 SLF4J 时,也可以将org.slf4j.MarkerLoggingAdapter一起使用。

由于akka-actor避免依赖于任何特定的日志记录库,因此对它的支持包括在akka-slf4j中,它提供了Slf4jLogMarker类型,可以作为第一个参数传递,而不是akka-actor传递日志框架不可知的日志标记类型。两者之间最显著的区别是,SLF4J 的标记可以有子标记,因此可以使用子标记来依赖更多的信息,而不仅仅是一个字符串。

java.util.logging

Akka 包含一个「java.util.logging」的记录器。

你需要在配置的loggers元素中启用akka.event.jul.JavaLogger。在这里,你还可以定义事件总线的日志级别。可以在日志后端的配置中定义更细粒度的日志级别。你还应该在logging-filter配置属性中定义akka.event.jul.JavaLoggingFilter。它将在日志事件发布到事件总线之前使用后端配置过滤日志事件。

  • 警告:如果将loglevel设置为比DEBUG更高的级别,则任何调试事件都将在源中被过滤掉,并且永远不会到达日志后端,无论后端是如何配置的。
akka {
  loglevel = DEBUG
  loggers = ["akka.event.jul.JavaLogger"]
  logging-filter = "akka.event.jul.JavaLoggingFilter"
}

其中一个问题是时间戳是在事件处理程序中属性化的,而不是在实际执行日志记录时。

为每个日志事件选择的 java.util.logging.Logger是根据创建LoggingAdapter时指定的日志源的类来选择的,除非它直接作为字符串提供,在这种情况下使用该字符串,即在第一种情况下使用LoggerFactory.getLogger(Class c),在第二种情况下使用LoggerFactory.getLogger(String s)

  • 注释:注意,如果创建了LoggingAdapter,并将ActorSystem提供给工厂,那么 Actor 系统的名称将附加到String日志源。如果不打算这样做,请提供一个LoggingBus,如下所示:
final LoggingAdapter log = Logging.getLogger(system.eventStream(), "my.string");

英文原文链接Logging.


———— ☆☆☆ —— 返回 -> Akka 中文指南 <- 目录 —— ☆☆☆ ————