'logback'에 해당하는 글 5건

Exclude log4j2

Daily/Prog 2021. 12. 17. 20:59

log4j

 

ECR 에서 log4j2 사용중인 이미지들 깜빡하고 있었는데 AWS 에서 친절히 메일이 왔다.

 

You are receiving this communication because your account has container images stored in Amazon Elastic Container Registry (ECR). While not all container images are impacted by this CVE, your images may be affected if they are using Java with Apache Log4j in certain configurations. Amazon Inspector scans container images stored in Amazon ECR for software vulnerabilities to generate Package Vulnerability findings and is able to detect this issue in container images. You can turn on enhanced scanning from ECR [2] or enable Inspector from Inspector console to discover this issue in your images[3].

 

계정에 Amazon Elastic Container Registry(ECR)에 저장된 컨테이너 이미지가 있기 때문에 이 통신을 수신하게 되었습니다. 모든 컨테이너 이미지가 이 CVE의 영향을 받는 것은 아니지만 특정 구성에서 Apache Log4j와 함께 Java를 사용하는 경우 이미지가 영향을 받을 수 있습니다. Amazon Inspector는 Amazon ECR에 저장된 컨테이너 이미지에서 소프트웨어 취약성을 스캔하여 패키지 취약성 결과를 생성하고 컨테이너 이미지에서 이 문제를 감지할 수 있습니다. ECR[2]에서 향상된 스캔을 켜거나 Inspector 콘솔에서 Inspector를 활성화하여 이미지에서 이 문제를 발견할 수 있습니다[3].

 

log4j2 취약점이 추가로 계속 발생하고 있다. 2.15 에서 2.16 버전으로 업데이트...

확인하기도 귀찮다. 자꾸 신경쓰이게 하지 말고, logback 쓰고 log4j2 는 그냥 꼬죠.

 

// build.gradle
configurations {
    implementation.exclude group: "org.apache.logging.log4j"
}

 

spring 종속성을 사용한다면,,

 

// gradle
ext['log4j2.version'] = '2.17.2'

 


WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

,

Logback 구성 파일을 사용하면 코드를 다시 컴파일 할 필요없이 logging 동작을 재정의 할 수도 있다.

구성 파일에 로그를 어떤 형태로 어디에 남길 것인지에 대한 계획도 명시할 수 있으며, 프로그래밍 방식이나 XML, Groovy 형식으로도 작성할 수 있다.

Logback 웹사이트에서 기존 log4j 사용자를 위한 PropertiesTranslator 도 제공한다. (log4j.properties -> logback.xml)



Logback 의 구성 파일 검색 순서


  1. classpath 에서 logback-test.xml 파일 검색.
  2. 없으면 logback.groovy 파일 검색.
  3. 없으면 logback.xml 파일 검색.
  4. 없으면 BasicConfigurator 를 사용하여 logback 을 가장 기본적으로 구성하고, console 로 logging 출력.


src/main/resources 디렉토리에 logback.xml, src/test/resources 디렉토리에 logback-test.xml 파일을 두어 운영/개발에 대한 구성을 분리하여 사용할 수 있다.

구성 파일이 없어서 BasicConfigurator 를 사용하게 되면, root logger 는 DEBUG 레벨이 할당되고 아래의 형식을 가진다.


%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n


// console :

16:06:09.031 [main] INFO  chapters.configuration.MyApp1 - Entering application.

16:06:09.046 [main] DEBUG chapters.configuration.Foo - Did it again!

16:06:09.046 [main] INFO  chapters.configuration.MyApp1 - Exiting application.



logback.xml 구성


아래 코드는 BasicConfigurator 로 기본 설정된 것을 logback-test.xml 이나 logback.xml 파일로 자체 구성한 것이다.


<configuration>
 
    <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="STDOUT" />
    </root>
</configuration>
cs


이것이 구성 파일을 찾지 못했을 때 적용되는 기본 구성이다.

위 기본 파일 처럼, 구성 파일의 기본적인 구조는 <configuration> 안에 <appender>, <logger>, <root> 순으로 나열하는 것이다.


