2013년 12월 11일 수요일

006. Log4j를 이용한 로그 남기기

/*
LOG4J와 SLF4J 연동하기.

Eclipse의 BuildPath에
log4j-1.2.17.jar와 slf4j-log4j12-1.7.5.jar를 추가하면 된다.
slf4j-log4j12-1.7.5.jar는 slf4j와 log4j의 바인딩 역활을 하고
log4j-1.2.17.jar는 log4j를 위한 jar이다.

 */
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

public class Log4J_Sample006_slf4J {

private static Logger client = LoggerFactory.getLogger("client");
private static Logger server = LoggerFactory.getLogger("server");

public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(Log4J_Sample006_slf4J.class);
logger.info("{}", "Hello World"); // 바뀐 부분
client.debug("{}", "client"); // 밑에서 추가 설명
server.debug("{}", "server"); // 밑에서 추가 설명
}
}
/*
logger.info("{}","Hello World"); <-- 이 부분이 바뀌엇다.
{}안에 "hello world"가 입력이 되서 출력이 된다는 것으로 이해 할 수 있다.
이 것때문에 slf4j를 사용해야 좋다는 것이다.

로깅 레벨 조절하기
log4j에는 info, debug, error 등의 로깅 레벨이 있다.
보통 개발 할 때는 info, debug로 사용하고 실제 서비스 할 때는 error로 로깅 레벨을 변경하고
파일로 저장한다.

여기서는 log4j.xml를 이용하였다.
<?xml version="1.0" encoding="UTF-8" ?-->
<log4j:configuration>
    <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="[%d{yyyy-MM-dd HH:mm}] %5p [%C{1}(%M:%L)] :%m%n%n">
        </layout>
    </appender>
    <!-- 날짜별 로그 <appender name="dailyout" class="org.apache.log4j.DailyRollingFileAppender"> -->
    <appender name="file" class="org.apache.log4j.DailyRollingFileAppender">
        <param name="file" value="./logs/dailyout.log">
        <param name="Append" value="true">
        <param name="DatePattern" value="'.'yyMMdd">
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%t> [%d{yyyy-MM-dd HH:mm:ss}] [%c{1}]
        [%L] [%p] %m %n">
        </layout>
    </appender>
    <category name="client">
        <priority value="DEBUG">
    </priority>
    </category>

    <category name="server">
        <priority value="DEBUG">
    </priority>
    </category>
    <root>
        <priority value="INFO">
        <appender-ref ref="stdout">
        <appender-ref ref="file">
    </appender-ref></appender-ref></priority></root>
</log4j:configuration>

xml 맨 밑에 root가 있다.
priority value=info로 되어있다. 이 부분을 debug or error로 변경 후 HelloWorld를 실행하면
[2011-07-20 12:48] DEBUG [HelloWorld(main:13)] :client
[2011-07-20 12:48] DEBUG [HelloWorld(main:14)] :server

위처럼 출력하게 된다.
logger.info("{}","Hello World");의 로깅레벨이 error로 바뀌어서 info는 출력되지 않는다.

프로젝트 단위가 크게 되면 로깅도 많아지고 결국 쉽게 볼 수 없게 된다.
이럴 경루 특성에 따라 로깅 종류를 만들어 사용할 수 있다.
예를 들면 DB에 관련된 로깅, Server에 관련된 로깅을 구분한다.

xml를 보면
<category name="server">
<category name="client">
2개의 카테고리가 있다.
server, client의 <priority value="DEBUG">를 이용해서 필요한 로그 정보만 볼 수 있다.
client의 priority를 error로 변경 후 실행하면
[2011-07-20 12:48] DEBUG [HelloWorld(main:14)] :server
위 처럼 출력된다. 즉 필요한 로깅 정보만 볼 수 있다. 아주 유용한 기능이다.
로그 정보를 하나의 category로 만들어서 사용하지 말고 되도록이면 category를 나누고
로그 정보를 따로 관리하는 방법이 매우 유용하다.
*/

005. Log4j를 이용한 로그 남기기

