====== Logback ======
* http://logback.qos.ch/
* [[java:slf4j|Slf4j]]와 함께 사용하는 것이 좋다.
* [[log4J]]를 계승하면서 훨씬 강력한 Java Logging Framework
* [[https://github.com/grgrzybek/tomcat-slf4j-logback|grgrzybek/tomcat-slf4j-logback]]
* [[http://hwellmann.blogspot.kr/2012/11/logging-with-slf4j-and-logback-in.html|Logging with slf4j-and-logback]] : 다른 코드 없이 기존 패키지들로 구성하기
* [[http://lilithapp.com/|Lilith]] Logback GUI Viewer
* [[http://logback.qos.ch/beagle/|Logback Beagle]] Eclipse Logback Viewer
===== 종류별 정리 =====
* logback-core : 로그백의 핵심
* logback-classic : log4j 처럼 애플리케이션의 일반 로깅 처리에 사용
* logback-access : Tomcat/Jetty 등에 설치되어 웹 요청을 로깅할 수 있다.
* [[http://audit.qos.ch/|logback-audit]] : 장시간 추적이 필요한 비지니스 애플리케이션의 이벤트 로깅
===== LOGBACK-1591 취약점 이슈 =====
* 가급적 logback **1.2.9** 이상 버전을 사용할 것.
* [[http://mailman.qos.ch/pipermail/announce/2021/000165.html|[qos.ch-announce] Secuirty Fix - logback 1.2.9 and 1.3.0-alpha11]]
* [[https://jira.qos.ch/browse/LOGBACK-1591|[LOGBACK-1591] Possibility of vulnerability - QOS.ch JIRA]]
* 발생 가능성은 희박한 편임.
* [[https://github.com/cn-panda/logbackRceDemo|cn-panda/logbackRceDemo: The project is a simple vulnerability Demo environment written by SpringBoot. Here, I deliberately wrote a vulnerability environment where there are arbitrary file uploads, and then use the `scan` attribute in the loghack configuration file to cooperate with the logback vulnerability to implement RCE.]]
* write access to logback.xml
* use of versions < 1.2.8
* reloading of poisoned configuration data, which implies application restart or scan="true" set prior to attack
===== 설정 =====
* [[http://logback.qos.ch/documentation.html|LogBack Documentation]]
* [[http://logback.qos.ch/manual/groovy.html|logback.groovy]]를 사용하려면 [[http://mvnrepository.com/artifact/org.codehaus.groovy/groovy-all|groovy]]의존성이 필요하다.
* [[https://wiki.base22.com/display/btg/How+to+setup+SLF4J+and+LOGBack+in+a+web+app+-+fast|Slf4j와 LogBack 빠르게 최소 설정하기]]
* ''logback.xml''
▶ %-5level %d{HH:mm:ss.SSS} [%thread] %class{36}.%method:%line - %msg%n
* ''logback.groovy''
def HOSTNAME = hostname // scope 룰 때문에 nested block에서는 hostname 변수 사용불가
appender('console', ConsoleAppender) {
encoder(PatternLayoutEncoder) {
pattern = "▶ %-5level %d{HH:mm:ss.SSS} [${HOSTNAME}] [%thread] %logger{36} - %msg%n"
}
}
root(INFO, ['console'])
==== 기본 Daily Rolling File Appener 예 ====
appender('daily', RollingFileAppender) {
file = "/logs/daily.log"
append = true
rollingPolicy(TimeBasedRollingPolicy) {
fileNamePattern = "/logs/daily.log.%d{yyyy-MM-dd}.gz" // 자동 gz 압축
maxHistory = 10 // 10일간만 보관
}
encoder(PatternLayoutEncoder) {
pattern = '%-5level %d{yyyy-MM-dd HH:mm:ss} [%thread] %logger{36} - %msg%n'
}
// 특정 레벨 이상만 로깅
filter(ch.qos.logback.classic.filter.ThresholdFilter) {
level = INFO // INFO 이상 레벨만 로깅
}
// 마커가 필요할 때
filter(EvaluatorFilter) {
evaluator(ch.qos.logback.classic.boolex.OnMarkerEvaluator) {
marker = '마커이름'
marker = '또다른마커이름'
}
onMismatch = DENY
onMatch = NEUTRAL
}
}
==== 기본 Fixed Window Rolling File Appender ====
appender('fixedWindowRollingFile', RollingFileAppender) {
file = '/logs/fixedWindowRollingFile.log'
append = true
encoder(PatternLayoutEncoder) {
pattern = '%-5level %d{yyyy-MM-dd HH:mm:ss} [%thread] %logger{36} - %msg%n'
}
rollingPolicy(ch.qos.logback.core.rolling.FixedWindowRollingPolicy) {
fileNamePattern = '/logs/fixedWindowRollingFile.log.%i'
minIndex = 1 // xx.log.1 ~ xx.log.5
maxIndex = 5
}
triggeringPolicy(ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy) {
maxFileSize = '100MB' // 파일 크기 100MB될때마다 롤링
}
}
===== Multi line to Single line =====
Grep 시에 여러줄로 된 로그를 올바로 grep하기 힘들 경우 multi line 을 single line으로 합쳐서 로깅하면 좀 더 편하게 로깅 가능하다.
[[http://co-de-generation.blogspot.kr/2012/08/java-logging-in-single-line-for-grep.html|CoDeGeneration: Java: Logging in single line for grep]]
Layout 을 다음과 같이 지정하면 새줄기호(%%\n%%)가 %%\n%% 문자 그 자체로 표시된다. 예외 Stacktrace까지 모두 한 줄로 합친다.
XML 설정의 경우에는
%replace(%msg){"\n","\\n"} %replace(%xException){"\n", "\\n"}%nopex%n
-- 공백으로 변환됨.
%replace(%msg){'[\r\n]', ''}
groovy 설정으로 할 경우에는
%replace(%msg){"\n","\\n"} %replace(%xException){"\\n", "\\\\n"}%nopex%n
===== Spring Framework =====
* [[https://github.com/qos-ch/logback-extensions/wiki/Spring|Logback Spring Extension]]
* ''LogbackConfigListener'' 설정시 리스너는 선언 순서에 따라 실행되므로 로깅 관련 리스너를 최상단으로 올려서 로깅 정책이 처음 부터 적용될 수 있도록 처리할 것.
* [[https://github.com/qos-ch/logback-extensions/blob/master/spring/src/main/java/ch/qos/logback/ext/spring/LogbackConfigurer.java|LogbackConfigurer]] 웹과 무관하게 logback 설정 파일 위치지정. Spring의 [[http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/util/ResourceUtils.html|ResourceUtils]]를 사용하므로 Spring 방식의 resource location 지정 방법 사용 가능.
===== Appenders =====
* [[https://github.com/Moocar/logback-gelf|logback-gelf]] 로그 수집/뷰어인 [[:logging:graylog2]] 연동 appender
* [[https://github.com/cosmocode/logback-xmpp|Logback XMPP]] XMPP, Jabber Appender
* [[https://github.com/danielwegener/logback-kafka-appender|logback-kafka-appender]]
* [[https://github.com/ptgoetz/logback-kafka|logback-kafka]]
===== Marker =====
* [[http://slf4j.org/api/org/slf4j/Marker.html|org.slf4j.Marker]]는 태그와 비슷한 것이다.
* 어펜더는 특정 마크가 된 로그에 대해 필터링을 하여 처리할 수 있다.
* 마커 A와 B가 있을 때,
* ''A.add(B)'' 는 ''A extends B''와 유사하다. 즉, A는 B를 상속하므로, B를 처리하는 어펜더는 A도 함께 처리할 수 있다.
===== Shutdown Hook =====
* logback이 정상 종료되게 하려면 JVM ShutdownHook을 걸어주는게 좋다.
* 특히 [[java:logback:asyncappender|Logback AsyncAppender]] 사용시에 모든 로그가 남은 뒤에 종료되게 하고자 한다면 더욱 중요하다.
* [[http://logback.qos.ch/manual/configuration.html#stopContext|Logback Stop Context]]
* ''ch.qos.logback.ext.spring.web.LogbackConfigListener'' 혹은 ''LogbackConfigServlet'' 사용시 서블릿 컨텍스트 종료시 자동 종료되도록 등록 되는 듯 보임.
* Code로
// assume SLF4J is bound to logback-classic in the current environment
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.stop();
* XML 설정
....
===== Filter =====
* [[http://logback.qos.ch/manual/filters.html|Logback Filters]]
==== GEventEvaluator ====
* ''EventEvaluatorFilter''에서 [[:groovy|Groovy]] 로 필터 조건을 줄수 있는 Evaluator.
* ''ILoggingEvent'' 객체가 ''e'' 혹은 ''event'' 변수로 전달되며 ''TRACE'',''DEBUG'',...''ERROR''도 변수로 전달된다.
* ''level'' 변수는 ''toInt()''로 숫자로 변환하여 비교할 수 있다. ''e.level.toInt() >= WARN.toInt()'' 형태.
* 설정 파일 파싱할 때 미리 컴파일 되므로 매번 컴파일 된다고 걱정하지 않아도 된다.
e.level.toInt() >= WARN.toInt() &&
!(e.mdc?.get("req.userAgent") =~ /Googlebot|msnbot|Yahoo/ )
DENY
NEUTRAL
%-4relative [%thread] %-5level %logger - %msg%n
===== Stacktrace 에서 불필요한 스택 감추기 =====
* 단점, ''String.contains()'' 로 검사한다.
* Test 용 설정에 적용하면 좋음.