<appender> 는 name, class 속성을 필수로 지정해야 하며, 선택적으로 하나 이상의 <layout>, <encoder>, <filter> 를 포함할 수 있다.

logback 0.9.19 부터 FileAppender와 하위 클래스는 <encoder> 를 필요로 하고 더 이상 <layout> 을 사용하지 않는다.

<encoder> 는 class 속성을 지정하지 않으면 PatternLayoutEncoder 클래스가 적용된다.

<logger> 는 root 로거와 레벨이나 appender 가 다를 경우 패키지명과 함께 설정한다.

<root> 는 appender 를 참조하여 root 로거에 첨부한다.


아래는 로그를 console 과 file 로 전달하는, 보편적(?)인 logback.xml 스트립트이다.


<?xml version="1.0" encoding="UTF-8"?>
 
<!-- 구성 파일을 수정하였을 때, logback-classic 이 변경 사항을 자동으로 반영하게 한다. -->
<!-- scanPeriod 값의 단위는 milliseconds (default), seconds, minutes, hours 단위로 입력할 수 있다. -->
<!-- StatusPrinter 클래스의 print() 메소드를 호출한 것과 동일하게 logback 의 내부 상태 출력 -->
<configuration scan="true" scanPeriod="30 seconds" debug="true">
 
    <!-- property 요소로 변수를 사용할 수 있다. (아래의 logDir 이나 HOSTNAME 등) -->
    <property resource="resource.properties"/>
 
    <!-- ConsoleAppender 는 거의 이 형식을 벗어나지 않는다. pattern 만 적절히 수정한다. -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern> %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
 
    <!-- RollingFileAppender 는 FileAppender 를 확장한 것으로 파일을 특정 조건에 맞게 로테이션 시키는데 유용하다. -->
    <!-- 아래의 로테이션 정책은 30일이 넘거나 총 파일 크기가 3GB 를 넘기면 오래된 순으로 삭제된다. -->
    <appender name="FILEOUT" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logDir}/logFile.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
            <totalSizeCap>3GB</totalSizeCap>
        </rollingPolicy>
 
        <encoder>
            <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>
 
    <!-- 아래 logger 들로 특정 라이브러리들에 대한 logger 레벨과 appender 를 지정할 수 있다. -->
    <!-- 여기 지정된 logger 레벨은 root 레벨보다 우선한다. -->
 
    <!-- Hibernate Loggers -->
    <logger name="org.hibernate" level="ERROR">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILEOUT"/>
    </logger>
 
    <!-- springframework -->
    <logger name="org.springframework" level="ERROR">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILEOUT"/>
    </logger>
 
    <!-- ibatis and mybatis spring -->
    <logger name="org.apache.ibatis" level="INFO">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILEOUT"/>
    </logger>
    <logger name="org.mybatis.spring" level="INFO">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILEOUT"/>
    </logger>
 
    <!-- if, then, else 구문을 사용하여 서버 환경에 따른 스크립트를 하나의 파일에 동시에 설정할 수 있다. -->
    <!-- 아래는 root 레벨만 조정하였지만, appender-ref 를 변경할 수도 있다. -->
    <if condition='property("HOSTNAME").contains("prod")'>
        <then>
            <root level="info">
                <appender-ref ref="STDOUT"/>
                <appender-ref ref="FILEOUT"/>
            </root>
        </then>
        <else>
            <root level="debug">
                <appender-ref ref="STDOUT"/>
                <appender-ref ref="FILEOUT"/>
            </root>
        </else>
    </if>
</configuration>
cs


구성 파일을 구문 분석하는 동안 경고 또는 오류가 발생하면 Logback 은 내부 상태 데이터를 자동으로 콘솔에 출력한다.

구성 파일의 configuration 요소에 debug="true" 를 설정하면, 오류가 없는 경우에도 구성 파일에 상태 데이터를 출력할 수 있다.

이것은 코드에 StatusPrinter.print() 메소드를 사용한 것과 같은 결과로 나타난다.

또한 설정 파일의 statusListener 요소에 OnConsoleStatusListener 클래스를 설정하는 것과도 같다.





WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

,

Logback 은 Logger, Appender, Layout 의 세 가지 기본 클래스를 기반으로 한다.

이를 사용하여 개발자는 메시지 유형 및 수준에 따라 메시지를 기록하고 런타임에 이러한 메시지의 형식과 보고 위치를 설정할 수 있다.

Logger 클래스는 logback-classic 모듈에 속하고, Appender 및 Layout 인터페이스는 logback-core 에 속하기 때문에, logback-core 에는 logger 의 개념이 없다.



Logger Class


Logger 의 레벨인 TRACE, DEBUG, INFO, WARN, ERROR 는 ch.qos.logback.classic.Level 클래스에 정의된다.

Logger 레벨을 지정하지 않으면 상위 클래스에서 상속받게 되고, default 레벨은 DEBUG 이다.

레벨은 아래와 같으며, 지정된 레벨 이하의 메소드 호출은 기록되지 않는다.


TRACE < DEBUG < INFO < WARN < ERROR



Appenders Class


Appender 는 로그 출력 대상(목적지) 을 설정할 수 있다.

현재 Appender 대상은 console, files, Syslog, TCP Sockets, JMS, DB, JMS 및 원격 UNIX Syslog 데몬이 될 수 있다.



Layouts Class


Appender 와 관련하여 출력 형식을 사용자 정의 하는 것이 Layout 이다.

PatternLayout 을 사용하면 C 언어 printf 함수와 비슷한 변환 패턴에 따라 출력 형식을 지정할 수 있다.

예를 들어 "%-4relative [%thread] %-5level %logger{32} - %msg%n" 같은 패턴을 가진 PatternLayout 의 결과는 다음과 같다.


176  [main] DEBUG manual.architecture.HelloWorld2 - Hello world.
cs



Logging 파라미터


logger.debug("The new entry is "+entry+".");
logger.debug("The new entry is {}.", entry);
cs


위 debug 메소드의 결과는 같지만, 두 번째 debug 메소드가 첫 번째 debug 메소드 형식보다 성능면에서 30배 이상 좋다. 

파라미터 수 만큼 해당 자리에 {} 를 나열하고 이어서 콤마로 구분하여 파라미터를 나열하면 된다.


logger.debug("The new entry is {}. It replaces {}.", entry, oldEntry);
cs


배열도 사용 가능하다.


Object[] paramArray = {newVal, below, above};
logger.debug("Value {} was inserted between {} and {}.", paramArray);
cs



내부 동작


사용자가 info() 메소드를 호출할 때 Logback 처리 과정은 다음과 같다.


  1. 필터 체인
    로깅 요청시 TurboFilter 가 존재할 경우 호출되어, 특정 이벤트를 필터링 하여 다음의 응답과 함께 단계 이동한다.
    FilterReply.DENY 이면 Logging 요청 삭제, NEUTRAL 인 경우 2 단계 진행, ACCEPT 인 경우 3 단계 진행.

  2. 기본 선택 규칙 적용
    요청 레벨과 logger 레벨을 비교하여, 사용할 수 없으면 요청을 삭제하고 그렇지 않으면 다음 단계로 진행.

  3. LoggingEvent 객체 생성
    logger, level, message 같은 요청의 모든 파라미터를 포함하는 ch.qos.logback.classic.LoggingEvent 객체를 생성.

  4. Appenders 호출
    LoggingEvent 객체를 만든 후 AppenderBase 의 doAppend() 메소드를 호출하여, 사용자 정의 필터가 있는 경우 해당 필터를 호출.

  5. 출력 형식화 (Layout)
    appender 를 통해, LoggingEvent 인스턴스를 형식화하고 결과를 문자열로 반환.

  6. LoggingEvent 목적지로 보내기
    로깅 이벤트가 완전히 형식화된 후, 각 appender 에 의해 목적지로 전송.




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

,

log4j 를 만든 Ceki Gülcü 는 log4j 를 포함한 다른 logging 시스템들 보다 더 가볍고 빠르게 동작하는 Logback 을 개발하였다.