/*
오래전에 작성한 log4j도 있지만
slf4j는 log4j와 거의 똑같이 사용하면서 로깅시 필요한 메시지 조합(?)을
좀 더 간편하게 할 수 있게 해준다.

관련정보 보기 -- http://www.slf4j.org/

예를들어 log4j에서 로깅 메시지를 다음과 같이 출력했다고 가정한다면..

log4j
String message = "iS DEBUG!";
String clsName = this.getClass().getName();
log.debug("debug message :" + message + " this class : " + clsName);

slf4j
String message = "iS DEBUG!";
String clsName = this.getClass().getName();
log.debug("debug message : {} this class : {}" ,message, clsName);

2개 이상의 파라미터는 new Object[] {} 를 이용해서 넘겨주면 된다.

=================================================================================
현재 추세는 Slf4J인 듯 하다. 사용하기 편하고 성능또한 좋다.
여하튼 지금 Slf4J를 가볍게 알아보자.
slf4j는 jdk, common_log, log4j 등등의 로깅 framework를 통합해서 사용할 수 있는 로깅 framework이다.

slf4j와 log4j를 위한 필수 jar들
slf4j-api-xxx.jar      
log4j-xxx.jar
slf4j-log4j12-xxx.jar

eclipse에서slf4j-api-1.6.1.jar 를 Build Path에 추가한 후 실행 해 보자.
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further
       details.

위와 같은 에러 메세지가 나올것이다.
slf4j에서 binding 할 Loggig framework를 찾지 못해서 나온 에러이다.

slf4j-simple-1.6.1.jar 파일을 Eclipse의 Build Path에 추가한 후 실행해본다.
 0 [main] INFO HelloWorld - Hello World
로깅 출력이 되엇다.

기본적인 SLF4J 설정이 완료 되었다.
 */

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Log4J_Sample005_slf4J {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(Log4J_Sample005_slf4J.class);
logger.info("Hello World");
}
}

004. Log4j를 이용한 로그 남기기

/*
Log4j.xml 을 이용한 로그 설정
=============================
이번엔 log4j.xml을 이용한 설정을 이용해 보자.
설정 내용은 거의 같다고 보면 되고, xml형식에 맞춰 작성되는것 밖에
차이가 없다.
파일위치는 properties와 같은 위치에 생성하면 된다.

log4j.xml 내용
==============
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <!-- 콘솔 로그 -->
    <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
    <!-- 패턴 레이아웃 설정.. -->
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%t> [%d{yyyy-MM-dd HH:mm:ss}] [%c{1}] [%L] [%p] %m %n"/>
        </layout>
    </appender>
 
    <!--  날짜별 로그 -->
    <appender name="dailyout" class="org.apache.log4j.DailyRollingFileAppender">
    <!-- 이것은 날짜별로  로그를 남김. 파일명.확장자.DatePattern으로 정의 함-->
        <param name="file" value="dailyout.log"/>
        <param name="Append" value="true"/>
        <param name="DatePattern" value="'.'yyMMdd"/>
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%t> [%d{yyyy-MM-dd HH:mm:ss}] [%c{1}] [%L] [%p] %m %n"/>
        </layout>
    </appender>
   
    <!-- 특정 크기마다 백업하며 로그-->
    <appender name="rolling" class="org.apache.log4j.RollingFileAppender">
        <param name="file" value="rolling.log"/>
        <param name="Append" value="true"/>
        <param name="MaxFileSize" value="1kb"/>
       <param name="MaxBackupIndex" value="1"/>
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%t> [%d{yyyy-MM-dd HH:mm:ss}] [%c{1}] [%L] [%p] %m %n"/>
        </layout>
    </appender>
 
    <!-- xml 로그-->
    <!-- xml형식으로 로그를 남김 -->
    <appender name="xmlout" class="org.apache.log4j.FileAppender">
        <param name="file" value="log4jxml.xml"/>
        <param name="Append" value="true"/>
        <layout class="org.apache.log4j.xml.XMLLayout"/>
    </appender>
   
    <root>
        <level value="WARN"/>
        <appender-ref ref="dailyout"/>
    </root>
</log4j:configuration>

이번엔 특별하게 xml형식의 로그까지 설정했다.
최 하단에 있는 rootLogger 설정에서 사용할 로그를 모두 설정하였다.


xml을 이용해 일정 값을 가져온다던지 .
아니면 원격에 있는 xml 로그정보를 확인할 일이 생긴다면
xml log로 남기는것도 괜찮아 보인다.
*/
import org.apache.log4j.*;

