2022. 1. 5. 17:33ㆍ[개발] 지식/Lib
log4j
log4j
는 로그를 다양한 대상으로 출력할 수 있도록 도와주는 오픈소스 라이브러리이다. 다양한 대상이라고 하면 조금 애매한 표현일 수 있는데.. 예를 들면 로컬서버에서 띄우는 로그, 개발서버에서 띄우는 로그, 운영서버에서 띄우는 로그를 분리하는 것을 말한다. 운영서버에서는 로그 용량만 해도 어마어마 하기 때문에 필수적인 로그만 출력해줘야 한다. 반면 개발서버에서는 개발의 용이성 때문에 좀 더 자세한 로그를 출력하기도 한다.
이를 위해서는 우선 로그 레벨의 개념을 이해해야 한다.
DEBUG < INFO < WARN < ERROR < FATAL
오른쪽으로 갈 수록 중요한 것이기 때문에 로그 레벨이 ‘높다’ 라고 표현할 수 있다. 보통 단순히 정보 확인을 위해 출력하는 DEBUG 또는 INFO 레벨의 로그와는 달리 ERROR 레벨의 로그는 원인을 알기 위해 운영 환경에서도 반드시 출력되어야 한다. 그래서 로그 레벨이 높은 단계에 위치해 있는 것이다.
개발자들은 로그의 성격을 고려하여 적합한 로그 레벨에 출력할 수 있다. 예를 들면 예외처리에서 발생하는 에러정보는 ERROR 레벨로 출력한다. 인자로 들어오는 객체 값을 확인하고 싶은데, 굳이 운영서버에서는 확인할 필요가 없다면 가장 낮은 레벨인 DEBUG로 출력하는 식이다.
취약점
그런데 얼마전 log4j
에 대한 취약점이 발견되어 SW업계에 큰 이슈가 된 사건이 있었다. log4j
2버전의 JNDI 인젝션 취약점이 발견된 것인데, 이를 악용하여 서버에서 악성코드를 실행시키게 할 수 있다. 헤더에 포함한 url을 서버가 읽어서 접속을 하도록 유도하는데, 목적지에 접속하면 파일을 다운로드하게 한다. 그런데 그 파일에는 악성코드가 심어져 있어 이를 자연스럽게 실행하게 된다. 이를 통해 예상되는 피해는 아래와 같다고 한다.
- 대상 컴퓨터(서버)의 모든 권한 취득 가능
- 비밀번호 없이 내부망 접근 가능
- 악성 프로그램(랜섬웨어 등) 실행 가능
- 기업의 중요한 자료 삭제 등
상황
취약점 보완 대상이 되는 것은 log4j
2버전 중 2.16 이하 버전이다. 사실 1.x 버전은 대상이 아니나, 다른 취약점들이 있기 때문에 2버전 이상으로 올리는 것이 권장된다.
본 글을 작성하기 전에는 취약점이 개선된 버전이 2.15 버전으로 알고 있었는데, 다시 찾아보니 2.17 버전 이상으로 바꿔야 한다고 한다. 새로운 취약점이 발견되어 2.16 버전까지 뚫리는 것이 확인되었기 때문이다. 2.16 혹은 2.17 버전 업그레이드 방법은 크게 다르지 않을 것이므로 일단 2.16 버전 업그레이드 방법에 대해 작성하였다. 본 글의 핵심은 라이브러리 구성이 상이한 1.x 버전에서 2.x 버전으로 올리는 것이기 때문이다.
내가 버전 업그레이드를 진행하고자하는 환경에서는 Srping-4.14 에서 log4j-1.2.16 버전을 사용중이다. 일단 업그레이드 할 버전은 아래 링크에서 다운받을 수 있다. (apache-log4j-2.16.0-bin.zip)
그런데 현재버전인 log4j-1.2.16과 업그레이드 할 버전인 log4j-2.16.0은 라이브러리 구성요소에 꽤 큰 차이가 있다. 다운받은 zip 파일을 열어보면 아래와 같은데, 전부 필요한 것은 아니고 몇 개만 골라서 사용하면 된다. 자세한 내용은 다음 섹션을 참고한다.
조치
1. 라이브러리 추가/삭제
라이브러리 jar 파일을 교체한다. 기존에 사용하던 1.2.16 버전 jar 는 빼고, 2.16.0 버전 jar 는 추가한다. slf4j
를 사용하는 경우 slf4j-api 외 slf4j
관련 라이브러리는 제거하고, log4j2
와의 연결을 위해 log4j-slf4j-impl 라이브러리를 추가한다.
- log4j-1.2.16.jar → 삭제
- slf4j-log4j12-1.7.2.jar → 삭제 (slf4j를 사용하는 경우)
- slf4j-api-1.7.2.jar → 유지 (slf4j를 사용하는 경우)
- log4j-core-2.16.0.jar → 추가
- log4j-api-2.16.0.jar → 추가
- log4j-web-2.16.0.jar → 추가
- log4j-slf4j-impl-2.16.0.jar → 추가 (slf4j를 사용하는 경우)
프로젝트 환경에 따라 일부 차이가 있을 수 있다. 중요한 것은 Spring 환경에서 추가해야 할 파일은 log4j-core-2.16.0.jar , log4j-api-2.16.0.jar , log4j-web-2.16.0.jar 3개라는 점이다. 만약 slf4j
를 사용한다면 1.x 버전과 slf4j
를 바인딩 해주는 slf4j-log4j12-1.7.2.jar 는 삭제하고 2.x 버전과 바인딩 해주는 log4j-slf4j-impl-2.16.0.jar를 추가한다. 호출할 메서드가 정의되어 있는 slf4j-api-1.7.2.jar 는 필요하므로 유지한다.
※ slf4j 버전이 1.8 미만이면 log4j-slf4j-impl-2.16.0.jar 를 사용하고, 1.8이상이라면 log4j-slf4j18-impl-2.16.0.jar 를 사용한다.
2. log4j2.xml 경로 명시
log4j-2.x 버전에서는 web.xml
에서 경로나 파일명을 명시하지 않는 이상, 기본적으로 classpath: 에서 log4j2-test.xml
또는 log4j2.xml
파일을 찾게 된다. src/main/resources 경로는 src/main/java 와 마찬가지로 WEB-INF/classes 경로로 복사되므로 src/main/resources 에 넣거나 프로젝트 별로 설정된 classpath: 에 넣도록 한다.
하지만 나 같은 경우, web.xml
에 별도로 경로와 파일명을 명시하였다. 기존에는 아래와 같이 설정되어 있었는데:
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath:/mypath/log4j.xml</param-value>
</context-param>
이를 아래와 같이 변경하였다.
<context-param>
<param-name>log4jConfiguration</param-name>
<param-value>classpath:/mypath/log4j2.xml</param-value>
</context-param>
param-name
이 log4jConfigLocation
에서 log4jConfiguration
로 변경되었음에 유의하자.
3. Listener 변경
listener도 같이 변경해주어야 한다. 일단 기존 버전은 아래와 같다.
<listener>
<listener-class>
org.springframework.web.util.Log4jConfigListener
</listener-class>
</listener>
이를 아래와 같이 변경한다.
<listener>
<listener-class>
org.apache.logging.log4j.web.Log4jServletContextListener
</listener-class>
</listener>
4. log4j2.xml 작성
기존 log4j-1.x 버전에서 사용하던 log4j.xml
과 문법에서 꽤 차이가 있으므로 아예 새로 작성하는 것이 나을 수도 있다. 기본적인 템플릿은 아래와 같다.
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<!-- Appender, Layout 설정 -->
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout/>
</Console>
<File name="file" fileName="./logs/file/sample.log" append="false">
<PatternLayout pattern="%d %5p [%c] %m%n"/>
</File>
</Appenders>
<!-- Logger 설정 -->
<Loggers>
<Logger name="testLogger" level="DEBUG" additivity="false">
<AppenderRef ref="console"/>
<AppenderRef ref="file"/>
</Logger>
<Rootlevel="ERROR">
<AppenderRef ref="console"/>
</Root>
</Loggers>
</Configuration>
Appenders
하위에 Console
이나 File
은 로그를 어떤 방식으로 어디에 어떻게 출력할 것인지를 정의한다. Logger
에서 로그 레벨을 지정하고, Console
이나 File
을 Logger
하위 태그에 넣음으로써 출력방식이 결정된다. 내용이 좀 있으므로 자세한 문법과 구조는 공식 사이트 매뉴얼을 확인하시길 바란다.
5. 호출 (logging)
아래는 1.x 버전을 사용했을 때의 기존 호출 코드이다. slf4j
를 사용한다면 인터페이스가 동일하므로 변경없이 사용할 수 있다.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static Logger testLogger = LoggerFactory.getLogger("testLogger");
prvate static void write(String s){
testLogger.info(s);
}
slf4j 미사용하는 경우
slf4j
를 미사용하는 경우, log4j2
자체 클래스를 아래와 같이 사용한다.
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
// logger name이 현재 클래스 경로인 것을 찾는다.
private static Logger logger = LogManager.getLogger(this.getClass());
// logger name이 "testLogger" 인 것을 찾는다.
private static Logger testLogger = LogManager.getLogger("testLogger");
prvate static void write(String s){
logger.info(s);
testLogger.info(s);
}
Reference
egovframework:rte3:fdl:logging:log4j_2:설정_파일을_사용하는_방법 [eGovFrame]
'[개발] 지식 > Lib' 카테고리의 다른 글
*WHATWG-FETCH (0) | 2020.05.14 |
---|---|
toastr.js 로 Toast Message 구현하기 (0) | 2020.01.20 |