반응형

1. 개요

 - 스프링 부트 환경에서 기본으로 제공하는 LogBack을 사용하여 로그를 남겨보자

 - spring.profiles.active를 사용하여 운영, 개발 환경에 따라 로그 설정을 분기하여 적용해보자

 [참고 : https://goddaehee.tistory.com/206 깃대희의 작은공간]

 

2. LogBack이란?

 - LogBack이란 Log4j를 만든 개발자가 Log4j를 기반으로 속도와 메모리 점유율을 개선하여 만든 로깅 프레임워크이다.

 - org.slf4j.Logger 인터페이스의 구현체이다.

   >> 코드 작성 시 이 인터페이스를 임포트해주면 된다.

 

3. LogBack 특징

 - Level : 로그 레벨을 설정할 수 있다.

 - Appender : 출력 방법을 선택할 수 있다. ex) Console, RollingFile 등

 - Logger : 로그마다 다른 설정을 적용시킬 수 있다.

 - Authmatic Reloading Configuration File : 특정 시간마다 별도의 스레드를 통해 설정 파일 변경 여부 파악 및 적용이 가능하다. > logback 설정 변경 시 서버 재시작이 필요없다.

 

4. 프로젝트 세팅

 1) Controller - 로그를 찍기 위한 클래스

 2) application.properties - spring.profiles.active 설정 추가

 3) logback-spring.xml - logback 설정

 4) logback-{운용환경}.properties - logback 설정파일에서 로드할 상수 (로그파일, 레벨 등)

 

 4.1. Controller

  /api/log 요청 시 trace부터 error 까지의 로그를 쌓는 메서드를 작성한다.

@Controller
@RequestMapping("/api")
public class FileController {

	private final Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@GetMapping("/log")
	@ResponseBody
	public ResponseDTO main() {
		logger.trace("trace Log");
		logger.debug("debug Log");
		logger.info("info Log");
		logger.warn("warn Log");
		logger.error("error Log");
		
		return null;
	}
}

 

 4.2. application.properties

  spring.profiles.active=dev 를 추가하여 개발 및 운영 환경을 정의해준다.

#서버포트
server.port=9090

#운용환경 : 개발
spring.profiles.active=dev

 

 4.3. logback-spring.xml

resource 경로에 logback-spring.xml 을 생성한 후 logback 설정을 정의한다.

* resource 경로에 logback-spring.xml 파일이 있으면 서버 기동 시 자동으로 로드한다.

<?xml version="1.0" encoding="UTF-8"?>

