본문 바로가기

학원/복기

[Spring] 로그 구현체 설정

로그 구현체를 설정해보자

 

 

main에 있는 log4j.xml과 test에 있는 log4j.xml은 각각 다르다

 

먼저 src/main/resources의 log4j.xml 파일을 설정해보자

 

log4j 라이브러리는 로그 구현체가 실행될 때 빌드된다.

 

 

따라서 log4j.dtd를 찾을 수 없기 때문에 에러가 발생한다

 

이를 해결하기 위해보자

 

해결방법1)

pom.xml에서 scope 엘리먼트를 지워버리면 된다. (scope 엘리먼트가 프로젝트에 빌드되는 시점을 설정하기 때문에)

*scope 엘리먼트를 사용하는 이유는 경량화를 위해서이다.

 

해결방법2)

[log4j.xml]에 인터넷 상의 파일로 변경해준다

<!DOCTYPE log4j:configuration SYSTEM "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">

 


 

log4j.xml 

log4j.xml 파일은 로그 구현체의 실행을 위한 정보를 제공하는 환경설정파일이다.

 

appender 

appender는 로그 구현체로 실행하기 위한 엘리먼트이다.

  • name 속성 : appender 엘리먼트를 구분하기 위한 식별자를 속성값으로 설정한다.
  • class 속성 : 로그 구현체로 실행되기 위한 클래스를 속성값으로 설정한다. 
    • class 속성값으로 설정된 클래스는 반드시 Appender 인터페이스를 상속받아 작성해야 한다. 
    • class 속성값으로 설정된 클래스에 따라 로깅정보를 기록하는 방식이 다르게 설정된다.
    •            ex) ConsoleAppender 클래스는 로깅정보를 서버 콘솔에 기록하기 위한 클래스이다.

 

param

param은 Appender 클래스에 필요한 값을 제공하기 위한 엘리먼트이다.

 

layout 

layout은 로깅정보를 제공받아 기록(로그) 형식을 설정하기 위한 엘리먼트이다.

  • class 속성 : 로그 형식 관련 정보를 제공하기 위한 클래스를 속성값으로 설정한다.
  • ex) PatternLayout 클래스는 로그 형식을 작성하기 위해 변환 문자를 제공하는 클래스이다.

 

# 변환문자(Conversion Character) - 변환문자를 일반문자와 구분하기 위해 % 기호로 시작된다.

  • %c : 카테고리를 기록한다. - 패키지가 포함된 클래스로도 표현이 가능하다. 
  • %C : 패키지가 포함된 클래스의 이름을 기록한다, -  {정수값} 을 이용하여 원하는 형식으로 기록한다.
  •         ex) %c{1} : 패키지를 제외한 클래스의 이름을 기록 
  • %d : 날짜와 시간을 기록한다. - {SimpleDateFormat}을 이용하여 원하는 형식으로 기록할 수 있다.
  •         ex) %d{yyyy-MM-dd} : [년-월-일] 형식으로 기록
  • %m : 로그 이벤트에 의해 생성된 로그 메세지를 기록한다.
  • %n : 엔터(Enter)를 기록한다. - 로그 줄바꿈
  • %M : 로그 이벤트가 발생된 메소드의 이름을 기록한다.
  • %p : 로깅정보를 제공한 로그 이벤트를 기록한다
    • 로그 이벤트 : TRACE(DEBUG 레벨이 너무 광범위한 것을 해결하기 위해서 좀 더 상세한 상태를 나타냄) >> DEBUG(디버그 용도로 사용한 메시지) >> INFO(로그인, 상태변경과 같은 정보성 메시지) >> WARN(향후 시스템 에러의 원인이 될 수 있는 경고성 메시지) >> ERROR( 요청을 처리하는 중 문제가 발생한 상태) >> FATAL(아주 심각한 에러)
  • %t : 로그 이벤트를 발생시킨 스레드의 이름을 기록한다.
  • %r : 프로그램 시작 이후 부터 로깅 정보를 제공받아 기록되는 시간(ms)을 기록한다. 
<appender name="console" class="org.apache.log4j.ConsoleAppender">
	<param name="Target" value="System.out" />
	<layout class="org.apache.log4j.PatternLayout">
		<param name="ConversionPattern" value="%-5p: %c - %m%n" />
	</layout>
</appender>

 

root

root는 모든 클래스에서 발생되는 로그 이벤트를 기록하기 위한 엘리먼트이다.

→ 모든 logger 엘리먼트는 root 엘리먼트의 정보를 상속받아 사용한다.

 

logger 

logger은 특정 패키지의 클래스에서 발생되는 로그 이벤트를 기록하기 위한 엘리먼트이다.

  • name 속성 : 패키지 정보를 속성값으로 설정한다.

level 

level은 로그 이벤트를 설정하기 위한 엘리먼트이다.

 속성값으로 설정된 로그 이벤트 및 상위 로그 이벤트에 대한 로깅정보를 기록한다.

 

piority

