본문 바로가기
text/Java

log4j2 executable jar에 적용하기

by hoonzii 2021. 10. 17.
반응형

회사에서 특정시간마다 동작하는 모듈을 만들어야 됐다.

해당 모듈의 특징은 한번 동작할때 멀티쓰레드(implements Runnable) 를 적용해 여러개의 작업이 동시에 이루어지고 완료된다는 점이다.

 

이전까지는 각 쓰레드 별로 작업이 정상적으로 완료되었는지를 지정된 폴더에 파일로 각자 적게끔 구현했다.

(로그 파일을 직접생성하는 것이다. 게다가 Thread safe 하게끔 각자...)

그러다가 문득 "내가 왜 직접 파일에 하나씩 적게 만들지...?" 하는 생각이 들어서 좀더 괜찮게 기록하는 방법이 없을까 찾아봤다.

 

구글링 했을때 가장 먼저 나오는 로그 관련 라이브러리로 log4j가 있었다.

(지금은 log4j2가 가장 최신버전이라고 한다.)

다른 라이브러리 사용시 나는 보통 프로젝트내 "lib" 폴더 생성후, 해당 폴더에 *.jar 파일을 넣는 방식으로 임포트해 사용했었다.

 

그런데 log4j2 라이브러리는 그런식으로 사용하니 eclipse내 실행시 문제는 없었지만, executable jar를 만들어 cmd창에서 돌릴 경우 문제가 발생했다.

WTF...

해당 오류를 찾아보니

ref) https://stackoverflow.com/questions/48033792/log4j2-error-statuslogger-unrecognized-conversion-specifier

 

log4j2 ERROR StatusLogger Unrecognized conversion specifier

I have log4j2 in my project when I run main method in intellij Idea ,it correct to print log. when i use maven-shade-plugin package project to jar file, and run jar as standalone application it s...

stackoverflow.com

이런식으로 maven pom.xml 파일의 설정을 건드리라는 답변이 돌아왔다.

(난 단순히 클래스만 조지는 executable jar를 만들고 싶을뿐인데... 메이븐은 쓰고 싶지 않다구...)

 

하지만 여러 방법을 시도해 봤지만 결국 제대로 돌아가는건 없었다.

그래서 생각을 바꿔 maven 으로 executable jar를 만들 수 있으면 되지 않을까? 하는 결론에 도달했다.

 

https://jhleeeme.github.io/create-jar-with-maven-in-vscode/

 

Maven으로 jar파일 생성 & 실행 | 내가 다시 보려고 만든 블로그

OS는 Ubuntu18.04vscode상에서 진행한다.시나리오 Maven Project 생성 & Run jar파일 생성 & Run1. Maven Project 생성 & Runtest라는 폴더 안에 Maven Project를 생성하고, 돌려보았다. grou...

jhleeeme.github.io

해당 블로그에 보면 maven으로 만든 jar 파일의 경우, manifest 가 mainClass를 찾지 못해 나는 에러가 나타난다고 한다.

<build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>3.2.0</version>
        <configuration>
          <archive>
          	<manifest>
          		<mainClass>{메인클래스명!}</mainClass>
          	</manifest>
          </archive>
        </configuration>
      </plugin>
    </plugins>
  </build>

해당 구문을 maven pom.xml 파일에 미리 추가 해준다.

 

물론 당연히 pom.xml에는 log4j2 에 관련된 라이브러리도 dependency로 추가 해야한다.

log4j2 document 중

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>hoonziTest</groupId>
  <artifactId>sample</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>sample</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
	    <groupId>org.apache.logging.log4j</groupId>
	    <artifactId>log4j-api</artifactId>
	    <version>2.14.1</version>
	  </dependency>
	  <dependency>
	    <groupId>org.apache.logging.log4j</groupId>
	    <artifactId>log4j-core</artifactId>
	    <version>2.14.1</version>
	  </dependency>
  </dependencies>
  
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>3.2.0</version>
        <configuration>
          <archive>
            <!-- <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile> -->
          	<manifest>
          		<mainClass>hoonziTest.sample.App</mainClass>
          	</manifest>
          </archive>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

내 pom.xml 의 설정은 이러하다.

 

log4j2의 경우 maven pom.xml 파일 처럼 log4j2.xml을 통해

