NO IMAGE
1 Star2 Stars3 Stars4 Stars5 Stars 給文章打分!
Loading...

Akka中的日誌不會依賴於特定的日誌後端。預設情況下,日誌訊息會列印到標準輸出STDOUT,但是你可以插入SLF4J日誌記錄器或者你自己的日誌記錄器。日誌是非同步執行的,以確保日誌具有最小的效能開銷。日誌通常意味著IO和鎖定,如果程式碼時非同步執行的,那麼IO和鎖定就會減慢程式碼的操作。

如何記錄日誌

建立日誌介面卡,使用error、warning、info或debug方法,正如下面這個例子說明:

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的第二個引數是日誌通道的源。源物件被會轉換為一個字串,規則如下:

· 如果是Actor或ActorRef,就使用它的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

    }

  }

}

如果你想要繼承自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的日誌選項: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類來完成這個功能。它還有一個特性,就是如果日誌負載太重,它也會丟棄INFO和DEBUG訊息。

MDC中的日誌執行緒、Akka
Source和Actor 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中一個有用的特性就是MDC,Akka有讓應用指定自定義值的方式,你只需要獲取特定的LoggingAdapter:DiagnosticLoggingAdapter。為了獲得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>

相關文章

程式語言 最新文章