본문 바로가기

IT/papers

Log4j for java

작성일 : 2010-09-30
작성자 : joanne

log4j의 중요 컴포넌트 설명

Loggers, Appenders and Layouts

  • Log4j는 Logger, Appender, Layout 3개의 구성 요소가 있다.
  • Log4j를 설정하는 것은 Category(logger)에 Appender (목적지/대상/핸들러)를 더하고 각각의 Appender 에게 Layout(구성)을 지정하는 것이다.

Loggers

  • 로깅을 사용하는 방법 중에 하나는 프로그램 코드 내에 로깅을 위한 구문(System.out.print)를 넣고 실행시킨 후 생성된 로그를 보고 분석하는 것이다. 하지만 이 때의 문제점은 생성되는 로그의 표준이 없어 다른 사람들이 알기 힘들다는 점이다.
  • logging API를 사용하면 프로그래머가 정의한 대로, 정의한 곳에 로그를 출력할 수 있게 되며 표준 정립이 가능하게 된다. 포맷을 통일할 수 있으며, 콘솔에 뿌려주는 것 이외에도 경우에 따라서 로그파일을 따로 남기는 등 다양한 방식의 출력이 가능하다.

Logger의 특징

  1. 대소문자를 구분한다.
  2. 계층적 naming rule을 따른다.

    com.foo라는 이름을 가진 logger는 com.foo.Bar라는 이름을 가진 logger의 조상이 된다.

  3. 최상위 계층에 존재하는 logger는 Root Logger이다. 이름이 따로 존재하지 않는다.

    이름이 존재하지 않기 때문에 Root Logger를 사용하려면 Logger.getRootLogger로 사용할 수 있다.

  4. Logger.getLogger(classname.class)의 형식으로 사용한다.

Logger의 6가지 레벨

  • org.apache.log4j.Level class를 보면 logger의 레벨을 TRACE, DEBUG, INFO, WARN, ERROR, FATAL 6가지로 규정한다.
  • TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF 오른쪽으로 갈 수록 로그 출력이 위험수위가 높은 것만 출력된다고 볼 수 있다.
  • 자식 logger에 별다른 level이 지정되어 있지 않으면 자식 logger는 조상 logger의 level을 상속 받는다.
  • level q에서의 level p의 로그 요청은 p>=q를 만족해야한다.
       // com.foo라는 이름의 logger instance 'logger' 생성
       Logger  logger = Logger.getLogger("com.foo");
    
       // leve(INFO) 설정
       logger.setLevel(Level.INFO);
    
       logger.warn("Low fuel level.");
    
       // logger는 INFO로 설정되어 있으므로 DEBUG < INFO로 p>=q공식을 만족하지 않아 disable된다.
       logger.debug("Starting search for nearest gas station.");
    
  • 같은 이름으로 getLogger를 호출했다면, 그 logger는 같은 object를 참조한다.
    Logger x = Logger.getLogger("wombat");
    Logger y = Logger.getLogger("wombat");
    

Appenders

  • Appender는 이름에서 알 수 있듯이 로그를 출력하는 위치를 말한다.
  • remote socket server, JMS, NT Event Loggers, remote UNIX Syslog daemon 등 하나 하나를 Appender 라고 한다. 하나의 Logger 에는 여러 개의 Appender 를 설정할 수 있다.

Appender의 종류

  • http://logging.apache.org/log4j/docs/api/index.html
    Appender comments
    ConsoleAppender 로그 메시지를 콘솔에 출력
    DailyRollingFileAppender 로그 메시지를 파일로 저장
    DatePattern 옵션에 따라 원하는 기간마다 로그파일을 갱신
    JDBCAppender 로그 메시지를 DB에 저장
    RollingFileAppender 로그 메시지를 파일로 저장. 설정된 size를 초과하면 로그파일이 갱신됨.
    SocketAppender 로그 메시지를 socket을 이용해서 지정된 곳으로 보냄
    TelnetAppender 로그 메시지를 telnet을 통해 보냄. 원격 모니터링, 특히 servlet의 모니터링에 유용함.