<!-- 10초마다 파일 변화를 체크하여 갱신시킨다. -->
<configuration scan="true" scanPeriod="10 seconds">

	<!-- spring.profile에 따른 설정파일 분기 -->
	<springProfile name = "prod">
		<property resource = "logback-prod.properties"/>
	</springProfile>
	
	<springProfile name = "dev">
		<property resource = "logback-dev.properties"/>
	</springProfile>
	
	
	<!-- 루트 로그 레벨 -->
	<property name ="LOG_LEVEL" value = "${log.config.level}"/>
	
	<!-- 로그 파일 경로 -->
	<property name ="LOG_PATH" value = "${log.config.path}"/>
	
	<!-- 로그 파일 명 -->
	<property name ="LOG_FILE_NAME" value = "${log.config.filename}"/>
	<property name ="ERR_LOG_FILE_NAME" value = "${log.config.filename}_error"/>
	
	<!-- 로그 파일 패턴 -->
	<property name ="LOG_PATTERN" value = "%-5level %d{yyyy-MM-dd HH:mm:ss}[%thread] [%logger{0}:%line] - %msg%n"/>
	
	
	
	<!-- 콘솔 Appender 설정 -->
	<appender name ="CONSOLE" class ="ch.qos.logback.core.ConsoleAppender">
		<encoder class ="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
			<pattern>${LOG_PATTERN}</pattern>
		</encoder>
	</appender>
	
	<!-- 파일 Appender 설정 -->
	<appender name="FILE" class ="ch.qos.logback.core.rolling.RollingFileAppender">
		<!-- 파일 경로 설정 -->
		<file>${LOG_PATH}/${LOG_FILE_NAME}.log</file>
		
		<!-- 로그 패턴 설정 -->
		<encoder class = "ch.qos.logback.classic.encoder.PatternLayoutEncoder">
			<pattern>${LOG_PATTERN}</pattern>
		</encoder>
		
		<!-- 롤링 정책 -->
		<rollingPolicy class = "ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			<!-- gz, zip 등을 넣을 경우 자동 로그파일 압축 -->
			<fileNamePattern>${LOG_PATH}/%d{yyyy-MM-dd}/${LOG_FILE_NAME}_%i.log</fileNamePattern>
			
			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
				<!-- 파일당 최고 용량 -->
				<maxFileSize>10MB</maxFileSize>
			</timeBasedFileNamingAndTriggeringPolicy>
			
			<!-- 로그파일 최대 보관주기 -->
			<maxHistory>30</maxHistory>
		</rollingPolicy>
	</appender>
	
	
	<appender name = "ERROR" class ="ch.qos.logback.core.rolling.RollingFileAppender">
		<filter class ="ch.qos.logback.classic.filter.LevelFilter">
			<level>error</level>
			<onMatch>ACCEPT</onMatch>
			<onMismatch>DENY</onMismatch>
		</filter>
		<file>${LOG_PATH}/${ERR_LOG_FILE_NAME}.log</file>
		
		<!-- 로그 패턴 설정 -->
		<encoder class = "ch.qos.logback.classic.encoder.PatternLayoutEncoder">
			<pattern>${LOG_PATTERN}</pattern>
		</encoder>
		
		<!-- 롤링 정책 -->
		<rollingPolicy class = "ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			<!-- gz, zip 등을 넣을 경우 자동 로그파일 압축 -->
			<fileNamePattern>${LOG_PATH}/%d{yyyy-MM-dd}/${ERR_LOG_FILE_NAME}_%i.log</fileNamePattern>
			
			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
				<!-- 파일당 최고 용량 -->
				<maxFileSize>10MB</maxFileSize>
			</timeBasedFileNamingAndTriggeringPolicy>
			
			<!-- 로그파일 최대 보관주기 -->
			<maxHistory>30</maxHistory>
		</rollingPolicy>
	</appender>
	
	<root level = "${LOG_LEVEL}">
		<appender-ref ref="CONSOLE"/>
		<appender-ref ref="FILE"/>
		<appender-ref ref="ERROR"/>
	</root>
	
	<logger name="org.apache.ibatis" level = "DEBUG" additivity = "false">
		<appender-ref ref="CONSOLE"/>
		<appender-ref ref="FILE"/>
		<appender-ref ref="ERROR"/>
	</logger>
</configuration>

 - springProfile 태그를 사용하여 시스템 변수의 spring.profile.active 값을 조회할 수 있다. 이 값에 따라 logback-prod, logback-dev 파일 중 하나를 로드하여 설정파일의 프로퍼티로 사용한다.

 

 4.4. logback-dev.properties

 - 개발환경에서는 로그레벨을 debug로 설정한다.

#로그 레벨
log.config.level=debug

#로그파일 경로
log.config.path=/logs

#로그파일 명
log.config.filename=reckey

 

 4.5. logback-prod.properties

 - 운영 환경에서는 로그레벨을 info로 설정한다.

#로그 레벨
log.config.level=info

#로그파일 경로
log.config.path=/logs

#로그파일 명
log.config.filename=reckey

 

5. 테스트

 spring.profile.active를 prod 설정하면 로그레벨이 INFO로 잡혀있어 불필요한 로그는 조회되지 않지만 dev로 설정할 경우 서버 기동 시 발생되는 로그, DB 로그 등이 중구난방으로 나오게 된다.

 원인은 dev로 설정할 경우 logback 설정의 루트 로그 레벨이(root level) 값이 debug로 설정되어 어플리케이션 내 모든 클래스의 debug 로그가 찍히기 때문이다.

 이런 로그들은 특수한 목적이 없는 한 debug보단 info로 올리는 것이 좋다. 이를 위해서는 logger 태그를 사용하여 클래스별 로그 레벨 분기처리를 해야한다.

