관리 메뉴

HAMA 블로그

예제로 보는 아카(akka) - 16. 로깅 (Logging) 본문

Akka

예제로 보는 아카(akka) - 16. 로깅 (Logging)

[하마] 이승현 (wowlsh93@gmail.com) 2016. 12. 26. 16:20

- Scala 2.11.8 기반 

- Akka 2.4.11 기반 

- Java 8  (akka 2.4 부터는 java 8 요구함. scala 2.11 은 java 7도 괜찮지만~) 



로깅 (Logging)


실전에서 로깅은 매우 중요합니다. 아카 프레임워크는 내부 로깅 시스템도 있으며 외부 로깅프레임워크 또한 지원 하는데요. 이렇게 혼재하다보니 처음에 아카를 할 때 로깅에 애를 먹을 수 도 있습니다. 다음 예를 따라해보면서 감을 익혀 보겠습니다. 

* self4j 나 logback 그 자체에 대한 정보는 다른글을 참고하세요.
* 모두 테스트하여 제대로 작동되는 것을 확인하였습니다.
* 하지만 이게 최선의 방법이라거나 제가 설명한 내용이 100% 정확하다는 보장은 못해드립니다. OTL.



0. 기본 프로젝트 생성   


가장 기본적인 프로젝트를 하나 만듭니다. 아래와 같이 간단한 코드를 작성합니다. 

class HelloWorld extends Actor {
override def receive: Receive = {
case msg : String => println(s"hi $msg")
}
}


object maintest extends App{
println("application start") <-------- (1)
val system = ActorSystem("helloAkka")
val actor = system.actorOf(Props[HelloWorld])

actor ! "hama"
}

처음엔 편하게  (1)  println 을 이용하여 로그를 출력합니다.  stdout 으로 출력됩니다.


1. 로깅  - 내부로깅 akka.event.Logging 사용

import akka.event.Logging class HelloWorld extends Actor {

val log = Logging(context.system, this)

override def receive: Receive = {
case msg : String => log.info(s"hi $msg")
}
}


- 이번에는 Logging 을 사용하여 출력 합니다.
- 이 경우는 Akka 내부에 가지고 있는 로깅객체를 이용합니다. akka.evnet.Logging 을 임포트 하였습니다.
- 인자로 ActorSystem 이 필요합니다.
- 이것도 역시 stdout 으로 출력됩니다. 


2. 로깅  - 내부 로깅 ActorLogging 사용  

import akka.actor.ActorLogging

class HelloWorld extends Actor with ActorLogging{

override def receive: Receive = {
case msg : String => log.info(s"hi $msg")
}
}

- 이번에는 ActorLogging 을 상속받아서 사용합니다.
-  akka.actor.ActorLogging 을 임포트 하였습니다.
- 이것도 역시 stdout 으로 출력됩니다. 

 resources/application.conf 를 만들고 아래와 같이 넣어주면 

akka {
loggers = ["akka.event.Logging$DefaultLogger"]
loglevel = "error"
}

- error 이상만 출력되도록 했기 때문에 위에 로깅이 출력되지 않습니다.
- loglevel = "debug" 로 하면 잘 출력되겠지요. debug 까지 출력됩니다.( debug - info - warning - error )

아래 처럼 {}  (placeholder) 를 이용하여 여러 정보를 간편히 넣을 수 있습니다.

log.info(s"hi $msg and {} , {} ", "one", "two")


3. 로깅  -  외부 프레임워크 사용해서 file 에도 쓰기

build.sbt 에 외부 로깅프레임워크에 대한  의존성을 추가합니다.

libraryDependencies ++= Seq(
"ch.qos.logback" % "logback-classic" % "1.1.7",
"com.typesafe.akka" % "akka-slf4j_2.11" % "2.4.8")

* 읽을거리 : logback 을 사용해야 하는 이유 


resources/application.conf  에는 아래와 같이 넣어주시구요. 


akka {
# event-handlers = ["akka.event.slf4j.Slf4jEventHandler"]
loggers = ["akka.event.slf4j.Slf4jLogger"]
loglevel = "DEBUG"
}


 원격 actor 일 경우는 다음과 같습니다. 

Provider {
akka {
loggers = ["akka.event.slf4j.Slf4jLogger"]
loglevel = "DEBUG"
actor.provider = "akka.remote.RemoteActorRefProvider"
remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
hostname = "192.168.1.45"
port = 24321
}
}
}

즉  remote 액터가 설정되있으면 여기도 loggers 설정해줘야 remote 액터들의 로그도 logback.xml 설정대로 작동됩니다.


* 로깅 전략 

resources/logback.xml 을 만들어서 아래와 같이 넣어주십시요.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<target>System.out</target>
<encoder>
<pattern>%X{akkaTimestamp} %-5level[%thread] %logger{0} - %msg%n</pattern>
</encoder>
</appender>

<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/akka.log</file>
<append>true</append>
<encoder>
<pattern>%date{yyyy-MM-dd} %X{akkaTimestamp} %-5level[%thread] %logger{1} - %msg%n</pattern>
</encoder>
</appender>

<logger name="akka" level="DEBUG" />

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