piority는 기본적으로 적용하기 위한 로그 이벤트를 설정하는 엘리먼트이다.

  • value 속성 : 로그 이벤트를 속성값으로 설정한다.

appender-ref

appender-ref는 참조할 Appender를 설정하기 위한 엘리먼트이다.

  • ref 속성 : appender 엘리먼트의 식별자를 속성값으로 설정한다.
<!-- logger : 특정 패키지의 클래스에서 발생되는 로그 이벤트를 기록하기 위한 엘리먼트  -->
<logger name="xyz.itwill.controller">
	<!-- info 레벨 이상의 로그 이벤트가 발생되면 기록 -->
	<level value="info" />
</logger>
	
<!-- 3rdparty Loggers -->
<logger name="org.springframework.core">
	<level value="info" />
</logger>
	
<logger name="org.springframework.beans">
	<level value="info" />
</logger>
	
<logger name="org.springframework.context">
	<level value="info" />
</logger>

<logger name="org.springframework.web">
	<level value="info" />
</logger>

<!-- Root Logger -->
<!-- root : 모든 클래스에서 발생되는 로그 이벤트를 기록하기 위한 엘리먼트 -->
<root>
	<!-- warn 레벨 이상의 로그 이벤트가 발생되면 consoleAppender를 사용  -->
	<priority value="warn" />
	<appender-ref ref="console" />
</root>

 

[log4j.xml 전체 소스코드]

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
<!-- log4j.xml : 로그 구현체의 실행을 위한 정보를 제공하는 환경설정파일 -->
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

	<!-- Appenders -->
	<appender name="console" class="org.apache.log4j.ConsoleAppender">
		<param name="Target" value="System.out" />
		<layout class="org.apache.log4j.PatternLayout">
			<param name="ConversionPattern" value="%-5p: %c - %m%n" />
		</layout>
	</appender>
	
	<!-- Application Loggers -->
	<!-- logger : 특정 패키지의 클래스에서 발생되는 로그 이벤트를 기록하기 위한 엘리먼트  -->
	<logger name="xyz.itwill.controller">
		<!-- info 레벨 이상의 로그 이벤트가 발생되면 기록 -->
		<level value="info" />
	</logger>
	
	<!-- 3rdparty Loggers -->
	<logger name="org.springframework.core">
		<level value="info" />
	</logger>
	
	<logger name="org.springframework.beans">
		<level value="info" />
	</logger>
	
	<logger name="org.springframework.context">
		<level value="info" />
	</logger>

	<logger name="org.springframework.web">
		<level value="info" />
	</logger>

	<!-- Root Logger -->
	<!-- root : 모든 클래스에서 발생되는 로그 이벤트를 기록하기 위한 엘리먼트 -->
	<root>
		<!-- warn 레벨 이상의 로그 이벤트가 발생되면 consoleAppender를 사용  -->
		<priority value="warn" />
		<appender-ref ref="console" />
	</root>
	
</log4j:configuration>

 


로그 구현체의 예제를 보자

 

먼저 로그 구현체를 왜 사용하는지 이유를 알아보기 위해 로그 구현체를 사용하지 않고 명령을 기록해 보겠다.

 

xyz.itwill00.log 패키지 생성

 

package xyz.itwill00.log;

public class HelloWorld {
	public String hello(String name) {
		String message=name+"님, 안녕하세요.";
		return message;
	}
}

//[HelloWorld] 클래스를 객체로 만들어 hello 메소드 호출하기 위한 [HelloWorldApp] 클래스 선언
public class HelloWorldApp {
	public static void main(String[] args) {
		HelloWorld hw=new HelloWorld();
		String message=hw.hello("홍길동");
		System.out.println("message = "+message);//message = 홍길동님, 안녕하세요.
	}
}

 

 

로깅에 관련된 로그구현체가 없다면 콘솔에 기록해야할 때, System.out.println()을 이용해 기록할 수 있다.

 

package xyz.itwill00.log;

public class HelloWorld {
	public String hello(String name) {
		System.out.println("HelloWorld 클래스의 hello 메소드 - 시작");
		String message=name+"님, 안녕하세요.";
		System.out.println("HelloWorld 클래스의 hello 메소드 - 종료");
		return message;
	}
}

public class HelloWorldApp {
	public static void main(String[] args) {
		System.out.println("HelloWorldApp 클래스의 main 메소드 - 시작");
		HelloWorld hw=new HelloWorld();
		String message=hw.hello("홍길동");
		System.out.println("message = "+message);//message = 홍길동님, 안녕하세요.
		System.out.println("HelloWorldApp 클래스의 main 메소드 - 종료");
	}
}

 

console 출력결과

 

 

이 방식을 사용하면 기록되는 명령과 프로그램에서 데이터를 처리하는 명령이 하나의 스레드에 의해 동작되기 때문에 가독성이 느려지고 유지보수의 효율성도 떨어진다.

 

이런 문제점을 해결하기 위해 로그 구현체를 이용하는 것이다.

 

