로깅
로그(Log)란 프로그램 개발이나 운영 시 발생하는 문제점을 추적하거나 운영 상태를 모니터링하기 위한 텍스트다.
지금까지 우리는 System.out.println();문을 사용하여 로그를 기록했다.
이것이 로그를 남기기 위한 가장 쉬운 방법이다.
이보다 로그를 기록하는 클래스를 만들어 사용하는 게 더 나은 방법이다.
다음 클래스는 사용자 정의 로그 클래스이다.
Log.java
package net.java_school.util; import java.io.*; import java.util.Date; public class Log { String logFile = "C:/debug.log"; FileWriter fw; static final String ENTER = System.getProperty("line.separator"); public Log() { try { fw = new FileWriter(logFile, true); } catch (IOException e){} } public void close() { try { fw.close(); } catch (IOException e){} } public void debug(String msg) { try { fw.write(new Date()+ " : "); fw.write(msg + ENTER); fw.flush(); } catch (IOException e) { System.err.println("IOException!"); } } }
아래 파일을 만들어 Log 클래스를 테스트한다.
LogTest1.java
package net.java_school.logtest; import net.java_school.util.Log; public class LogTest1 { public void someMethod() { Log log = new Log(); //출력스트림 생성 log.debug("로그 테스트!"); //로그 기록하기 log.close(); //출력스트림 닫기 } public static void main(String[] args) { LogTest1 test = new LogTest1(); test.someMethod(); } }
Log4j 2
로그를 전담하는 프레임워크가 있다.
프레임워크란 공통적인 작업을 자동화하고, 개발자로 하여금 빨리 애플리케이션을 개발하도록 하기 위한 노력의 산물을 말한다.
첫 번째로 설명할 프레임워크는 Log4j 2이다.
Log4j 2를 사용하기 위해서는 아래 경로에서 Log4j 2 바이너리 파일을 내려받는다.
http://logging.apache.org/log4j/2.x/download.html
파일 압축을 풀면 생기는 디렉터리에서 log4j-api-2.x.x.jar 와 log4j-core-2.x.x.jar을 복사하고 클래스로더가 찾을 수 있는 경로에 붙여넣는다.
그다음 아래 프로퍼티 파일을 역시 클래스로더가 찾을 수 있는 경로에 생성한다.
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?> <Configuration> <Appenders> <File name="A1" fileName="A1.log" append="true"> <PatternLayout pattern="%t %-5p %c{2} - %m%n" /> </File> <Console name="STDOUT" target="SYSTEM_OUT"> <PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n" /> </Console> </Appenders> <Loggers> <Logger name="net.java_school" level="debug"> <AppenderRef ref="A1" /> </Logger> <Root level="debug"> <AppenderRef ref="STDOUT" /> </Root> </Loggers> </Configuration>
이클립스에서 Log4j 2 테스트
- Java Project 퍼스펙티브에서 새로운 프로젝트 생성한다.
- Log4j 2 라이브러리를 프로젝트 빌드패스에 추가한다.
- Package Explorer 뷰에서 프로젝트를 선택하고 마우스 오른쪽 버튼을 클릭한다.
- Build Path - Configure Build Path...를 선택한다.
- Libraires 탭에서 Add External JARs...를 선택한다.
- log4j-api-2.x.x.jar 와 log4j-core-2.x.x.jar를 추가하고 OK 버튼을 클릭한다.
- 아래 LogTest2.java 파일을 생성한다.
LogTest2.java
package net.java_school.logtest; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; public class LogTest2 { //private Logger log = LogManager.getLogger(LogTest2.class); //ROOT 로거만 작동 private Logger log = LogManager.getLogger("net.java_school"); public void xxx() { if (log.isDebugEnabled()) { log.debug("debug message"); } } public void yyy() { if (log.isInfoEnabled()) { log.info("info message"); } } public static void main(String[] args) { LogTest2 test = new LogTest2(); test.xxx(); test.yyy(); } }
실행하여 콘솔과 A1.log 파일에서 로그 메시지를 확인한다.
LogTest1.java처럼 파일에 로그를 쓰지만 출력 스트림을 닫는 코드가 없음에 주목하자.
Log4j2.xml 설정 파일에서 주요 엘리먼트 설명
Log4j 2는 Logger, Appender, Layout라는 세 개의 주요 엘리먼트가 있다. 이들의 협력으로 로그를 기록한다.
- Logger : 로그의 주체로 로그 메시지를 Appender에 전달한다.
- Appender : 로그 메시지를 출력할 대상을 지정한다. Appenders
- Layout : 로그의 포맷을 지정한다. Layouts
루트 로거는 언제나 존재하며, 모든 로거 중 가장 위에 있다.
자세한 설명은 http://logging.apache.org/log4j/2.x/manual/configuration.html에서 찾을 수 있다.
Log Level
로그 레벨은 아래와 같이 계층적으로 구성되어 있다.
TRACE > DEBUG > INFO > WARN > ERROR > FATAL
INFO로 셋팅하면, INFO, INFO, WARN, ERROR, FATAL은 기록된다.
- FATAL : 프로그램이 중지될 수도 있는 치명적인 에러 이벤트
- ERROR : 프로그램이 중지될 정도는 아닌 에러 이벤트
- WARN : 잠재적인 위험
- INFO : 대략적으로 프로그램의 진행 상황을 강조
- DEBUG : 응용 프로그램을 디버깅하는 데 가장 유용한 세밀한 정보 이벤트
- TRACE : DEBUG보다 세분화 된 정보 이벤트
Commons Logging와 Log4j 2 연동
아파치 그룹의 commons-logging은 개발자들에게 공통 로깅 API를 제공하기 위해 만들어진 프레임워크로 애플리케이션이 특정 로깅 프레임워크에 종속되지 않게 한다.
현재 많은 서드 파티 로깅 프레임워크들이 commons-logging 기반으로 구현되어 있다.
아래 주소에서 바이너리 배포본을 내려받는다.
http://apache.mirror.cdnetworks.com/commons/logging/binaries/
다운로드하고 압축을 풀면 서브 폴더에 commons-logging-1.2.jar 파일이 생긴다.
이 파일을 클래스 패스에 복사한다.
commons-logging은 자체적으로 로깅을 지원한다기보다는 여러 로깅 API를 표준화된 방법으로 사용할 수 있게 해주는 개념이기 때문에,
실제 로깅 처리를 위한 별도의 로깅 구현체가 필요하다.
여기서는 로깅 구현체로 Log4j 2를 사용하는 방법을 설명한다.
아래 프로퍼티 파일을 클래스 패스에 생성한다.
commons-logging.properties
org.apache.commons.logging.Log = org.apache.commons.logging.impl.Log4JLogger
테스트 파일을 만들고 테스트한다.
LogTest3.java
package net.java_school.logtest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class LogTest3 { private Log log = LogFactory.getLog(LogTest3.class); // 또는 private Log log = LogFactory.getLog(this.getClass()); public void xxx() { if (log.isDebugEnabled()) { log.debug("debug message"); } } public void yyy() { if (log.isInfoEnabled()) { log.info("info message"); } } public static void main(String[] args) { LogTest3 test = new LogTest3(); test.xxx(); test.yyy(); } }
commons-logging-1.2.jar와 log4j-jcl-2.8.jar를 빌드 패스에 추가한다. log4j-jcl-2.8.jar는 압축을 푼 Log4j 2 디렉터리에서 찾을 수 있다.
slf4j
다음 주소에서 최신 배포본을 내려받는다.
http://www.slf4j.org/download.html
압축을 풀고 slf4j-api-1.7.25.jar 와 slf4j-simple-1.7.25.jar 파일을 클래스 패스에 추가한다.
다음 예제를 작성하고 테스트한다.
LogTest4.java
package net.java_school.bank; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LogTest4 { public static void main(String[] args) { Logger logger = LoggerFactory.getLogger(Test.class); int amount = 1000; int balance = 10000; logger.info("amount: {}, balance: {}", amount, balance); } }
slf4j의 디폴트 레벨은 INFO이다.
로깅 레벨을 DEBUG로 변경하려면 클래스 패스에 simplelogger.properties 파일을 아래 내용으로 만든다.
simplelogger.properties
org.slf4j.simpleLogger.defaultLogLevel=DEBUG
자바은행 예제에 slf4j 적용하기
slf4j-api-1.7.25.jar 와 slf4j-simple-1.7.25.jar 파일을 자바은행 클래스 패스에 추가한다.
Account, NormalAccount, MinusAccount 클래스의 입금과 출금 메소드에 로깅을 적용한다.
Account.java
package net.java_school.bank; //..중간 생략.. import org.slf4j.Logger; import org.slf4j.LoggerFactory; public abstract class Account implements Serializable { Logger logger = LoggerFactory.getLogger(Account.class); //..중간 생략.. public synchronized void deposit(double amount) { balance = balance + amount; Transaction transaction = new Transaction(); Calendar cal = Calendar.getInstance(); Date date = cal.getTime(); transaction.setTransactionDate(Account.DATE_FORMAT.format(date)); transaction.setTransactionTime(Account.TIME_FORMAT.format(date)); transaction.setAmount(amount); transaction.setBalance(balance); transaction.setKind(DEPOSIT); transactions.add(transaction); logger.debug("AccountNo:{} Amount:{} DEPOSIT/WITHDRAW:{} NORMAL/MINUS:{}", accountNo, amount, DEPOSIT, getKind()); } //..중간 생략.. }
NormalAccount.java
@Override public synchronized void withdraw(double amount) { if (amount > balance) { throw new InsufficientBalanceException("잔액이 부족합니다."); } balance = balance + amount; Transaction transaction = new Transaction(); Calendar cal = Calendar.getInstance(); Date date = cal.getTime(); transaction.setTransactionDate(Account.DATE_FORMAT.format(date)); transaction.setTransactionTime(Account.TIME_FORMAT.format(date)); transaction.setAmount(amount); transaction.setBalance(balance); transaction.setKind(Account.WITHDRAW); transactions.add(transaction); logger.debug("AccountNo:{} Amount:{} DEPOSIT/WITHDRAW:{} NORMAL/MINUS:{}", getAccountNo(), amount, Account.WITHDRAW, getKind()); }
MinusAccount.java
@Override public synchronized void withdraw(double amount) { balance = balance - amount; Transaction transaction = new Transaction(); Calendar cal = Calendar.getInstance(); Date date = cal.getTime(); transaction.setTransactionDate(Account.DATE_FORMAT.format(date)); transaction.setTransactionTime(Account.TIME_FORMAT.format(date)); transaction.setAmount(amount); transaction.setBalance(balance); transaction.setKind(Account.WITHDRAW); transactions.add(transaction); logger.debug("AccountNo:{} Amount:{} DEPOSIT/WITHDRAW:{} NORMAL/MINUS:{}", getAccountNo(), amount, Account.WITHDRAW, this.getKind()); }
로그 메시지를 콘솔이 아닌 파일에 출력하기를 원한다면 simplelogger.properties에 아래 설정을 추가한다.
simplelogger.properties
org.slf4j.simpleLogger.logFile=C:/java/Bank/javabank.log org.slf4j.simpleLogger.defaultLogLevel=DEBUG
javabank.log 파일에 로그가 기록된다.
하지만 simpleLogger는 로그 파일에 로그 메시지를 축적하지 못한다.
logback
다음 주소에서 최신 배포본을 내려받는다.
http://logback.qos.ch/download.html
logback-core-x.x.x.jar, logback-classic-x.x.x.jar, logback-access-x.x.x.jar 파일을 클래스 패스에 추가한다.
slf4j-simple-x.x.x.jar을 빌드 패스에서 제거한다.
다음과 같이 logback.xml 파일을 생성한다.
(logbkack.xml은 웹 애플리케이션의 WEB-INF/classes 아래 생성되도록 작성한다)
logback.xml
<configuration> <appender name="FILE" class="ch.qos.logback.core.FileAppender"> <file>javabank.log</file> <encoder> <pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern> </encoder> </appender> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder by default --> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="debug"> <appender-ref ref="FILE" /> <appender-ref ref="STDOUT" /> </root> </configuration>
LogTest4.java를 다시 실행한다.
이번에는 프로젝트 루트 디렉터리에 javabank.log 파일이 생성되고, 이 파일에 로그가 축적된다.