Appender Additivity

  • Logger의 상속처럼 Appender 또한 상속 관계를 가질 수 있는데 이를 Appender Additivity라고 한다. 이는 Logger가 가지고 있는 additivity의 설정값에 따라 변경될 수 있다.
    Logger 이름 설정된 Appenders Additivity Flag 적용 Appender 설명
    root A1 설정 불가 A1 Root 의 additivity 값은 설정할 수 없다.
    x A-x1, A-x2 TRUE A1, A-x1, A-x2 additivity 값이 true 이므로 부모의 appender 도 사용하게 된다.
    x.y none TRUE A1, A-x1, A-x2 additivity 값이 true 이므로 부모 x Logger 에 적용된appender 도 사용하게 된다.
    x.y.z A-xyz1 TRUE A1, A-x1, A-x2, A-xyz1 additivity 값이 true 이므로 부모 x Logger 에 적용된appender 도 사용하게 된다.
    security A-sec FALSE A-sec additivity 값이 false 이므로 부모의 appender 는 사용하지 않는다.
    security.access none TRUE A-sec Additivity 값이 true 이므로 부모의 적용된 appender를 사용하게 된다.

Appender 사용하기

  • ConsoleAppender 옵션
    Threadhole=WARN : category 의 priority가 더 낮게 지정되어 있더라도 여기 명시된 priority 보다 낮은 메세지들은 로깅하지 않는다.
    ImmediateFlush=true : 기본값은 true 로그메세지가 버퍼되지 않는다.
    Target=System.err : 기본값은 System.out
    
  • FileAppender 옵션
    Threadhole=WARN : category 의 priority가 더 낮게 지정되어 있더라도 여기 명시된 priority 보다 낮은 메세지들은 로깅하지 않는다.
    ImmediateFlush=true : 기본값은 true 로그메세지가 버퍼되지 않는다.
    File=testlog.txt : 로깅할 파일명
    Append=false : 기본값은 true이며, 파일끝에 추가하는 것을 의미한다.
    
  • RollingFileAppender 옵션
    Threadhole=WARN : category 의 priority가 더 낮게 지정되어 있더라도 여기 명시된 priority 보다 낮은 메세지들은 로깅하지 않는다.
    ImmediateFlush=true : 기본값은 true 로그메세지가 버퍼되지 않는다.
    File=testlog.txt : 로깅할 파일명
    Append=false : 기본값은 true이며, 파일끝에 추가하는 것을 의미한다.
    MaxFileSize=100KB : KB, MB, GB 의 단위를 사용, 지정한 크기에 도달하면 로그파일을 교체한다.
    MaxBackupIndex=5 : 최대 5개의 백업 파일을 유지한다. 
    
  • DailyRollingFileAppend 옵션
    Threadhole=WARN : category 의 priority가 더 낮게 지정되어 있더라도 여기 명시된 priority 보다 낮은 메세지들은 로깅하지 않는다.
    ImmediateFlush=true : 기본값은 true 로그메세지가 버퍼되지 않는다.
    File=testlog.txt : 로깅할 파일명
    Append=false : 기본값은 true이며, 파일끝에 추가하는 것을 의미한다.
    DatePattern='.'yyyy-mm : 매월 파일을 교체한다. 교체주기는 월, 주, 일, 시간, 분 별로 정할 수 있다.
    
  • Appender Addictivity 때문에 똑같은 로그가 두번 출력되는 경우가 생긴다. 이 때, rootLogger를 상속하지 않고 패키지에 설정된 로그만 표현하고자 할 때 log4j.additivity.classname=false로 지정해 주면 이러한 현상을 제거할 수 있다.

Layout

  • 지정된 Layout기능을 이용해서 로그 포맷팅이 가능하다.
    characters comment
    %c 클래스 명을 출력한다
    %p debug, info, warn, error, fatal 등의 로깅레벨이 출력된다.
    %m 로그내용(코드상에서 설정한 내용)이 출력
    %d 로깅 이벤트가 발생한 시간이 출력
    %t 로그이벤트가 발생된 쓰레드의 이름을 출력
    %% % 표시를 출력하기 위해 사용한다.
    %n 행바꿈
    %F 로깅이 발생한 프로그램 파일명
    %l 로깅이 발생한 라인의 정보
    %L 로깅이 발생한 라인번호
    %M 로깅이 발생한 method 명
    %r 어플리케이션 시작 이후 부터 로깅이 발생한 시점의 시간(milliseconds)
  • Pattern Layout 설정 포맷 예제
    pattern output
    " %r%t %-5p %c - %m%n" 176main INFO org.foo.Bar - Located nearest gas station.

Quick Start