public class Log4J_Sample004_UseXML {
static Logger logger = Logger.getLogger(Log4J_Sample004_UseXML.class);
public static void main(String arg[]) {
// 로그를 계속 쌓아두기 위해 MAX_COUNT값을 정의 함.
final int MAX_COUNT = 10;
for (int i=0;i<MAX_COUNT;i++) {
// 각각의 레벨에 맞춰 로그를 남김
logger.debug("debug 로그");
logger.info("info 로그");
logger.warn("warn 로그");
logger.error("error 로그");
logger.fatal("fatal 로그");
}
System.out.println("끝~~~~~~");
}
}

003. Log4j를 이용한 로그 남기기

/*
Log4j를 이용해 간단히 콘솔에 로그를 출력하는 것을 해봤다면
이제 좀 더 실용적인 로깅 방법에 대해 살펴보자.
xml을 이용해서도 가능하지만 properties는 xml보다 직관적이지 않나 싶다.
단지 key와 value로 구성되어 있으니까 ..

먼저 log4j.properties 파일을 생성한다.
이건 그냥 파일이면 상관 없다.
뭐 이클립스에서 무엇을 들어가 생성하고 .. 그런게 전혀 필요없다는 얘기다.
단, 경로는 잘 지켜줘야 한다.
일반적인 app일 경우 .class가 생성되는 위치
(일반적으로 이클립스에서 java project를 이용하면 bin에 .class가 생성 된다.
소스는 당연히 src에 ..)에 넣어주면 된다.
웹이라면 WEB-INF를 이용하면 된다.

먼저 간단한 log4j.properties를 보면 다음과 같다.

#로그설정(로그레벨 DEBUG ~ FATAL, OFF일경우 로그사용 안함), 사용 로그 이름
log4j.rootLogger=DEBUG, stdout, rolling

#stdout 로그 레벨 설정
log4j.appender.stdout.Threshold=WARN
#stdout 콘솔 어펜더로 사용
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
#stdout 패턴 레이아웃 사용
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
#stdout 레이아웃 패턴 정의
log4j.appender.stdout.layout.ConversionPattern=%t> [%d{yyyy-MM-dd HH:mm:ss}] [%c{1}] [%L] [%p] %m %n

#롤링파일 업펜더 사용
log4j.appender.rolling=org.apache.log4j.RollingFileAppender
#롤링파일 로그에 대한 파일명 (경로 설정 가능 함)
log4j.appender.rolling.File=output.log
#로그파일 최대 크기
log4j.appender.rolling.MaxFileSize=1KB
#최대파일이 넘어갔을 경우 백업
log4j.appender.rolling.MaxBackupIndex=1
#롤링파일 패턴 레이아웃 사용
log4j.appender.rolling.layout=org.apache.log4j.PatternLayout
#롤링파일 패턴 정의
log4j.appender.rolling.layout.ConversionPattern=%t> [%d{yyyy-MM-dd HH:mm:ss}] [%c{1}] [%L] [%p] %m %n


그냥 보기에도 어렵지 않은 내용이다.
최상단에 있는 log4j.rootLogger 에 Log4j의 설정이 들어가는데 여기에 로그를 출력할 레벨을
설정 한다. 만약 로그를 사용하지 않을경우 'OFF'로 성정하는데 주목하자.
그리고 그 후에 오는 것들은 각각의 로그에 대한 설정이다.
stdout와 rolling가 있는데 stdout는 콘솔출력을 위한 로그고 rolling는 파일 출력을 위한
로그 설정이다.
콘솔출력에서 봐야할 것은 log4j.appender.stdout.Threshold 부분인데 지금의 설정대로라면
DEBUG 레벨까지 출력하게 되어있다.
하지만 stdout에선 WARN으로 되어있으므로, stdout에서 출력되는 로그들은 WARN의 레벨 이상만
보여주게 된다. 롤링파일을 보면 남길 파일명을 기록하는 부분과 로그파일 최대크기, 백업
인덱스가있는데 먼저 로그파일의 최대크기는 말그대로 '내가 설정한 로그파일이 가질 최대의
크기'를 의미한다. 그리고 백업은 output.log.1 하는 식으로 인덱스에 숫자를 붙혀 나가게
되는데 이런식으로 로그가 남다가 풀이되면 백업 후 새로운 파일을 만들어 기록하게 된다.
자. 만약 로그파일 최대 크기가 넘어갔고, 백업을 1개만 했는데 이미 백업파일까지 존재한다.
그렇다면 이후의 로그들은 어찌될까? 이럴경우엔 이전 로그정보는 사라지고 계속 추가되게
된다. 만약 이와 반대로 되어있다면 .. 이건 재앙일 것이다.
*/