log4j 와 logback 은 개념적으로 매우 유사하고, log4j 를 사용했던 사용자라면 logback 역시 쉽게 다가갈 수 있다.

System.out.println 와 비교하자면 빠르고, 레벨, 환경 등에 따라 출력 여부를 표시할 수 있다는 점?



Logback 모듈


Logback 은 logback-core, logback-classic, logback-access 세 가지 모듈로 나뉜다.


  • logback-classic 은 log4j 의 향상된 버전에 해당하며, SLF4J API를 기본적으로 구현하여 log4j 이나 JUL 과 같은 다른 로깅 시스템과 쉽게 전환이 가능하다.
  • logback-access 는 Servlet 컨테이너와 통합되어 HTTP 액세스 로그 기능을 제공한다.
  • logback-core 은 위 두 모듈의 기반이 되며, logback 은 이 모듈의 일부인 Joran 이라는 구성 라이브러리에 의존한다. 



Logback 특징


  • logback-classic 모듈의 Logger 클래스가 SLF4J API 를 기본적으로 구현하므로 SLF4J 로거를 호출 할 때 오버 헤드가 발생하지 않는다. 
  • logback-classic 은 설정 파일이 수정되면 자동으로 reload 할 수 있다. 
  • FileAppender 를 비롯한 하위 클래스는 I/O 오류를 정상적으로 복구할 수 있다.
  • 아카이브 된 로그 파일을 자동으로 비동기 압축하며, maxHistory 프로퍼티를 설정하여 오래된 아카이브 로그 파일을 자동으로 삭제할 수 있다.
  • Prudent 모드로 여러 FileAppender 인스턴스로부터 동일한 로그 파일에 안전하게 기록할 수 있다.
  • development, testing, production 같은 다른 환경에 대하여 구성 파일을 중복 구성할 필요없이, 단일 구성 파일에서 조건부 처리를 사용할 수 있다.
    (조건부 처리시 Janino 라이브러리 필요)
  • MDCFilter 를 사용하여 특정 사용자에 대한 Logging Level 지정이 가능하다.
  • SiftingAppender 는 사용자 세션에 따라 로그 파일을 분리할 수 있다.
  • logback 이 exception 을 출력할 때, stack trace 에서 exception 에 개입하는 클래스, 패키지, 패키지 버전을 알 수 있다.
  • logback-Access 모듈은 Jetty, Tomcat 같은 Servlet 컨테이너와 통합되어 풍부하고 강력한 HTTP 액세스 로그 기능을 제공한다.



Logback 요구사항


logback-classic 모듈을 사용하려면, classpath 에 logback-classic.jar 외에도 slf4j-api.jar 와 logback-core.jar 파일이 있어야 한다.

maven 이나 gradle 같은 빌드 툴을 사용할 때는, logback-classic 모듈만 가져오면 slf4j-api.jar, logback-core.jar 파일도 딸려온다.


gradle 예)


dependencies {
    // logback-classic (slf4j-api, logback-core 포함)
    compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3'
}
cs



Logback 구현


package chapters.introduction;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
public class HelloWorld1 {
 
    public static void main(String[] args) {
 
        Logger logger = LoggerFactory.getLogger("chapters.introduction.HelloWorld1");
        logger.debug("Hello world.");  // 20:49:07.962 [main] DEBUG chapters.introduction.HelloWorld1 - Hello world.
 
    }
}
cs


"Hello world." 라는 debug 레벨의 로그를 출력하는 예제이다.

SLF4J API 에 정의된 Logger 와 LoggerFactory 를 import 하고, Logger 인스턴스를 생성해서 사용했다.

이 예제처럼, Logging 이 필요한 대부분의 경우 logback 클래스를 참조하지 않고 SLF4J API 만으로 구현해야 한다.


만약 logback 수명주기 동안의 로그가 필요하다면 StatusManager 라는 컴포넌트를 이용할 수 있으며, Logback 관련 문제 진단에 매우 유용할 수 있다.

StatusPrinter 클래스의 print() 메소드를 호출하여 logback 의 내부 상태를 출력할 수 있다.