환경 설정

  • 아래의 위치에서 파일을 다운로드 받는다.
    http://logging.apache.org/log4j/1.2/download.html
  • 원하는 장소에 압축을 푼다.
  • \logging-log4j-1.2.14\logging-log4j-1.2.14\dist\lib\log4j-1.2.14.jar 파일을 해당 어플리케이션의 \WEB-INF\lib\ 에 위치시킨다.
  • log4j.properties파일을 사용한다면 /WEB-INF/src/에 위치시킨다. 각 어플 당 하나씩, 여러개를 가질 수 있다.

프로그래밍 내에서 직접 설정

BasicConfigurator

  • 예제코드
    import org.apache.log4j.Logger;
    import org.apache.log4j.BasicConfigurator;
    
    
    public class SimpleLog {
     
     // Logger 클래스의 인스턴스를 받아온다.
       static Logger logger = Logger.getLogger(SimpleLog.class);
    
       public SimpleLog() {}
    
       public static void main(String[] args) {
    
         /*
         콘솔로 로그 출력 위한 간단한 설정,
         이 설정이 없다면 경고 메세지가 출력되면서 실행이 중단된다.
         */
           BasicConfigurator.configure();
    
           logger.debug("[DEBUG] Hello log4j.");
           logger.info ("[INFO]  Hello log4j.");
           logger.warn ("[WARN]  Hello log4j.");
           logger.error("[ERROR] Hello log4j.");
           logger.fatal("[FATAL] Hello log4j.");
           //loger.log( Level.DEBUG , "debug") 와 동일하다.
       }
    }
    
  • 출력결과 및 설명
    //output
    0 [main] DEBUG SimpleLog  - [DEBUG] Hello log4j.
    0 [main] INFO SimpleLog  - [INFO]  Hello log4j.
    0 [main] WARN SimpleLog  - [WARN]  Hello log4j.
    0 [main] ERROR SimpleLog  - [ERROR] Hello log4j.
    0 [main] FATAL SimpleLog  - [FATAL] Hello log4j.
    ... (생략)...
    
  • BasicConfigurator.configure 메소드는 ConsoleAppender 내 정의되어 있고, PatternLayout은 %r %t %p %c %x - %m%n 로 설정되어 있다.
  • BasicConfigurator.configure 메소드의 PatternLayout의 형태로 출력되는 것을 알 수 있다.

FileAppender의 사용

  • 예제코드
    import java.io.IOException;
    
    import org.apache.log4j.DailyRollingFileAppender;
    import org.apache.log4j.Logger;
    import org.apache.log4j.PatternLayout;
    
    public class DailyLog {
      
      static Logger logger = Logger.getLogger(DailyLog.class);
      
      static void doIt() {
        logger.info("Test info");
      }
    
      public static void main(String[] args) {
        // 로그 파일의 내용에 대한 패턴을 정의한다. 
        String pattern = "[%d{yyyy-MM-dd HH:mm:ss}] %-5p [%l] - %m%n";
        PatternLayout layout = new PatternLayout(pattern);
        
        // 처음 생성될 로그 파일의 이름
        String filename = "DailyLog.log";
        
        // 날짜 패턴에 따라 추가될 파일 이름
        String datePattern = ".yyyy-MM-dd";
        
        DailyRollingFileAppender appender = null;
        try {
          appender = new DailyRollingFileAppender(layout, filename, datePattern);
        } catch (IOException ioe) {
          ioe.printStackTrace();
        }
        
        logger.addAppender(appender);
        
        while (true) {
          logger.debug("Hello log4j.");
        }
    
      }
    }
    
    • org.apache.log4j.DailyRollingFileAppender를 import하여 FileAppender의 사용을 가능하게 하였다.
    • pattern을 지정하고, 생성될 filename과 날짜 패턴에 따라 추가될 파일 이름 datePattern을 지정하였다.
    • pattern, filename, datePattern을 인자로 DailyRollingFileAppender의 intance를 appender를 생성하였다.
  • 실행결과 및 상세설명
    //output
    [2004-06-02 02:37:30] DEBUG [simplelog.DailyLog.main(DailyLog.java:32)] - Hello log4j.
    [2004-06-02 02:38:30] INFO [simplelog.DailyLog.doIt(DailyLog.java:14)] - Test info
    [2004-06-02 02:38:30] DEBUG [simplelog.DailyLog.main(DailyLog.java:32)] - Hello log4j.
    [2004-06-02 02:39:30] INFO [simplelog.DailyLog.doIt(DailyLog.java:14)] - Test info
    [2004-06-02 02:39:30] DEBUG [simplelog.DailyLog.main(DailyLog.java:32)] - Hello log4j.
    
    • 소스코드를 컴파일하고 실행하면 디렉토리에 DailyLog.log파일이 생성된다.
    • 파일의 내용은 소스코드에서 정의된 패턴에 따라 생성된 것을 볼 수 있다.
    • DailyLogFileAppender를 사용하여 밤 12시 정각이 지나면 조금 전까지 출력하던 로그 파일은 백업이 되면서 새로운 로그를 쌓기 시작한다.

