[Akka] Classic Logging 2

smlee·2023년 9월 14일
0

Akka

목록 보기
25/50
post-thumbnail

Loggers

로깅은 이벤트 버스(event bus)를 통해 비동기적으로 수행된다. 로그 이벤트들은 로그 이벤트들이 방출된 것과 동일한 순서로 수신되는 이벤트 핸들러 액터에 의해 처리된다.

이벤트 핸들러 액터는 유계된 받은편지함을 갖지 않고 디폴트 디스패처 상에서 실행된다. 이것은 극단적인 양의 데이터를 로깅하는 것이 당신의 애플리케이션에 나쁜 영향을 미칠 수 있다는 것을 의미한다. 그러나 비동기식 로깅 백엔드를 사용함으로써 이것은 어느 정도 완화될 수 있다.

시스템 시작 시 어떤 이벤트 핸들러가 생성되는지 구성하고 로깅 이벤트를 들을 수 있다. 이는 구성 내의 로거 요소를 사용하여 수행된다. 여기서 로그 레벨을 정의할 수도 있다. 로그 소스에 기초한 보다 세분화된 필터링은 사용자 지정 LoggingFilter에서 구현될 수 있으며, 이는 로깅-필터 구성 속성에 정의될 수 있다.

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"
}

위의 configuration은 STDOUT에 로깅되고 default로 등록된다. 생산용으로 사용하는 것은 아니다. akka-slf4j' 모듈에서 사용 가능한 SLF4J 로거도 있다.

밑은 리스너를 만드는 예시이다.

import akka.event.Logging.Debug
import akka.event.Logging.Error
import akka.event.Logging.Info
import akka.event.Logging.InitializeLogger
import akka.event.Logging.LoggerInitialized
import akka.event.Logging.Warning

class MyEventListener extends Actor {
  def receive = {
    case InitializeLogger(_)                        => sender() ! LoggerInitialized
    case Error(cause, logSource, logClass, message) => // ...
    case Warning(logSource, logClass, message)      => // ...
    case Info(logSource, logClass, message)         => // ...
    case Debug(logSource, logClass, message)        => // ...
  }
}

이벤트리스너 역시 액터로 선언하며, 옵션에 따라 다른 기능을 수행하도록 한다.

시작할 때와 종료할 때 std를 Logging하기

액터 시스템이 시동되고 종료될 때 구성된 로거가 사용되지 않는다. 대신 로그 메시지는 stdout(System.out)으로 인쇄된다. 이 stdout 로거의 기본 로그 레벨은 WARNING이며 akka.stdout-loglevel=OFF를 설정하여 완전히 silence화할 수 있다.

SLF4J

Akka는 SLF4J를 위한 로거를 제공한다. 이 모듈은 'akka-slf4j.jar'에서 사용할 수 있다. slf4j-api jar라는 단일 의존성을 가지고 있다. 런타임에 SLF4J 백엔드도 필요하다.

밑은 SLF4J를 위한 maven 설정이다.

<properties>
  <scala.binary.version>2.13</scala.binary.version>
</properties>
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.typesafe.akka</groupId>
      <artifactId>akka-bom_${scala.binary.version}</artifactId>
      <version>2.8.4</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>
<dependencies>
  <dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-slf4j_${scala.binary.version}</artifactId>
  </dependency>
  <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.12</version>
  </dependency>
</dependencies>

configuration의 loggers 요소에서 Slf4jLogger를 활성화해야 한다. 여기서 이벤트 버스의 로그 레벨을 정의할 수도 있다. 보다 세분화된 로그 레벨은 SLF4J 백엔드(예를 들어 logback.xml)의 구성에서 정의할 수도 있다. 로그-필터 구성 속성에서 Slf4jLoggingFilter를 정의해야 한다. 로그 이벤트가 이벤트 버스에 퍼블리시되기 전에 백엔드 구성(예를 들어 logback.xml)을 사용하여 필터링할 것이다.

akka {
  loggers = ["akka.event.slf4j.Slf4jLogger"]
  loglevel = "DEBUG"
  logging-filter = "akka.event.slf4j.Slf4jLoggingFilter"
}

각 로그 이벤트에 대해 선택된 SLF4J 로거는 해당 문자열이 사용되는 문자열로 직접 지정되지 않는 한 LoggingAdapter를 생성할 때 지정된 로그 소스의 Class[_]를 기준으로 선택된다. (즉, 첫 번째 경우에는 LoggerFactory.getLogger(c: Class[_]), 두 번째 경우에는 LoggerFactory.getLogger(s: String)가 사용된다).

val log = Logging(system.eventStream, "my.nice.string")

위와 같이 로그를 남길 수 있다.

SLF4J 직접 사용하기

응용프로그램에서 SLF4J API를 직접 사용하는 경우 기본 인프라스트럭처가 로그 문을 작성하는 동안 로깅 작업이 차단됩니다.

이것은 로깅 구현이 non-blocking Appender를 사용하도록 구성함으로써 피할 수 있다. 로그백은 이것을 수행하는 AsyncAppender를 제공한다.

Logback Configuration

로그백에는 유연한 구성 옵션이 있으며 자세한 내용은 로그백 매뉴얼 및 기타 외부 리소스에서 확인할 수 있습니다.

한 가지 강조해야 할 점은 AsyncAppender를 구성하는 것의 중요성인데, 이는 로깅 이벤트의 렌더링을 백그라운드 스레드로 오프로드하여 성능을 향상시키기 때문이다. 기본 인프라스트럭처가 로그 메시지를 디스크나 구성된 다른 대상에 쓰는 동안에는 ActorSystem의 스레드를 차단하지 않는다. 또한 로깅 로드가 높을 경우 INFO 및 DEBUG 메시지를 드롭하는 기능도 포함되어 있다.

운영을 위한 logback.xml 구성 시작점은 밑과 같다.

<?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}] [%level] [%logger] [%marker] [%thread] - %msg MDC: {%mdc}%n</pattern>
        </encoder>
    </appender>

    <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
        <queueSize>8192</queueSize>
        <neverBlock>true</neverBlock>
        <appender-ref ref="FILE" />
    </appender>

    <root level="INFO">
        <appender-ref ref="ASYNC"/>
    </root>
</configuration>

개발을 위해 표준 아웃으로 로그아웃할 수도 있지만, 다음 예제와 같이 모든 DEBUG 레벨 로깅을 파일로 저장할 수도 있다.

<?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}] [%level] [%logger] [%marker] [%thread] - %msg MDC: {%mdc}%n</pattern>
        </encoder>
    </appender>

    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>target/myapp-dev.log</file>
        <encoder>
            <pattern>[%date{ISO8601}] [%level] [%logger] [%marker] [%thread] - %msg MDC: {%mdc}%n</pattern>
        </encoder>
    </appender>

    <root level="DEBUG">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>

src/main/resources/logback.xml에 logback.xml 파일을 배치한다. 테스트의 경우 src/test/resources/logback-test.xml에서 다른 로깅 구성을 정의할 수 있다.

0개의 댓글