AKKA日志

Akka中的日志不会依赖于特定的日志后端。默认情况下,日志消息会打印到标准输出STDOUT,但是你可以插入SLF4J日志记录器或者你自己的日志记录器。日志是异步执行的,以确保日志具有最小的性能开销。日志通常意味着IO和锁定,如果代码时异步执行的,那么IO和锁定就会减慢代码的操作

如何记录日志

创建日志适配器,使用errorwarninginfodebug方法,正如下面这个例子说明:

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

class MyActor extends UntypedActor {

    LoggingAdapter log = Logging.getLogger(getContext().system(), this);

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

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

   public void onReceive(Object message) {
        if (message.equals("test")) {
            log.info("Received test");
        } else {
            log.warning("Received unknown message: {}", message);
        }
    }
}

Logging.getLogger的第一个参数也可能是任意的日志总线(LoggingBus),尤其是system.eventStream();在示例里,actor system的地址包含在日志源的akkaSource表示(参见Logging Thread, Akka Source and Actor System in MDC),而在第二个示例里,这不是自动完成的。Logging.getLogger的第二个参数是日志信道的源。源对象被会转换为一个字符串,规则如下:

· 如果是ActorActorRef,就使用它的path

· 如果是字符串,就直接用

· 如果是Class,使用它的simpleName的近似值

· 所有其它的场景,使用它的Class的simpleName

日志消息可能包含参数占位符{},如果日志级别被使能,那么这个占位符就会被代替。给予更多的参数作为占位符将导致在输出到日志语句时发生警告(即同一行具有相同的严重性)。你可能需要传入一个Java数组,因为这唯一的替代参数中的元素都被独立对待:

    finalObject[] args = new Object[] {"The", "brown","fox", "jumps", 42 };

system.log().debug("five parameters: {}, {}, {}, {}, {}", args);

日志源的Java类也包含在生成的LogEvent中。如果是简单的字符串,那么会用标记类akka.event.DummyClassForStringSources代替,以允许特殊的场景,例如SLF4J事件监听器。SLF4J事件监听器会使用字符串代替类名来查找要使用的日志实例。

死信的日志

默认情况下,发送给死信的消息按info级别记录。存在死信并不表明存在问题,但是也可能存在问题,因此默认情况下它们是记日志的。在几个消息之后,这个日志被关闭,避免洪泛日志。你可以完全禁止这个日志,来调整要记录多少死信。在系统关闭过程中,你有可能看到死信,由于推迟将actor邮箱中的消息发送给死信。在关闭过程中,你也可以关闭死信的日志:

akka {

  log-dead-letters = 10

  log-dead-letters-during-shutdown = on

}

为了进一步自定义日志或者对死信执行其它动作,你可以订阅事件流Event Stream

附加的日志选项

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 et.c.)

      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

    }

  }

}

如果你想要继承自LoggingFSMFSM 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的日志选项:Tracing Actor Invocations.

关闭日志

为了关闭日志,你可以配置日志级别为OFF

akka {

  stdout-loglevel = "OFF"

  loglevel = "OFF"

}

stdout-loglevel只影响系统的启动和关闭,将它设置为OFF,那么确保在系统启动和关闭时没有任何日志。

日志记录器

日志是通过事件总线异步执行的。Log事件有事件处理器Actor处理,它会按照日志事件发出的顺序接收日志事件。

