저자: 김영익 / (주)콘델라
프로그램을 개발하는 사람이라면 누구나 로그(log)를 남기게 마련이다. 자바로 프로그램을 하거나 C++을 사용하거나 비주얼 베이직을 사용하더라도 마찬가지이다. 어떠한 형태로던지 로그를 남겨서 자신이 작성한 프로그램이 정상적으로 작동하는지, 혹은 실행 도중에 문제가 생겼을 때에도 로그를 참고해서 어느 부분에 문제가 있는지 찾아보곤 한다.
개발자가 아닌 시스템을 관리하는 경우에도 로그는 매우 중요하다. 보안 담당자의 경우 누가 시스템에 불법적(?)으로 접근하는지 못된 짓(?)을 하는지 감시할 때에도 로그가 중요한 역할을 한다. 이렇게 다양한 사람들과 용도로 사용하는 로그를 여러분은 어떻게 생성하고 관리하고 있을까? 자바 프로그래머라면 System.out.println 을 사용할 수도 있고 C 프로그래머라면 printf 를 사용할지도 모른다. 단순한 로그라면 상관 없겠지만 로그의 형태도 다양하고 여러 조건이 주어져야 한다면 이런 단순한 방법을 사용해서는 원하는 목표를 달성하기 힘들 것이다.
서울에서 부산까지 가는 방법에는 여러 가지가 있다. 고속 철도를 이용하거나, 기차, 자가용, 자전거, 심지어 걸어서 가는 방법도 있다. 여러분은 어떤 방법을 선택하겠는가? 당연히 일반적인 경우라면 좀더 편하고 빠른 방법을 택할 것이다. 로그를 남기는 방법에도 마찬 가지로 좀 더 편하고 다양한 기능과 성능까지 보장하는 방법이 있다면? 그렇다. 바로 log4j 를 사용하는 것이다.
Log4j는 이런 시대의 다양한 욕구를 충족시키기 위해 등장한 것이다. 이미 log4j 프로젝트가 시작 된지 몇 년이 지났고 그 동안 충분히 검증되었으며 업계에서도 표준처럼 굳어져 있어서 많은 상용 제품에도 포함되어 있다. JDK 1.4 버전부터는 일부 로깅 API가 추가되었지만 log4j 처럼 막강하지는 않는 듯 하다. 지금부터 몇 가지 예제를 통해서 log4j 의 편하고 강력한 기능을 살펴 보기로 하자.
* log4j 에 소개에 대한 한글 사이트는 아래 링크를 참고하자.
http://jakarta.apache-korea.org/log4j/
1. Hello log4j
역시나 처음 프로그램을 시작할 때 빠지지 않는 단골인 Hello 프로그램으로 시작하자.
Log4j 의 설치나 다운로드는 아래 주소나 다른 참고 문헌을 살짝 훔쳐 보도록 하자.
http://logging.apache.org/log4j/docs/
Ant 와 log4j 활용
설치가 정상적으로 이루어졌다면 다음 소스 코드를 보자. 소스에 대한 설명은 주석을 참고하면 되겠다.
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();
while (true) {
/*
로그 레벨에는 아래의 5가지가 있다.
적당한 레벨을 지정해두면 나중에 여러 가지로 손발이 편하다.
*/
logger.debug("Hello log4j.");
logger.info("Hello log4j.");
logger.warn("Hello log4j.");
logger.error("Hello log4j.");
logger.fatal("Hello log4j.");
try {
// 잠시 쉬자 ^^
Thread.sleep(500);
}
catch (Exception e) {
}
}
}
}
소스를 컴파일하고 실행하면 아래와 같이 실행된다.
맨 앞에 등장하는 숫자는 로깅 호출로부터 얼마만큼의 시간이 지났는지를 밀리세컨드 단위로 보여준다. 이 정보는 별로 유용할 것 같지는 않다. 다음에 보이는 [main] 은 호출한 쓰레드의 이름을 나타낸다. 다음에 나타나는 DEBUG , INFO , WARN 등은 로그 레벨이다. 소스 코드에서 지정한 레벨을 그대로 출력해주고 있다. 그 뒤에 나오는 것은 보시다시피 클래스의 이름과 실제 로그 메시지를 보여준다. 특별히 유용한 예제는 아니지만 사용하는 방법에 따라 System.out.println 보다는 그럴 듯 해 보인다. 예제에는 포함되어 있지 않지만 로그를 출력할지를 결정하는 플래그 값을 사용하거나 레벨로 제한을 하도록 수정하면 금상첨화일 것이다.
2. 하루에 하나씩
앞서 살펴본 Hello 프로그램으로 log4j 에 대한 허탈감을 느낀다면 아직 이르다. 이번에 소개할 예제는 실제 업무에 많이 사용되는 것이다.
로그를 콘솔로 출력한다면 스크롤 되어 지나간 로그에 대해 아무 것도 할 수 없으므로 대략 낭패가 되겠다. 그런 문제 때문에 대부분의 로그는 파일로 저장하는 것이 일반적인데 파일로 저장하는 것에도 문제점이 있다. 바로 파일 사이즈 문제이다. 요즘 아무리 하드 디스크 가격이 싸다고 하지만 심한 경우 하루에도 로그 파일의 사이즈가 몇 백 메가바이트를 넘어 가는 경우도 있기 마련이다. 그런 파일이 일주일, 한 달이 지나면… 파일 하나가 수 기가 바이트에 이르게 된다. 이 파일을 열어서 원하는 것을 검색한다면 이 또한 낭패가 아닐 수 없다. 로그 파일 사이즈가 너무 커서 부담스러운 것이다. 그래서 대부분의 경우 로그 파일을 하루 단위로 저장을 하여 사이즈도 너무 커지지 않고 일정 기간이 지나서 필요 없다고 판단되면 과감히 지울 수 있도록 한다. 본인이 현재 수행하는 프로젝트에서도 로그 파일을 앞서 설명한 방식으로 저장을 하고 있다. 이제부터 소스 코드를 살펴보자.
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.");
try {
Thread.sleep(1000 * 60);
}
catch (Exception e) {
}
doIt();
}
}
}
소스 코드를 컴파일하고 실행하면 디렉토리에 DailyLog.log 파일이 생성되며 파일의 내용은 아래와 같다.
[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.
…
파일의 내용은 소스 코드에서 정의된 패턴인 "[%d{yyyy-MM-dd HH:mm:ss}] %-5p [%l] - %m%n" 에 따라 생성된 것이다. 출력된 것과 대조해보면 대충 어떤 의미인지 유추가 가능하다. 첫번째 내용은 날짜를 나타낸다. 날짜를 나타내는 형태도 SimpleDateFormat 을 사용하는 것처럼 원하는 형태로 가능하다. 다음에 보이는 것은 로그 레벨이며 다음에 보이는 것은 [패키지 이름.클래스 이름.메소드 이름(소스 파일 이름:소스 라인 넘버)] 이다. 그리고 마지막으로 실제 로그 내용을 보여준다. 정말 대단하지 않은가? 간단한 코딩 몇 줄만으로 이렇게 아름다운(?) 로그가 출력되다니 감동이 파도처럼 밀려온다. 출력 패턴은 이외에도 많은 것들이 있는데 자세한 사항은 log4j API 문서를 참고하도록 하자.
이 예제 프로그램의 기능은 여기서 멈추지 않는다. 프로그램을 계속 실행하면서 밤 12시 정각이 지나면 조금 전까지 로그를 출력하던 파일은 백업이 되면서 다시 새로운 로그를 쌓기 시작한다. 예를 들어 2004년 6월 2일 11시부터 프로그램이 시작되었다. 이때 처음 생성된 로그 파일 이름은 DailyLog.log 이다. 잠시 후 밤 12시가 지나고 6월 3일이 되면 2일까지 출력된 로그들은 DailyLog.log.2004-06-02 라는 파일로 저장이 되며 3일의 로그는 DailyLog.log 에 출력된다. 이런 식으로 DailyLog.log.2004-06-03 , DailyLog.log.2004-06-04 파일이 생성되며 항상 그날의 로그는 DailyLog.log 파일에 저장이 되는 것이다. 이런 놀라운 기능에 더 이상 말이 필요 없다.
여기서 잠깐! 여러분 중에 이런 궁금증을 가질 수도 있겠다. “프로그램이 종료되었다 다시 시작되면 그전까지 쌓였던 로그의 내용은 어떻게 되나?” 이미 테스트 해보았다 ^^. 로그 파일의 이전 로그에 덧붙여서 다시 로그를 슬그머니 출력하는 것이었다. 그럼 이전의 로그 내용을 지울 수도 있을까? 당연히 가능할 것이다. 이것은 여러분 숙제!!!
3. 프로퍼티 파일을 사용하자
이번에 살펴 볼 예제는 처음에 보았던 Hello 예제를 살짝 변경한 것이다. Log4j의 설정 부분에서 BasicConfigurator 를 사용하지 않고 PropertyConfigurator 를 사용한 것이다. 프로퍼티 파일에 원하는 설정 정보를 넣어두고 사용하면 소스 파일을 다시 컴파일하거나 배포하지 않고 프로퍼티 파일의 내용만 변경하여 사용 가능하므로 시스템 관리자들은 아주 편할 것이다. 게다가 log4j에서는 변경된 프로퍼티에 대해서 자동으로 다시 설정을 하고 동작하게 하는 기능이 있으므로 프로그램을 종료하고 다시 시작하는 일이 필요 없이 실행 도중에도 원하는 설정이 가능한 것이다. 이제 프로그램과 프로퍼티 파일의 내용을 둘러보자.
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) {
/*
프로퍼티 파일로부터 설정을 하며,
1초마다 변경 사항이 있는지 감시한다
*/
PropertyConfigurator.configureAndWatch(args[0], 1000);
while (true) {
logger.debug("Hello log4j.");
try {
Thread.sleep(1000);
}
catch (Exception e) {
}
}
}
}
프로퍼티 파일의 이름은 “log.properties” 이라고 하자. 그리고 파일의 내용은 다음과 같다.
#log4j.rootLogger=DEBUG, A1
log4j.rootLogger=OFF, 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
자세한 내용은 API 문서를 참고하기 바라며 간단한 설명만 하고 넘어가자.
roorLogger 다음에 나오는 값은 레벨과 Appender의 이름이다. 레벨은 OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL 의 값이 가능하다. A1 appender 는 콘솔에 출력하는 것으로 정의되었고 출력 패턴은 이전 예제의 일부를 그대로 사용한 것이다. 이제 컴파일을 하고 실행을 해보도록 하자. 클래스 이름 뒤의 값은 프로퍼티 파일을 지정하는 것이다.
콘솔에서 아래처럼 입력하면 처음에는 아무 것도 콘솔에 출력되지 않는다.
java LogProperty log.properties
그러나 프로퍼티 파일에서 레벨을 아래처럼 OFF 에서 DEBUG로 살짝 바꿔치기 하면…
log4j.rootLogger=DEBUG, A1
#log4j.rootLogger=OFF, A1
콘솔 화면에 DEBUG 레벨의 메시지가 출력되기 시작한다.
이외의 다른 설정 값들도 가능하며 여러분이 직접 API 문서를 참고해서 다양하게 사용할 수 있다.
4. 마치면서
몇 가지 예제를 살펴 보았지만 이것으로 log4j 의 모든 것을 보여주지는 못한다고 생각하지만, 이런 간단한 예제들도 적절히 조합해서 응용하면 상당히 멋진 로그 기능을 제공한다.
여러분도 log4j 를 사용하면서 좋은 정보가 있으면 다른 개발자들과 공유하고 널리 알려주기 바란다. 게다가 log4j는 다른 많은 언어로도 포팅(porting) 되어 있으므로 다른 프로그래밍 언어를 사용하는 개발자와도 일관성 있는 인터페이스를 통해 공감대를 형성할 수 있을 것이다.