</configuration>

- logs/akka.log 파일에 로그가 출력됩니다.
- DEBUG 레벨 이상 모두 로깅됩니다. 


위의 FILE Appender 의 경우 하나의 파일에 계속 로깅을 합니다. 따라서 로그파일을 압축시키거나 날짜별로 관리해줄 필요가 생깁니다. 이때는 다른 Appender 를 만들어서 사용하면 됩니다.


* 파일 용량별 구분  Appender 

<!-- 용량이 차면 압축해주는 로깅 --> 
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/hello.log</file>
<encoder>
<pattern>%date %level %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<maxIndex>10</maxIndex>
<FileNamePattern>logs/hello.log.%i.gz</FileNamePattern>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>10MB</maxFileSize>
</triggeringPolicy>
</appender>

- 위의 경우는 10MB 이내의 10개의 압축파일을 만들며 , 그 이후에는 압축하지 않고 계속 하나의 파일에 쓰게 됩니다.


* 날짜별 구분 파일 Appender


<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>

<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{dd-MM-yyyy}.log.gz</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>

<encoder>
<pattern>%date %level %msg%n</pattern>
</encoder>
</appender>

- 날짜별로 하나의 로그파일을 저장해 둡니다.
- 최근 30일치의 로그를 남겨둡니다. 즉 하루가 지나면 가장 옛날 로그파일 하나를 지웁니다.


팁 : 파일이름 뒤에 .zip .gz 처럼 압축형식을 적어주면 자동으로 압축해서 보관합니다.


* 로그 사용 방법 

기존 처럼 아래와 같이 사용하시면 됩니다.

import akka.actor.ActorLogging

class HelloWorld extends Actor with ActorLogging{

override def receive: Receive = {
case msg : String => log.info(s"hi $msg")
}
}


기존에는 stdout 에만 출력되었다면 이제는 file에도 출력됩니다. logs/로그파일명.log 파일을 확인하세요.

2016-12-26  INFO [helloAkka-akka.actor.default-dispatcher-4] a.e.s.Slf4jLogger - Slf4jLogger started
2016-12-26 08:41:11.329UTC DEBUG[helloAkka-akka.actor.default-dispatcher-4] a.e.EventStream - logger log1-Slf4jLogger started
2016-12-26 08:41:11.331UTC DEBUG[helloAkka-akka.actor.default-dispatcher-4] a.e.EventStream - Default Loggers started
2016-12-26 08:41:11.350UTC INFO [helloAkka-akka.actor.default-dispatcher-4] HelloWorld - hi hama


4. ActorLogging 과 scala-logging 의 혼용 

액터 내에서 로거 인스턴스를 제공해야 할 때 ActorLogging 을 사용하는 것은 자연스러운 일입니다. 그러나 때때로 scala-logging(예 : LazyLogging 또는 StrictLogging)으로 로깅하는 것을 볼 수 있습니다. 일반적으로 액터 모델이 Dataflow or Reactive Stream 을 만나는 코드에서 볼 수 있는데요. 이 두 솔루션을 서로 같이 사용할 수 있을까요? 자세한 내용을 살펴 보겠습니다.

ActorLogging은 Akka와 깊이 통합되어 있습니다. 기본적으로 Mapping Diagnostic Context (DiagnosticActorLogging 참조)에는 액터 트리에있는 액터의 전체 경로를 비롯하여 많은 유용한 정보가 있습니다. 또한 로깅 설정 중 일부는 Akka 구성과 공유되기 때문에 application.conf에서 akka.loglevel을 변경하면 액터가 보고하는 로그에 영향을 미칩니다. 또한 ActorLogging에서 제공하는 로그 인스턴스는 액터 내부에서 사용되기 때문에 지연 된 계산 (예 : Futures)에 전달하지 말아야한다는 점도 중요합니다.

반면 scala-logging 은 보다 일반적인 솔루션입니다. Akka와의 연결이 없으므로 액터의 적절한 컨텍스트를 제공 할 수 없습니다. 또한 로깅 구성은 Akka 설정과 완전히 분리되어 있습니다. 로거 인스턴스를 보호 할 필요가 없으므로 필요할 때마다 전달할 수 있습니다.

요약하면, Actor 본문 내부에서 로깅이 발생할 때마다 ActorLogging을 사용하여 로깅 정보의 컨텍스트를 유지합니다. 다른 모든 장소에서는 스칼라 로깅을 사용하십시오.

예)

의존성 추가

libraryDependencies += "com.typesafe.scala-logging" % "scala-logging-slf4j_2.11" % "2.1.2"


 ActorLogging 과 LazyLogging 을 함께 사용하는 모습


import akka.actor.{Actor, ActorLogging, ActorSystem, Props}
import com.typesafe.scalalogging.slf4j.LazyLogging

/**
* Created by hama on 2016-12-26.
*/


class HelloWorld extends Actor with ActorLogging with LazyLogging {

override def receive: Receive = {
case msg : String =>
log.info(s"hi $msg")
logger.info("strictLogging...........................")
}
}




참고) 

- Akka in Action

https://sachabarbs.wordpress.com/2016/08/03/akka-logging/

Comments