이번엔 로그 구현체를 이용해 기록하는 방법에 대해 알아보자

 

 

Logger 객체 : 로그 이벤트를 발생시키기 위한 객체

→ 로그 이벤트가 발생되면 로그 구현체가 로깅 정보를 제공받아 기록한다.

LoggerFactory 클래스 : Logger 객체를 생성하여 제공하기 위한 클래스

LoggerFactory.getLogger(Class<T> claszz) : 메모리에 저장된 클래스(Class 객체 - Clazz)를 전달받아 해당 클래스에서 로그 이벤트를 발생할 수 있는 Logger 객체를 생성하여 반환하는 메소드 

Logger.info(String message) : Logger 객체로 INFO 레벨의 로그 이벤트를 발생하는 메소드 

→ 매개변수에는 로그 구현체로 기록될 로그 메세지를 전달한다.

 

 

[LogHelloWorld] 클래스 선언

package xyz.itwill00.log;

public class LogHelloWorld {
	public String hello(String name) {
		String message=name+"님, 안녕하세요.";
		return message;
	}
}

 

[LogHelloWorldApp] 클래스 선언

package xyz.itwill00.log;

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

public class LogHelloWorldApp {
	//Logger 객체 생성
	private static final Logger logger=LoggerFactory.getLogger(LogHelloWorldApp.class);//바뀌면 안되므로 상수처럼 만들어서 사용한다 
	
	public static void main(String[] args) {
		logger.info("시작");//info 레벨의 이벤트를 발생시킴
		//logger.debug("시작");//debug 레벨의 이벤트를 발생시킴
		LogHelloWorld hw=new LogHelloWorld();
		String message=hw.hello("홍길동");
		System.out.println("message = "+message);//message = 홍길동님, 안녕하세요.
		logger.info("종료");
	}
}

 

log4j.xml 의 설정 때문에 해당 로그이벤트는 경고 이상일 때만 기록이 되고, info 레벨일 때는 기록이 되지 않는다.

<root>
	<!-- warn 레벨 이상의 로그 이벤트가 발생되면 consoleAppender를 사용  -->
	<priority value="warn" />
	<appender-ref ref="console" />
</root>

 

따라서 이를 해결하기 위해서는  info 레벨이 아닌 warn 이상의 레벨의 이벤트를 발생시키면 된다.

 

 

만약  info 레벨에서 이벤트를 기록하고 싶다면, log4j.xml에 logger 엘리먼트를 추가해주면 된다.

<logger name="xyz.itwill00.log">
	<level value="info"/>
</logger>

console 출력결과

 

 


additivity 속성

 

 

새로운 Appender를 등록해 기록하는 것도 가능하다. 

 

[log4j.xml]

<appender name="helloLog" class="org.apache.log4j.ConsoleAppender">
	<param name="Target" value="System.out" />
	<layout class="org.apache.log4j.PatternLayout">
		<param name="ConversionPattern" value="%c{1} 클래스의 %M 메소드 - %m%n" />
	</layout>
</appender>

<!-- ...  -->
<!-- ...  -->
<logger name="xyz.itwill00.log">
	<level value="info"/>
	<appender-ref ref="helloLog" /><!-- hello Appender 사용 -->
</logger>

 

 

하지만 이렇게 하면 기록이 중복되어 출력된다. 

 

 

logger 엘리먼트는 기본적으로 root 엘리먼트의 Appender(예시에선 console)를 제공받아 기록된다.

<root>
	<priority value="warn" />
	<appender-ref ref="console" />
</root>

 

이를 사용하고 싶지 않으면 additivity 속성을 사용하면 된다.

 

additivity 속성에는 false 또는 true 중 하나를 속성값으로 설정하면 된다.

속성값이 [false]인 경우 root 엘리먼트의 Appender를 제공받지 않도록 설정하고, [true]인 경우에는 root 엘리먼트의 Appender를 제공받아 사용하도록 설정한다. (기본값은 true)

<logger name="xyz.itwill00.log" additivity="false">
	<level value="info"/>
	<appender-ref ref="helloLog" />
</logger>

 

 


 

이번엔 파일에 기록하는  파일Appender를 만들어 보자 

 

[log4j.xml]

<appender name="dailyLogFile" class="org.apache.log4j.DailyRollingFileAppender">
	<param name="File" value="dailyLog" />
	<param name="DatePattern" value=".yyyy-MM-dd" />
	<layout class="org.apache.log4j.PatternLayout">
		<param name="ConversionPattern" value="[%d{HH:mm:ss}]%-5p : %c - %m%n" />
	</layout>
</appender>

<!-- ... -->
<!-- ... -->

<logger name="xyz.itwill00.log" additivity="false">
	<level value="info"/>
	<appender-ref ref="helloLog" />
	<appender-ref ref="dailyLogFile" />
</logger>

 

 

[LogHelloWorldApp] 클래스에서 [F5]를 누르면 [dailyLog] 파일이 생성되는 것을 확인할 수 있다.