설정파일을 사용하는 방법

  • 코드가 길어질수록 configuration file을 이용해 log statement를 효율적으로 관리하는 것이 필요하게 된다.
  • Configuration file을 이용하려면 PropertyConfigurator를 사용하면 된다.
  • log4j.properties의 기본적인 포맷
    # Appender의 정의
    log4j.appender.이름=Appender의 완전 수식명
    log4j.appender.이름.옵션=값
    log4j.appender.이름.layout=레이아웃 클래스의 완전 수식명
    log4j.appender.이름.layout.옵션=레이아웃의 옵션
     
    #카테고리의 정의
    log4j.logger.카테고리=로그 레벨, 적용할 Appender의 이름[, Appender명]
     
    #루트 로거의 정의
    log4j.rootLogger=로그 레벨, 적용할 Appender의 이름[, Appender명]
    

ConsoleAppender의 사용

  • 예제코드
    //log4j.properties
    log4j.rootLogger=DEBUG, A1
    log4j.appender.A1=org.apache.log4j.ConsoleAppender 
    log4j.appender.A1.layout=org.apache.log4j.PatternLayout
    log4j.appender.A1.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}] - %m%n
    
    //log를 출력할 자바문서
    import org.apache.log4j.Logger;
    import org.apache.log4j.PropertyConfigurator;
    
    public class LogProperty {
      
      static Logger logger = Logger.getLogger(LogProperty.class);
      
      public static void main(String[] args) {
        logger.debug("Hello log4j.");
      }
    }
    
  • 출력결과 및 설명
    [2004-07-29 18:10:33] - Hello log4j.
    
    • log4j.properties에서 logger를 DEBUG레벨로 지정하였고, A1 appender를 ConsoleAppender로 설정하고, 특정 패턴을 정의해주었다.
    • java 문서에서 org.apache.log4j.PropertyConfigurator를 import하여 property 파일을 사용 가능하게 하였다.

RootLogger의 다중 Appender 정의

//log4j.properties
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %5p %c{1} - %m%n
 
### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=mylog.log
log4j.appender.file.Append=true
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d %5p %c{1} - %m%n
 
log4j.rootLogger=debug, stdout, file
  • RootLogger에 대해서 ConsoleAppender (이름은 「stdout 」)와 FileAppender (이름은 「file 」)을 주고 있다.
  • 표준 출력과 로그파일에 로그가 남는 설정이다.

여러 파일에 로그 남기기

  • 예제코드
    log4j.logger.process.work1=DEBUG, WORK1 
    log4j.logger.process.work2=DEBUG, WORK2 
    log4j.logger.process.work3=DEBUG, WORK3 
     
     
    # WORK1 Logger의 정보를 설정 
    log4j.appender.WORK1=org.apache.log4j.DailyRollingFileAppender 
    log4j.appender.WORK1.File=work1.log 
    log4j.appender.WORK1.DatePattern='.'yyyy-MM-dd 
    log4j.appender.WORK1.Threshold=DEBUG 
    log4j.appender.WORK1.layout=org.apache.log4j.PatternLayout 
    log4j.appender.WORK1.layout.ConversionPattern=[%d] %-5p %l - %m%n 
     
     
    # WORK2 Logger의 정보를 설정 
    log4j.appender.WORK2=org.apache.log4j.DailyRollingFileAppender 
    log4j.appender.WORK2.File=work2.log 
    log4j.appender.WORK2.DatePattern='.'yyyy-MM-dd 
    log4j.appender.WORK2.Threshold=DEBUG 
    log4j.appender.WORK2.layout=org.apache.log4j.PatternLayout 
    log4j.appender.WORK2.layout.ConversionPattern=[%d] %-5p %l - %m%n 
     
     
    # WORK3 Logger의 정보를 설정 
    log4j.appender.WORK3=org.apache.log4j.DailyRollingFileAppender 
    log4j.appender.WORK3.File=work3.log 
    log4j.appender.WORK3.DatePattern='.'yyyy-MM-dd 
    log4j.appender.WORK3.Threshold=DEBUG 
    log4j.appender.WORK3.layout=org.apache.log4j.PatternLayout 
    log4j.appender.WORK3.layout.ConversionPattern=[%d] %-5p %l - %m%n
    
    import org.apache.log4j.Logger; 
     
     
    public class LoggerTest 
    { 
        
    // 각각의 Logger를 가져온다. 
        
    static Logger logger1 = Logger.getLogger("process.work1"); 
        
    static Logger logger2 = Logger.getLogger("process.work2"); 
        
    static Logger logger3 = Logger.getLogger("process.work3"); 
         
     
        
    public static void main(String args[]) 
    { 
            
    // 로그 출력 
            
    logger1.error("Hello log4j."); 
            
    logger2.error("Hello log4j."); 
            
    logger3.error("Hello log4j."); 
        
    } 
    }
    
    • 여러 파일에 로그를 남기기 위해서는 properties 파일에 Root logger 방식이 아닌 각각의 Logger에 대해 선언해 주어야 한다.
    • 코드 내에서 각각의 Logger를 가져와 로그를 출력하는 것을 볼 수 있다.