注意:事件处理器Actor没有有界有限,并且运行在默认的分发器上。这意味着记录大量日志数据可能会非常影响应用性能。从某种程度上说,使用异步日志后端会减少这个影响。(参见直接使用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 UntypedActor {

    public void onReceive(Object message) {
        if (message instanceof InitializeLogger) {
            getSender().tell(Logging.loggerInitialized(), getSelf());
        } else if (message instanceof Error) {
            // ...
        } else if (message instanceof Warning) {
            // ...
        } else if (message instanceof Info) {
            // ...
        } else if (message instanceof Debug) {
            // ...
        }
    }
}

在启动和关闭过程中记录日志到标准输出

虽然配置了日志记录器,但是actor系统正在启动或者关闭时,日志记录器并不会被使用。取而代之的是,日志消息打印到标准输出System.out。这个标准输出的日志记录器的日志级别是WARNING,通过设置akka.stdout-loglevel=OFF,它会完全沉默的。

SLF4J

Akka提供了SL4FJ的日志记录器。这个模块位于akka-slf4j.jar。它有一个依赖slf4j-api jar。在运行时,你也需要SLF4J后端,我们推荐Logback

<dependency>

  <groupId>ch.qos.logback</groupId>

  <artifactId>logback-classic</artifactId>

  <version>1.1.3</version>

</dependency>

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

警告

如果你设置日志级别比"DEBUG"更高,任何DEBUG事件都将在源被过滤,决不会达到日志后端,不管后端是如何配置的:

akka {

  loggers = ["akka.event.slf4j.Slf4jLogger"]

  loglevel = "DEBUG"

  logging-filter = "akka.event.slf4j.Slf4jLoggingFilter"

}

时间戳是在事件处理器中加上的,而不是实际记录日志时。

当创建LoggingAdapter,每一个事件选择的SLF4J日志记录器都是基于特定日志源的Class选择的,除非直接给出了字符串(即,第一种情况使用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 SourceActor System

由于记录日志是异步的,执行日志记录的线程被映射诊断上下文(Mapped Diagnostic Context MDC)以属性名sourceThread捕获。Logback中线程名可在pattern配置中用%X{sourceThread}获取:

<appendername="STDOUT"class="ch.qos.logback.core.ConsoleAppender">

  <encoder>

    <pattern>%date{ISO8601} %-5level %logger{36} %X{sourceThread} - %msg%n</pattern>

  </encoder>

</appender>

注意:为了让sourceThread MDC值在日志中保持一致性,在应用的非Akka部分使用sourceThread MDC值可能是个好主意。

另一个有用的工具是当在actor内实例化日志记录器时,Akka可以捕获actor的地址,意思是说完整的实例标识对于相关的日志消息来说都是可用的,例如router成员。这个信息可在MDC中使用属性名akkaSource获取到:

<appendername="STDOUT"class="ch.qos.logback.core.ConsoleAppender">

  <encoder>

    <pattern>%date{ISO8601} %-5level %logger{36} %X{akkaSource} - %msg%n</pattern>

  </encoder>

</appender>

最后,执行日志的actor系统也可以再MDC中用属性名sourceActorSystem 获取到:

<appendername="STDOUT"class="ch.qos.logback.core.ConsoleAppender">

  <encoder>

    <pattern>%date{ISO8601} %-5level %logger{36} %X{sourceActorSystem} - %msg%n</pattern>

  </encoder>

</appender>

想要了解这个属性包含哪些细节,请参考How to Log

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

令人诧异的是,Akka日志是异步的,这意味着日志条目的时间戳是调用底层日志实现的时间。如果你想要更加精确的输出时间戳,使用MDC的属性akkaTimestamp:

<appendername="STDOUT"class="ch.qos.logback.core.ConsoleAppender">

  <encoder>

    <pattern>%X{akkaTimestamp} %-5level %logger{36} %X{akkaSource} - %msg%n</pattern>

  </encoder>

</appender>

由应用定义MDC

Slf4j中一个有用的特性就是MDCAkka有让应用指定自定义值的方式,你只需要获取特定的LoggingAdapterDiagnosticLoggingAdapter。为了获得DiagnosticLoggingAdapter,你将使用接收UntypedActor类型作为logSource的工长:

// Within your UntypedActor

final DiagnosticLoggingAdapter log = Logging.getLogger(this);

一旦你获取到这个日志记录器,你只需要在记录任何日志之前添加自定义的值即可。在输出日志之前,这种方式会将这些值会正确地放入SLF4J MDC中;输出日志后再移除。

注意:在actor中清理(移除)应该再最后做,否则下一个消息将会以同样的MDC值记录日志。使用log.clearMDC()

import akka.actor.UntypedActor;
import akka.event.DiagnosticLoggingAdapter;
import akka.event.Logging;
import java.util.HashMap;
import java.util.Map;

class MdcActor extends UntypedActor {

    final DiagnosticLoggingAdapter log = Logging.getLogger(this);

    @Override
    publicvoid onReceive(Object message) {
        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();
    }
}

现在,这些值在MDC都是可用的,你可以再布局模式中使用它们:

<appendername="STDOUT"class="ch.qos.logback.core.ConsoleAppender">

  <encoder>

    <pattern>

      %-5level %logger{36} [req: %X{requestId}, visitor: %X{visitorId}] - %msg%n

    </pattern>

  </encoder>

</appender>

使用标记Marker

一些日志库允许在日志消息中附加所谓的“标记”,除了MDC数据。这些标记用于过滤极少的特定事件,例如你可能标记标记日志,检测到了一些恶意活动,并将它们标记为ECURITY标签。在你的输出源配置中,让这些标记立即触发邮件或者其他的通知。

当获取LoggingAdapter时,Marker可通过Logging.withMarker获取。第一个传入的参数被传递给所有的日志调用,然后是akka.event.LogMarker

akka-slf4j模块中提供的slf4j桥会自动获取这个marker值,对于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>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值