DEBUG 2021-10-07 02:11:23[http-nio-9090-exec-1] [AnonymousAuthenticationFilter:96] - Set SecurityContextHolder to anonymous SecurityContext
DEBUG 2021-10-07 02:11:23[http-nio-9090-exec-1] [FilterSecurityInterceptor:210] - Authorized filter invocation [GET /api/log] with attributes [permitAll]
DEBUG 2021-10-07 02:11:23[http-nio-9090-exec-1] [FilterChainProxy:323] - Secured GET /api/log
DEBUG 2021-10-07 02:11:23[http-nio-9090-exec-1] [DispatcherServlet:91] - GET "/api/log", parameters={}
DEBUG 2021-10-07 02:11:23[http-nio-9090-exec-1] [PropertySourcedRequestMappingHandlerMapping:108] - looking up handler for path: /api/log
DEBUG 2021-10-07 02:11:23[http-nio-9090-exec-1] [RequestMappingHandlerMapping:522] - Mapped to com.reckey.controller.FileController#main()
DEBUG 2021-10-07 02:11:23[http-nio-9090-exec-1] [FileController:24] - debug Log
INFO  2021-10-07 02:11:23[http-nio-9090-exec-1] [FileController:25] - info Log
WARN  2021-10-07 02:11:23[http-nio-9090-exec-1] [FileController:26] - warn Log
ERROR 2021-10-07 02:11:23[http-nio-9090-exec-1] [FileController:27] - error Log
DEBUG 2021-10-07 02:11:23[http-nio-9090-exec-1] [RequestResponseBodyMethodProcessor:268] - Using 'application/json;q=0.8', given [text/html, application/xhtml+xml, image/avif, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, */*;q=0.8] and supported [application/json, application/*+json, application/json, application/*+json]
DEBUG 2021-10-07 02:11:23[http-nio-9090-exec-1] [RequestResponseBodyMethodProcessor:298] - Nothing to write: null body
DEBUG 2021-10-07 02:11:23[http-nio-9090-exec-1] [DispatcherServlet:1131] - Completed 200 OK
DEBUG 2021-10-07 02:11:23[http-nio-9090-exec-1] [HttpSessionSecurityContextRepository:346] - Did not store anonymous SecurityContext
DEBUG 2021-10-07 02:11:23[http-nio-9090-exec-1] [SecurityContextPersistenceFilter:118] - Cleared SecurityContextHolder to complete request
DEBUG 2021-10-07 02:11:25[reckeyPool housekeeper] [HikariPool:421] - reckeyPool - Pool stats (total=10, active=0, idle=10, waiting=0)
DEBUG 2021-10-07 02:11:25[reckeyPool housekeeper] [HikariPool:517] - reckeyPool - Fill pool skipped, pool is at sufficient level.

 

 5.1. 특정 클래스 분기처리 (로그레벨 INFO로 격상)

  일단 분기처리할 클래스의 경로를 확인해보자. 현재 로그 설정으로는 클래스 명만 나오고 있기 때문에 클래스 경로를 파악하기 힘들다. 로그 패턴에 클래스 경로 패턴인 %C를 다음과 같이 추가해보자.  자동 리로드 설정이 있으므로 서버 재시작은 하지 않아도 된다.

<property name ="LOG_PATTERN" value = "%-5level %d{yyyy-MM-dd HH:mm:ss}[%thread] [%C] [%logger{0}:%line] - %msg%n"/>

 그리고 다시 요청을 보내면 다음과 같이 클래스 경로가 함께 조회된다.

INFO  2021-10-07 02:14:37[restartedMain] [org.springframework.boot.StartupInfoLogger] [ReckeyApplication:61] - Started ReckeyApplication in 0.703 seconds (JVM running for 3806.024)
DEBUG 2021-10-07 02:14:37[restartedMain] [org.springframework.boot.availability.ApplicationAvailabilityBean] [ApplicationAvailabilityBean:77] - Application availability state LivenessState changed to CORRECT
INFO  2021-10-07 02:14:37[restartedMain] [org.springframework.boot.devtools.autoconfigure.ConditionEvaluationDeltaLoggingListener] [ConditionEvaluationDeltaLoggingListener:63] - Condition evaluation unchanged
DEBUG 2021-10-07 02:14:37[restartedMain] [org.springframework.boot.availability.ApplicationAvailabilityBean] [ApplicationAvailabilityBean:77] - Application availability state ReadinessState changed to ACCEPTING_TRAFFIC
DEBUG 2021-10-07 02:14:37[reckeyPool connection adder] [com.zaxxer.hikari.pool.HikariPool$PoolEntryCreator] [HikariPool:728] - reckeyPool - Added connection org.postgresql.jdbc.PgConnection@36e9f17b
DEBUG 2021-10-07 02:14:37[reckeyPool connection adder] [com.zaxxer.hikari.pool.HikariPool$PoolEntryCreator] [HikariPool:728] - reckeyPool - Added connection org.postgresql.jdbc.PgConnection@73eb69c2
DEBUG 2021-10-07 02:14:37[reckeyPool connection adder] [com.zaxxer.hikari.pool.HikariPool$PoolEntryCreator] [HikariPool:728] - reckeyPool - Added connection org.postgresql.jdbc.PgConnection@439a7476
DEBUG 2021-10-07 02:14:37[reckeyPool connection adder] [com.zaxxer.hikari.pool.HikariPool$PoolEntryCreator] [HikariPool:728] - reckeyPool - Added connection org.postgresql.jdbc.PgConnection@42521e57
DEBUG 2021-10-07 02:14:37[reckeyPool connection adder] [com.zaxxer.hikari.pool.HikariPool$PoolEntryCreator] [HikariPool:728] - reckeyPool - Added connection org.postgresql.jdbc.PgConnection@3bc95d4f
DEBUG 2021-10-07 02:14:37[reckeyPool connection adder] [com.zaxxer.hikari.pool.HikariPool$PoolEntryCreator] [HikariPool:728] - reckeyPool - Added connection org.postgresql.jdbc.PgConnection@13c4968d
DEBUG 2021-10-07 02:14:37[reckeyPool connection adder] [com.zaxxer.hikari.pool.HikariPool$PoolEntryCreator] [HikariPool:728] - reckeyPool - Added connection org.postgresql.jdbc.PgConnection@36d6947
DEBUG 2021-10-07 02:14:37[reckeyPool connection adder] [com.zaxxer.hikari.pool.HikariPool] [HikariPool:421] - reckeyPool - After adding stats (total=10, active=0, idle=10, waiting=0)
DEBUG 2021-10-07 02:15:07[reckeyPool housekeeper] [com.zaxxer.hikari.pool.HikariPool] [HikariPool:421] - reckeyPool - Pool stats (total=10, active=0, idle=10, waiting=0)
DEBUG 2021-10-07 02:15:07[reckeyPool housekeeper] [com.zaxxer.hikari.pool.HikariPool] [HikariPool:517] - reckeyPool - Fill pool skipped, pool is at sufficient level.
DEBUG 2021-10-07 02:15:37[reckeyPool housekeeper] [com.zaxxer.hikari.pool.HikariPool] [HikariPool:421] - reckeyPool - Pool stats (total=10, active=0, idle=10, waiting=0)
DEBUG 2021-10-07 02:15:37[reckeyPool housekeeper] [com.zaxxer.hikari.pool.HikariPool] [HikariPool:517] - reckeyPool - Fill pool skipped, pool is at sufficient level.
DEBUG 2021-10-07 02:16:07[reckeyPool housekeeper] [com.zaxxer.hikari.pool.HikariPool] [HikariPool:421] - reckeyPool - Pool stats (total=10, active=0, idle=10, waiting=0)
DEBUG 2021-10-07 02:16:07[reckeyPool housekeeper] [com.zaxxer.hikari.pool.HikariPool] [HikariPool:517] - reckeyPool - Fill pool skipped, pool is at sufficient level.
DEBUG 2021-10-07 02:16:37[reckeyPool housekeeper] [com.zaxxer.hikari.pool.HikariPool] [HikariPool:421] - reckeyPool - Pool stats (total=10, active=0, idle=10, waiting=0)
DEBUG 2021-10-07 02:16:37[reckeyPool housekeeper] [com.zaxxer.hikari.pool.HikariPool] [HikariPool:517] - reckeyPool - Fill pool skipped, pool is at sufficient level.
DEBUG 2021-10-07 02:17:07[reckeyPool housekeeper] [com.zaxxer.hikari.pool.HikariPool] [HikariPool:421] - reckeyPool - Pool stats (total=10, active=0, idle=10, waiting=0)
DEBUG 2021-10-07 02:17:07[reckeyPool housekeeper] [com.zaxxer.hikari.pool.HikariPool] [HikariPool:517] - reckeyPool - Fill pool skipped, pool is at sufficient level.

 org.springframework경로의 여러 클래스와 com.zaxxer.hikari로 시작하는 여러 클래스에서 DEBUG레벨로 로그들이 찍히고 있다. (히카리 로그는 DB연결로 인함입니다. 신경쓰지않으셔도됩니다.) 이 클래스들만 DEBUG레벨에서 INFO 레벨로 올린다면 이 문제가 해결될 것이다.

logback-spring.xml 파일의 logger 태그에 분기처리할 클래스를 다음과 같이 추가한다.

name에 들어가는 경로의 하위 모든 클래스에서 발생하는 로그를 INFO 레벨로 올리게 된다.

	<logger name="org.springframework" level = "INFO" additivity = "false">
		<appender-ref ref="CONSOLE"/>
		<appender-ref ref="FILE"/>
		<appender-ref ref="ERROR"/>
	</logger>
	
	<logger name="com.zaxxer.hikari" level = "INFO" additivity = "false">
		<appender-ref ref="CONSOLE"/>
		<appender-ref ref="FILE"/>
		<appender-ref ref="ERROR"/>
	</logger>

 

이제 실제 요청을 보내면 다음과 같이 불필요한 debug 레벨의 로그들을 쌓지 않게 된다.

DEBUG 2021-10-07 02:26:21[http-nio-9090-exec-1] [com.reckey.controller.FileController] [FileController:24] - debug Log
INFO  2021-10-07 02:26:21[http-nio-9090-exec-1] [com.reckey.controller.FileController] [FileController:25] - info Log
WARN  2021-10-07 02:26:21[http-nio-9090-exec-1] [com.reckey.controller.FileController] [FileController:26] - warn Log
ERROR 2021-10-07 02:26:21[http-nio-9090-exec-1] [com.reckey.controller.FileController] [FileController:27] - error Log

 

6. 마치며

 스프링 부트에서 로그백을 사용하여 로그를 남겨본 것은 처음이다. 생각보다 되게 간단하고, log4j 같은 경우 로그파일 리로드 설정을 외부 설정파일에서 해줘야하는데 logback은 리로드되는 기능이 설정 파일 안에 있어서 좋은것 같다.

반응형
반응형

1. 개요

 quartz 를 사용하여 1초마다 로그 클래스를 실행시키고, 발생하는 로그를 1분마다 Rolling 하여 파일로 남겨보자!


2. 환경

 1) maven

 2) JDK 1.8

 3) Tomcat 8.0


3. 준비

 3.1) Maven 라이브러리 추가

  스프링 라이브러리와 quartz 라이브러리를 추가해주자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.8.RELEASE</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>5.2.8.RELEASE</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.2.8.RELEASE</version>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
    <!-- dispatcherServlet 지원 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.8.RELEASE</version>
    </dependency>
        
    <!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <version>2.3.0</version>
    </dependency>
cs

 

 3.2) web.xml 파일 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  
      <!-- Controller가 공유하는 Bean들을 포함하는 Spring Container를 생성한다. 
             공통 설정파일은 여기에 생성한다. 즉 quartz 또한 이곳에 생성하는 것이 좋다.
      -->
      
      <context-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>
              /WEB-INF/spring/quartz-context.xml
          </param-value>
      </context-param>
      
      <listener>
          <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
      
      
     <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    
    <!-- /의 형식으로 시작하는 url에 대하여 UTF-8로 인코딩 -->
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
 
</web-app>
cs

 contextConfigLocation을 /WEB-INF/spring/quartz-context.xml 로 설정하고, UTF-8 인코딩 설정한다(인코딩 부분은 딱히 필요없지만 넣었다.)

 

 3.3) quartz-context.xml 파일 생성

 WEB-INF에 spring 폴더를 생성하고 내부에 quartz-context.xml 파일을 생성하자.

요로코롬.

이제 기본적인 준비가 끝났다. 실제 동작을 위한 코드를 삽입하자.


4. 클래스 생성 및 코딩

 - TestJob.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package quartz;
 
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
 
public class TestJob implements Job{
    
    private Logger logger = LogManager.getLogger(TestJob.class);
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // TODO Auto-generated method stub
        logger.debug("this is log and for you");
        logger.trace("this is log and for you");
        logger.info("this is log and for you");
        logger.warn("this is log and for you");
        logger.error("this is log and for you");
    }
}
 
cs

quartz를 적용시키려면 반드시 Job 인터페이스를 상속받아야 한다.

그리고 execute에 실행시킬 로직을 삽입한다.

보는것과 같이 log를 level 별로 찍고있다.

 

 - CronTrigger.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package quartz;
 
import static org.quartz.CronScheduleBuilder.cronSchedule;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;
 
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
 
public class CronTrigger {
    
    public CronTrigger() {
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        try {
            Scheduler scheduler = schedulerFactory.getScheduler();
            
            JobDetail job = newJob(TestJob.class)
                    .withIdentity("jobName""jobGroup")
                    .build();
            
            Trigger trigger = newTrigger()
                    .withIdentity("triggerName1""triggerGroup1")
                    .withSchedule(cronSchedule("0/1 * * * * ?"))
                    .build();
 
            scheduler.scheduleJob(job, trigger);
            
            scheduler.start();
        }catch(Exception e){
            System.out.println(e.getMessage());
        }
        
    }
}
cs

 

JobDetail 에는 스케줄링 하려는 클래스 정보를, Trigger에는 cron 설정을 통해 스케줄링 시간을 설정한다.

* "0/1 * * * * ?" : 1초마다 실행

 

29번 줄을 통해 스케줄러 잡에 job, trigger를 등록시키고, 31번줄에서 start 시킨다.

결과적으로 1초마다 TestJob의 execute 메서드가 실행된다.


5. quartz-context

 앞서 생성한 quartz-context 설정을 통해 CronTrigger 객체를 bean으로 등록하도록 한다.

 bean으로 등록하는 과정에서 기본생성자인 CronTrigger()를 호출하고, 이 생성자를 통해 설정이 세팅 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
 
    <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
    
    <beans:bean id = "cronTrigger" class = "quartz.CronTrigger"/>
    
</beans:beans>
cs

quartz.CronTrigger 객체를 bean으로 등록한다.

 

그 후 서버 tomcat 서버를 실행시키면 아래와 같은 라이프 사이클을 통해 quartz가 시작된다!

 

web.xml load -> quartz-context load -> CronTrigger bean 객체 생성 -> 기본생성자 호출 -> quartz 세팅 및 start!

 

실행 결과 아래와 같은 화면이 출력된다면 quartz 구현에 성공한 것이다.

console 로그

 

추가적으로! 앞의 게시글에서 설정했던 Rolling 파일을 확인해보자.

생성된 Rolling 폴더 및 log 파일
Rolling 파일

매 분마다 폴더가 생성되고, 그 시간동안 쌓였던 로그데이터가 해당 폴더로 들어가 zip 파일로 남게되는 것을 확인할 수 있을 것이다!

 

만약 이를 1일단위로 설정한다면 1일마다 쌓인 로그들이 압축파일 형태로 저장되기 때문에 로그로 인한 저장공간 사용량을 줄일 수 있고, 필요할 때마다 해당 일자의 로그를 살펴볼 수 있기 때문에 로그 관리의 효율성이 높아진다!

 

 

------끄읕------

반응형
반응형
반응형

목차

1. 개요

2. logging이란

3. log4j란

4. log4j 설치 및 설정

5. 예제


1. 개요

 서버는 클라이언트와 정보를 주고받는다. 다양한 이유로 그 과정에서 발생하는 여러 사건이나 정보들을 기록으로 남기는데 이 기록을 log라고 한다. 그리고 log를 남기는 행위를 '로깅'이라고 하는데, 모든 서버에 기본적으로 들어가는 개념이기 때문에 오늘은 이 logging에 대해 공부하는 시간을 가져보도록 하자. 구동 환경은 이클립스와 tomcat 7.0이다.


2. logging이란

 로깅이란 시스템 동작 시 시스템 상태/작동 정보를 시간의 경과에 따라 기록하는 것이다. 그 기록을 '로그'라고 한다.

 한마디로 '로깅 = 로그 기록'이다.

 로깅은 많은 부분에서 사용되는데, 사용자의 패턴이나 시스템 동작 분석에 사용되거나 해킹 사고가 발생할 경우 비정상 동작의 기록을 통해 추적하는 데 사용한다. 백엔드 개발자들의 경우 개발 과정에 있어 디버깅에 활용할 수도 있다. 이 외에도 다방면으로 쓰인다.

 그렇다면 Spring과 같은 Java환경에서 로깅을 하려면 어떻게 해야 할까? 시스템 동작에 대한 기록을 남기는 것이기 때문에 흔히 알고 있는 system.out.println()과 같은 메소드를 사용할 수 있겠다. 하지만 이는 메모리면에서 비효율적인 데다 클라이언트의 접속량이 많아질수록 안정적으로 실행되지 못하는 등 여러 문제를 안고 있다. (문제에 대해 궁금하다면 직접 구글링을 추천)

 이 문제에 대한 솔루션이자 Java 환경의 logging 시스템이 바로 log4j이다. log4j에 대해 본격적으로 알아보도록 하자.


3. log4j란 

 Java 환경의 로깅 시스템을 제공하는 라이브러리.

 Logger, Appender, Layout 등 다양한 컴포넌트가 사용되며, 로그 레벨을 분류하여 로그 정보를 출력시킬 수 있다.

 

 3.1) 컴포넌트

컴포넌트 설명
Logger 로그의 주체, 로그 파일을 작성하는 클래스
Appender 로그를 출력하는 위치
Layout Appender의 출력포맷(일자, 시간 등)을 설정하여 로그 내용으로 지정하는 속성. 

  Layout의 종류는 여러 가지가 있지만 일반적으로 디버깅에 가장 적합한 PattenLayout을 사용한다.

  PattenLayout이란 출력 포맷을 정해진 일련의 패턴을 사용하여 설정하는 것인데 패턴 정보는 아래와 같다.

패턴 설명
C 클래스명 출력
d 로그 시간 출력
F 파일명 출력. 수행한 메소드와 라인번호가 함께 출력.
L 라인 번호 출력
m 로그로 전달된 메시지 출력
M 로그를 수행한 메소드명 출력
n 개행
p 로그 이벤트명 (DEBUG 등)
r 로그 처리시간(milliseconds)
t 로그 이벤트가 발생된 쓰레드 출력

 위의 패턴을 잘 조합하여 로그에 대한 출력 포맷을 설정한다.

 

 3.2) 로그 레벨

 로그는 기본적으로 6개의 레벨을 갖는다. 아래로 갈수록 낮은 레벨이다.

로그 레벨 설명
fatal 시스템 문제와 같은 아주 심각한 에러가 발생한 상태를 나타냄.
error 요청을 처리하는중 문제가 발생한 상태를 나타냄.
warn 처리 가능한 문제이지만, 향후 시스템 에러의 원인이 될 수 있는 경고성 메시지를 나타냄.
info 로그인, 상태변경과 같은 정보성 메시지를 나타냄.
debug 개발시 디버그 용도로 사용한 메시지를 나타냄.
trace 디버그 레벨이 너무 광범위한것을 해결하기위해서 좀더 상세한 상태를 나타냄

 debug(), warn(), error()와 같이 메소드를 사용해서 로그 정보를 얻을 수 있으며, 로그 레벨 설정을 통해 로그를 통제할 수 있다. 이는 Logger의 Level 메소드를 통해 이루어지며, 지정한 로그 레벨보다 낮은 로깅 이벤트는 무시된다이는 뒷부분의 예제를 보면 이해할 수 있을 것이다.


4. log4j 설치 및 설정

 1) http://logging.apache.org/log4j/2.x/download.html 에서 최신버전.zip 파일 다운.

log4j 홈페이지

 2) 압축을 풀면 많은 파일이 있는데 그중 log4j-api-2.x.jar 와 log4j-core-2.x.jar 파일을 복사.

 

 3) 이클립스를 실행시켜 Dynamic Web Project를 생성하고 프로젝트 폴더에 lib 폴더를 생성.

 

 4) lib 폴더에 복사한 두 파일을 붙여넣기.

lib에 파일 붙여넣기

  5) 라이브러리를 추가는 '프로젝트 우클릭 → properties → Java Build Path → Libraries → Add JARs' 클릭

  

  6) lib에 있는 두 파일을 선택 후 Apply 클릭하면 라이브러리 설정이 완료.

프로젝트에 log4j 라이브러리 추가


5. 예제

  1) project  Java Resources → src 경로에 log4j2.xml 파일 생성 후 아래의 소스코드 추가.

     (log4j2.xml은 로깅에 대한 기본 환경 설정 파일)

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <Appenders>
        <Console name="STDOUT" target="SYSTEM_OUT">
            <PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n" />
        </Console>
    </Appenders>
    <Loggers>
        <Root level="debug">
            <AppenderRef ref="STDOUT" />
        </Root>
    </Loggers>
</Configuration>

  4번 라인 - 로그를 출력하는 위치(Appenders)를 Console로 설정. 로깅 이름은 STDOUT으로 설정.

  5번 라인 - patten을 시간, 로그 이벤트명, 쓰레드, 클래스명, 파일명, 라인 번호, 메시지, 개행으로 설정

  9~10번 라인 - STDOUT에 대한 Root level을 debug로 설정(서블릿에서 호출될 로그 중 debug보다 낮은 로그 레벨은 무시)

  

  2) proejct에 servlet 클래스를 생성하고 아래의 코드를 입력. 

  (servlet 생성 및 기본 개념을 모르겠다면 클릭)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package servlet;
 
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
 
/**
 * Servlet implementation class log
 */
@WebServlet("/log")
public class log extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private Logger logger = LogManager.getLogger(log.class);
    /**
     * @see HttpServlet#HttpServlet()
     */
    public log() {
        super();
        // TODO Auto-generated constructor stub
    }
 
    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        response.getWriter().append("Served at: ").append(request.getContextPath());
        logger.error("error message");
        logger.warn("warn message");
        logger.info("info message");
        logger.debug("debug message");
        logger.trace("trace message");
    }
 
    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        doGet(request, response);
    }
 
}
 

  18번 라인 - log 클래스에 대한 Logger객체 생성

  33~37번 라인 - 5가지의 로그 메소드 호출

 

  3) Servlet 파일 Run

실행 결과

  현재 servlet 파일에서 logger를 error, warn, info , debug, trace 총 5개 레벨에 대한 출력을 시도했으나 제일 낮은 레벨인 trace를 제외한 4개만 console로 출력된 상태이다. 이유는 앞서 언급했던 로그 레벨에 따른 통제와 관련이 있다.   log4j2.xml 파일에서 STDOUT 로깅 데이터에 대한 level이 debug로 설정되었기 때문이다. trace를 출력시키고 싶다면 xml 파일 9번째 줄의 debug 대신 trace를 넣어주면 된다.

 

  4) tomcat 서버 에러 발생 시

   tomcat 서버 에러가 발생할 경우 tomcat에 서버에 대해서도 log4j 라이브러리를 추가시켜줘야 한다. log4j를 사용하여 얻은 로그가 기본적으로 WAS(tomcat)에 남기 때문이다.

   추가 방법은 tomcat Server Overview → Open launch configuration → Classpath → UserEntries → Add JARs를 클릭하여 lib 폴더에 있는 두 라이브러리를 클릭하면 된다.

tomcat Overview
log4j 라이브러리 추가

 

시간이 늦은 관계로 log4j을 통한 롤링은 다음에 포스팅하도록 하겠다.

반응형

+ Recent posts