카테고리

  • Logger는 카테고리 형식의 계층적 naming rule을 따른다
    (sug.log4j.test.TestLog4j)
    package sug.log4j.test;
     
    import org.apache.log4j.Logger;
    import org.apache.log4j.NDC;
    import org.apache.log4j.PropertyConfigurator;
    import org.apache.log4j.xml.DOMConfigurator;
     
    import sug.log4j.test.child.TestChild;
     
    public class TestLog4j {
       protected static Logger log = Logger.getLogger( TestLog4j.class ); 
     
       public static void main( String [] args ) {
          NDC.push( "level-No.1" );  /* NDC 
    에 PUSH */
          log.info( "ENTER: TestLog4j" );
          TestChild tc  = new TestChild();
          tc.doSomething();
          log.info( "EXIT: TestLog4j" );
          NDC.pop();
       }
    }
    
    (sug.log4j.test.child.TestChild)
    package sug.log4j.test.child;
     
    import org.apache.log4j.Logger;
     
    import java.io.*;
    import java.util.*;
     
    public class TestChild {
       protected Logger log = Logger.getLogger( this.getClass() ); 
     
       public void doSomething( ) {
          try {
             BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) );
             while( true ) {
                String line = br.readLine();
                if( line == null ) {
                   break;
                }
                int at = line.indexOf( "," );
                if( at == -1 ) {
                   break;
                }
                String cat = line.substring( 0, at );
                String body = line.substring( at + 1 );
                if( cat.equals( "fatal" ) ) {
                   log.fatal( body );
                } else if( cat.equals( "error" ) ) {
                   log.error( body );
                } else if( cat.equals( "warn" ) ) {
                   log.warn( body );
                } else if( cat.equals( "info" ) ) {
                   log.info( body );
                } else if( cat.equals( "debug" ) ) {
                   log.debug( body );
                /* 1.3을 사용한다면 유효하게 하면 된다
                } else if( cat.equals( "trace" ) ) {
                   System.out.println( "TRACE: " + body  );
                   log.trace( body );
                */
                } else if( cat.equals( "exception" ) ) {
                   log.error( body, new IOException( "test Exception" ) );
                } else if( cat.equals( "quit" ) ) {
                   break;
                }
             }
          } catch( IOException e ) {
             return;
          }
       }
    }
    
    • 패키지 지정을 하고, 서브 디렉토리가 있는 설정이다.
    • 이 때 sug.log4j.test.child.TestChild 클래스의 로그만을 파일에 보존하고 싶다면 log4j.properties의 내용을다음과 같이 설정한다.
      log4j.rootLogger=debug, stdout
      log4j.logger.sug.log4j.test.child.TestChild=debug, file
      
    • sug.log4j.test.child.TestChild클래스의 로그는 mylog.log에 보존된다.
    • sug.log4j.test.TestLog4j클래스의 로그는 RootLogger의 설정을 계승하기 때문에 표준출력에는 로그가 출력된다.
    • 이러한 계층(카테고리)를 잘 이용하기 위해서는 Logger의 이름은 완전 수식 클래스명으로 하는 것이 바람직하다.

references