import org.apache.log4j.*;

public class Log4J_Sample003_UseProperties {
// 이건 더이상의 설명이 필요 없죠.
static Logger logger = Logger.getLogger(Log4J_Sample003_UseProperties.class);
public static void main(String arg[]) {
// 로그를 계속 쌓아두기 위해 MAX_COUNT값을 정의 함.
final int MAX_COUNT = 1000;
for (int i=0;i<MAX_COUNT;i++) {
// 각각의 레벨에 맞춰 로그를 남김
logger.debug("debug 로그");
logger.info("info 로그");
logger.warn("warn 로그");
logger.error("error 로그");
logger.fatal("fatal 로그");
}

}
}

002. Log4j를 이용한 로그 남기기

/*
사용법도 그다지 어렵지 않고, 로그를 출력하는것도 어렵지 않다.
Log4j에서 사용하는 주요 객체는 다음과 같다.

1.Logger (로그 본체)
위에서 사용 했던것 처럼 Logger의 본체이다.
Logger.getLogger를 이용해 Logger을 가져와 사용한다.
여기에는 로그 출력 여부를 동적으로 확인할 수 있고,
로그레벨을 가지고 있어 출력할 로그의 레벨을 확인 후 출력할 수 있다.

2. Appender (로그 출력위치)
로그를 반드시 콘솔로 출력해야 할 이유는 없다.
파일로 출력할 경우도 있고, db에 저장할 경우도 있다.
이러한 로그 출력 위치를 결정한다.

3. Layout (레이아웃)
로그 출력에 대한 레이아웃이다.
일정 패턴을 갖고 있어 보다 정확한 로그를 확인할 수 있다.
패턴은 다음과 같이 설정할 수 있다.
레이아웃의 종류는 여러가지(DateLayout,XMLLayout, HTMLLayout ..)가 있는데
그중 하나인 PatternLayout를 살펴보도록 하자.

%p debug, info등의 로그 타입
%d 로그 발생 시간 SimpleDateFormat 포멧을 따름 ( ex> %d{yyyy-MM-dd HH:mm:ss} )
%t 로그 쓰레드 이름
%F 로그 발생 프로그램 파일 명 (만약 패키지가 너무길어 불편하다면 %F {숫자}로 조절 가능)
%M 로그 발생 메소드 명
%r 프로그램 시작 후 로그 발생까지 시간
%m 로그로 전달된 메시지
%n 개행
%L 호출 코드 라인

기본적으로 이정도만 알고 있어도 크게 무리는 없을것 같다.
이제 포멧을 지정해 로그를 출력해 보자.
 */
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;

public class Log4J_Sample002_PatternLayout {
static Logger logger = Logger.getLogger(Log4J_Sample002_PatternLayout.class);
public static void main(String ar[]) {
// 패턴 레이아웃을 작성합니다. 형식은 마음대로 작성하시면 되죠!
PatternLayout pattern =
new PatternLayout("%t> [%d{yyyy-MM-dd HH:mm:ss}] [%c{1}] [%L] [%p] %m %n");
// 로그 출력 위치는 콘솔로 합니다.
// 다음번 예제는 파일로 해볼께요.
logger.addAppender(new ConsoleAppender(pattern));
logger.debug("로그를 출력 합니다.");
logger.debug("콘솔 Appender를 이용했으므로 콘솔로 출력 됩니다.");
logger.debug("파일, 혹은 db로도 저장 가능 하구요!");
}
}
/*
출력결과
===========================================================
main> [2013-12-12 15:06:50] [Log4J_Sample002_PatternLayout] [15] [DEBUG] 로그를 출력 합니다.
main> [2013-12-12 15:06:50] [Log4J_Sample002_PatternLayout] [16] [DEBUG] 콘솔 Appender를 이용했으므로 콘솔로 출력 됩니다.
main> [2013-12-12 15:06:50] [Log4J_Sample002_PatternLayout] [17] [DEBUG] 파일, 혹은 db로도 저장 가능 하구요!

*/