import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.core.util.StatusPrinter;
 
...
 
// print internal state
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
StatusPrinter.print(lc);
 
/*
12:49:22.203 [main] DEBUG chapters.introduction.HelloWorld2 - Hello world.
12:49:22,076 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
12:49:22,078 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
12:49:22,093 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.xml]
12:49:22,093 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Setting up default configuration.
*/
cs


위 로그에서 logback 이 구성 파일을 찾지 못하여 기본 정책(ConsoleAppender) 으로 구성되었음을 알 수 있다.

Appender 는 로그를 남길 목적지로, console, files, Syslog, TCP Sockets, JMS 등 다양하게 설정할 수 있다.

에러가 발생하면 logback은 자동으로 내부 상태를 콘솔에 출력한다.



Logback 사용 정리


  1. logback.xml 이나 logback.groovy 같은 구성 설정.
  2. Logging 이 필요한 모든 클래스에 org.slf4j.Logger 인스턴스 생성.
  3. debug(), info(), warn() 및 error() 등 적절한 출력 메소드 호출로 구성된 appender 에 Logging.




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

,

개발자들은 어플리케이션의 중심부에 통합 로깅을 구성하여, 개발시 문제점 추적이나 필요한 부분을 모니터링 하려고 할 것이다.

System.out.println() 을 열심히 붙여 넣으면 로깅이 가능하다.

조금더 진보적인 방법으로는 Logging Framework 를 사용하는 방법이 있다.


Logging Framework 의 종류는 매우 많겠지만, 대부분 사용하는 것은 정해져 있는 듯 하다.

JUL, Log4j, Logback 정도...


Spring Framework 은 기본적으로 spring-core 모듈이 JCL(Jakarta Commons Logging) API 에 의존성을 가지고 있다.

commons-logging 의 역할은 지정된 클래스패스에서 다른 Logging Framework 를 찾아 동일한 API 로 로깅을 구현할 수 있게 한다.

예를 들면 Log4j 같은 Logging Framework 와 연동시켜 logging 을 가능하게 한다.

사용 가능한 Logging Framework 가 없다면 JDK 의 JUL(java.util.logging) 을 기본으로 사용한다.


문제는 commons-logging 이 slf4j 에 비해 비효율적이라는데 있다.

slf4j 는 commons-logging 처럼 다른 Logging Framework 를 찾는 작업을 런타임시 하지 않고, 컴파일 시점에 바인딩하기 때문에 더 효율적이다.

이러한 효율성이 얼마나 체감 가능한지는 모르겠지만 아무튼 그러한 이유로 slf4j 를 많이 사용한다고 한다.

Log4j 도 런타임 오버헤드와 코드 사이즈가 증가하는 단점으로 인해, slf4j API 를 구현하는 Logback 을 주로 사용한다.

JUL, JCL, Log4j 등을 호출하는 외부 라이브러리 이용시에는 slf4j 의 extend 라이브러리(jcl-over-slf4j 등) 를 사용하면 slf4j 로 통합된다.


정리하자면, commons-logging 이나 slf4j 같은 Logging API 를 사용하는 이유는,

JUL 이든 Log4j 든 Logback 이든 개발 도중 Logging Framework 가 바껴도 Logging 에 관련된 코드를 수정할 필요가 없도록 하기 위해서이다.

slf4j + logback 인 logging 을 위해 dependancy 에 추가할 것은 세가지 정도가 되겠다.


  1. slf4j Logging API
  2. logback Logging Framework
  3. Binding to slf4j : jul-over-slf4j / jcl-over-slf4j / log4j-over-slf4j / ... 


Gradle 을 예로 들면, 다음과 같은 형태가 되겠다.


dependencies {
    // logback-classic (slf4j-api, logback-core 포함)
    compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3'
    compile group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.25'
    compile group: 'org.slf4j', name: 'log4j-over-slf4j', version: '1.7.25'
}
cs


마지막으로 resources 디렉토리에 적당한 logback.xml 나 logback.groovy 파일을 생성/설정한다. ^^




WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

,