console에 쓸지, file에 쓸지 형식은 어떻게 할지에 등에 대한 사항을 세부적으로 설정할 수 있다.

 

maven project내 log4j2.xml의 위치는 src/main/resources에 놓으면 된다.

log4j2.xml 의 위치

내 경우에는 가장 중요한게 각 쓰레드별로 작업한 뒤 정상적으로 종료되었는지 여부를 "파일"에 적는게 중요했다

<?xml version="1.0" encoding="UTF-8"?>
 
<configuration status="debug">
    <Appenders>
        <!-- 콘솔 -->
        <Console name="console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd hh:mm:ss} %5p [%c] %m%n"/>
        </Console>
        <!-- 파일 -->
        <File name="file" fileName="{폴더위치}\sample.log" append="false">
         	<PatternLayout pattern="%d %5p [%c] %m%n"/>
        </File>
    </Appenders>
    
    <loggers>
        <root level="info" additivity="true">
            <AppenderRef ref="console"/>
            <AppenderRef ref="file" />
        </root>
    </loggers>
 
</configuration>

자 main class와 thread class를 구성해보자

  1. main class에서는 thread class를 구성하고 start와 join을 통해 모든 thread가 완료된 후 종료되게 설정, logger를 만들고 각 thread로 전달
  2. thread class는 작업 완료 후, 넘겨받은 logger instance로 logging

main class

package hoonziTest.sample;

import java.util.ArrayList;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class App 
{
//logger instance 생성
	static Logger logger = LogManager.getLogger(App.class);
    public static void main( String[] args )
    {
    	logger.info("program start"); //프로그램의 시작을 logging
        List<Thread> thread_list = new ArrayList<Thread>();
    	for(int i = 0; i < 10; i++) { //thread 생성부
    		threadTest tt = new threadTest(i, logger); // main에서 설정한 logger를 전달
    		thread_list.add(new Thread(tt));
    	}
    	
    	try {
	    	for(Thread t : thread_list) {
	    		t.start(); //thread 실행 명령
	    	}
	    	
	    	for(Thread t : thread_list) {
	    		t.join(); 
	    	}
    	}catch(InterruptedException e) {
    		logger.debug("Interrupted error occured");
    	}
        logger.info("program end");
    }
}

 

thread class

package hoonziTest.sample;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class threadTest implements Runnable{
	
	private int numbers = -1;
	private Logger logger = null;
	
	public threadTest(int numbers, Logger logger) {
		this.numbers = numbers;
		this.logger = logger;
	}
	
	public void run() {
		// TODO Auto-generated method stub
		System.out.println("this number = "+this.numbers);
		logger.info(this.numbers+" work complete"); // 넘겨받은 logger를 통해 logging
		
	}
	
}

 

위 코드를 통해 이제 진짜 executable jar를 만들어보자.

export -> executable jar

cmd -> java -jar 실행파일.jar 

잘 실행된걸 볼수있다.

실제로 log4j2.xml에 설정한 file에 잘 써져있는지 확인해보자

 

동작이 제대로 된걸 확인할 수 있다.

이제 쓰레드 별로 파일 생성&파일관리 할 필요 없이 하나의 파일에 작성되게끔 할 수 있게 되었다.

휴~

 

2021-10-18 추가 내용

 

회사 컴퓨터에서 돌렸을때, maven 설정이 엉킨건지 resources 폴더에 넣은 log4j2.xml을 인식하지 못하는 문제가 있었다.

eclipse 내 실행시에는 sample.log에 정상적으로 작성됐지만, executable jar 파일로 만들고 cmd에서 실행했을땐 파일에 로그가 기록이 안되는 문제가 있었는데

 

public App() {
	System.setProperty("log4j.configurationFile", "resources/log4j2.xml");
	logger = LogManager.getLogger(App.class);
}

이런식으로 생성자와 함께 log4j2.xml의 위치를 property로 등록하고 문제가 해결되었다.

 

 

참고)

https://logging.apache.org/log4j/2.x/index.html

https://tlatmsrud.tistory.com/32

https://www.egovframe.go.kr/wiki/doku.php?id=egovframework:rte3:fdl:logging:log4j_2:설정_파일을_사용하는_방법

반응형

댓글