001. Log4j를 이용한 로그 남기기

/*
  1. Log4j를 이용한 로그 남기기

  프로그램을 개발하다 보면 오류메시지를 확인하거나 분기에 변수를 확인하기 위해
  디버깅모드를 사용한다. 만약 이런 디버깅 모드를 사용하지 못하는 경우라면 java의
  경우 System.out.println()을 이용해 콘솔에 로그를 남기고 문제점을 추적하는게 대부
  분이다.
개발단계에선 로그를 남기며 확인 하지만 실제 서비스를 하거나 배포를 할때 이런식의
  로그를 남기는 것은 낭비다. 먼저 배포된 경우 프로그램의 동작 원리가 공개될 수도 있
  으므로 해선 안되고, 어쩌면 System.out.println()으로 인해 약간의 성능 저하를 감당해
  야 할 지도 모르겠다. 그리고 개발이 완료된 후 System.out.println()식의 로그는 일일
  이 해제를 해주어야 하는데

Log4j를 이용하면 외부의 설정 변경으로 로그출력여부를 결정할 수 있다.
apache에선 log4j라는 logging framework를 제공한다.

Log4j 다운받기 --   http://logging.apache.org/


log4j 2.0도 있지만 이것은 jdk 5.0이상 버젼에서만 사용 가능하다고 한다.
  ** 2.0은 아직 릴리즈 된 것은 아니라고 하네요!
압축을 해제하면 log4j-x.x.xx.jar 파일이 보일 것이다.
프로젝트에 추가하여 다음코드를 통해 샘플을 확인하자.
 */
import org.apache.log4j.Logger;
import org.apache.log4j.Level;
import org.apache.log4j.BasicConfigurator;
public class Log4J_Sample001 {
static Logger logger = Logger.getLogger(Log4J_Sample001.class);
public static void main(String[] args) {
// 별도의 설정(예를들어 프로퍼티,XML등)이 없으므로 콘솔출력을 위해
// 기본으로 설정 한다.
BasicConfigurator.configure();
// 로그레벨 순서는 DEBUG INFO WARN ERROR FATAL 이다.
logger.debug("[logger.debug()]"+"디버그");
logger.info("[logger.info()]  "+"정보");
logger.warn("[logger.warn()]  "+"경고");
logger.error("[logger.error()]"+"에러");
logger.fatal("[logger.fatal()]"+"심각한 에러");
// Level을 이용한 출력
logger.log(Level.DEBUG, "디버그 프로퍼티를 이용");


}
}


출력결과
===========================================================
{경과시간} [{호출스레드}] {레벨} {클래스} {작성한내용}
===========================================================
0 [main] DEBUG Log4J_Sample001  - [logger.debug()]디버그
0 [main] INFO Log4J_Sample001  - [logger.info()]  정보
0 [main] WARN Log4J_Sample001  - [logger.warn()]  경고
0 [main] ERROR Log4J_Sample001  - [logger.error()]에러
0 [main] FATAL Log4J_Sample001  - [logger.fatal()]심각한 에러
0 [main] DEBUG Log4J_Sample001  - 디버그 프로퍼티를 이용

출력 결과는 위와 같다. 여기서 주목해야할 점은 Logger.getLogger(Log4J_Sample001 .class);
코드에서 StartApp.class부분이다. 이것은 그냥 String로 들어가도 되는데 저런식으로 클레스를 지정해주면 해당 클레스의 정보(패키지포함)까지 출력하게 된다.
만약 클레스를 전달하지 못하는경우(예를들어 jsp에서 처리할때)는 Logger.getLogger("CallJsp.jsp");로 해줘도 무관하다.