[ 소개 ]
- Apache Tomcat 의 내부 로그는 JULI 를 사용한다. JULI 는 Apache Commons Logging 소스를 포크한 후 java.util.logging 프레임워크를 사용하도록 하드코딩된 패키지 이다.
- 이를통해 Tomcat 의 내부 로그와 WEB_Application 로그가 독립적으로 유지되며, WEB_Application 이 Apache Common Logging 을 사용하는 경우에도 충돌을 방지할 수 있다.
- Tomcat 의 내부 로그 대신 다른 로그 프레임워크를 대체하여 사용하려면, java.util.logging 을 사용하는 Application 에서 로그를 리디렉션 하는 방법에 대한 대체 로그 프레임워크 지침을 따라야 한다.
- 이때 대체 로그 프레임워크는 동일한 이름의 Logger 가 서로 다른 Class Loader 에 존재할 수 있는 환경에서도 작동할 수 있어야 한다는걸 유의해야 한다.
- Apache Tomcat 에서 실행되는 WEB_Application 은 다음을 수행할 수 있다.
1) 원하는 로그 프레임워크 사용
2) java.util.logging System 로그 API 사용
3) Java Servlet 스펙의 jakarta.servlet.ServletContext.log(..) 로그 API 사용
- 서로다른 WEB_Application 에서 사용하는 로그 프레임워크는 각각 독립적이며, 자세한 내용은 Class Loader 문서를 참고하고, java.util.logging 은 예외사항이다.
- (Tomcat_설치경로/lib 와 같이 톰캣 내부에서) 로그 라이브러리가 직간접적으로 사용되는경우 System Class Loader 에 의해서 로드되므로 해당 로그 라이브러리는 WEB_Application 간에 공유된다.
1. Java 로그 API - java.util.logging
- Apache Tomcat 은 JULI 로 불리는 java.util.logging API 의 몇가지 핵심 요소에 대한 자체 구현체을 가지고 있다.
- JULI 의 핵심 구성요소는 커스텀 LogManager 구현체이고, Tomcat 에서 실행되는 다양한 WEB_Application (과 각각의 Class Loader) 를 인식하며, 이를 통해 개별적인 로그 구성을 지원한다.
- 또한 WEB_Application 이 메모리에서 (unloaded)언로드 될 때 Tomcat 에 알림을 보내 해당 클래스에 대한 레퍼런스를 클리어해서 메모리 누수를 방지한다.
- 해당 JULI 의 java.util.logging 구현체는 Java 를 시작할 때 특정 System Properties 를 제공함으로써 활성화 되는데, (bin/catalina.sh 와 같은) Tomcat 시작 스크립트는 이러한 설정을 알아서 수행하지만 (jsvc 나 IDE 같이) 다른 도구를 사용하여 Tomcat 을 실행하는 경우 직접 처리를 해야 한다.
- java.util.logging 에 대한 자세한 내용은 JDK 설명서나 Javadoc 페이지에서 확인할 수 있다.
- Tomat JULI 에 대한 자세한 내용은 아래에서 확인할 수 있다.
2. Servlet 로그 API
- Tomcat 내부 로그 모듈에서 jakarta.servlet.ServletContext.log(...) 를 호출하여 로그 메시지를 작성하며, 아래와 같은 카테고리 이름을 통해 로그메시지가 작성된다.
org.apache.catalina.core.ContainerBase.[${engine}].[${host}].[${context}]
- 톰캣 로그 설정을 통해 로그가 생성이 되며, WEB_Application 에서는 (Overrite) 재정의 할 수 없다.
- Servlet 로그 API 는 요즘 Java 에서 제공하는 java.util.logging API 보다 이전 버전이고, 많은 옵션을 제공하지 않는다. (예: 로그레벨을 제어할 수 없음)
- 하지만 Apache Tomcat 구현체에서 ServletContext.log(String), GenericServlet.log(String) 을 호출하면 INFO 레벨로 로그가 생성되고, ServletContext.log(String, throwable), GenericServlet.log(String, Throwable) 을 호출하면 SEVERE 레벨로 로그가 생성된다.
1004lucifer
3. Console
- Tomcat 을 Unix 계열 운영체제에서 실행할 때 콘솔 출력은 일반적으로 catalina.out 이라는 파일로 리디렉션 되는데, 이 파일 이름은 환경변수를 사용하여 변경할 수 있다. (bin/catalina.sh 스크립트 참조)
- System.err 또는 System.out 에 작성된 내용은 모두 해당 파일에 기록되며 여기에는 아래 사항도 포함이 된다.
1) java.lang.ThreadGroup.uncaughtException(..) 에 의해 출력된 미처리 예외
2) (kill -3 과 같이) 시스템 신호를 통해 요청된 Thread Dump
- Windows 에서 Service 로 실행하는 경우 콘솔 출력도 캡쳐되어 리디렉션 되지만 파일 이름은 다르게 지정된다.
- Apache Tomcat 의 기본 로그 구성은 콘솔과 로그파일에 동일한 메시지를 기록하며, 개발할 때는 유용하지만 운영(Prod) 환경에서는 불필요하다.
- 여전히 System.out / System.err 를 사용하는 구식 애플리케이션은 Context 에 swallowOutput 속성을 설정하여 이를 (트릭)처리할 수 있는데, 속성을 true 로 설정하면 Request 처리중 System.out/err 호출이 인터셉트되어 jakarta.servlet.ServletContext.log(...) 호출을 사용하여 출력이 로그 서브시스템에 전달된다.
- swallowOutput 기능은 실제로는 트릭이며 다음과 같은 제한이 있다.
1) Request 사이클 중에만 작동하며, System.out/err 에 대한 직접적인 호출에서만 동작한다.
2) Application 에서 생성된 다른 Thread 에서는 동작하지 않을 수 있다.
3) 시스템 스트림에 직접 쓰는 로그 프레임워크는 미리 기동되어 리디렉션이 수행되기 전에 스트림에 직접 레퍼런스를 얻을 수 있기 때문에 인터셉터 할수가 없다.
4. Access 로그
- Access Log 는 Valve 로 구현된 다른 기능이며, 자체 로직을 사용하여 로그파일을 기록한다.
- Access Log 의 필수요건은 낮은 (부하)오버헤드로 대량의 연속된 데이터 스트림을 처리하는 것으로 자체 디버그 메시지는 Apache Commons Logging 만 사용을 한다.
- 이 구현방식은 추가적인 오버헤드와 잠재적인 복잡한 구성을 피할 수 있고, 다양한 포멧을 포함하고 있다. Valve 문서 참고.
[ ★ 작성자 내용 추가 ★ ]
- Apache 또는 NginX 의 Access 로그와 같은거라고 생각하면 된다.
[ java.util.logging 사용 (기본값) ]
- JDK 에서 제공하는 java.util.logging 의 기본 구현체는 VM별로 로깅이 이루어지고 WEB_Application 별로는 로깅을 할 수 없는 제한사항이 있어서 별로 유용하지 않다.
- 따라서 Tomcat 은 이러한 단점을 해결하기 위해 기본 LogManager 구현체를 컨테이너 친화적인 JULI 라는 구현체로 대체한다.
- JULI 는 프로그래밍 방식과 properties 파일을 사용하여 표준 JDK java.util.logging 과 동일한 구성 매커니즘을 지원한다.
- 주요 차이점은 (쉽게 재배포 친화적으로 WebApp 구성이 가능하도록) ClassLoader 별 properties 파일을 구성가능하고, properties 파일이 핸들러와 로거 할당에 더 많은 자유를 허용하는 확장된 구조를 지원한다.
- JULI 는 기본적으로 활성화 되어있으며, 일반적인 글로벌 java.util.logging 구성과 ClassLoader 별로 구성을 지원한다.
- 즉 다음과 같은 계층에서 로그를 구성할 수 있다.
1) Global - 일반적으로 ${catalina.base}/conf/logging.properties 파일에서 수행되며, 이 파일은 시작스크립트에 의해 java.util.logging.config.file 시스템 프로퍼티 속성에 의해 지정된다. 이 파일을 읽을 수 없거나 구성되지 않은경우 기본값은 JRE 의 ${java.home}/lib/logging.properties 파일을 사용한다.
2) WEB_Application - /WEB-INF/classes/logging.properties 파일 사용
- JRE의 기본 logging.properties 는 로깅을 System.err 로 라우팅하는 ConsoleHandler 를 지정하며, Tomcat 의 기본 conf/logging.properties 에는 (로그)파일에 기록하는 여러 AsyncFileHandler 도 추가한다.
- Handler 의 로그 Level 기본값은 INFO 이고 SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST (or ALL) 을 사용하여 설정할 수 있으며, 특정 Package 에서 로그를 수집하고 Level 을 지정할 수 있다.
- Tomcat 내부 로그중 특정 Package 에 대해서 디버그 로깅을 사용하려면 적절한 Logger 와 적절한 Handler 를 모두 FINEST 또는 ALL 레벨로 설정해야 한다. (아래 예 참고)
org.apache.catalina.session.level=ALL
java.util.logging.ConsoleHandler.level=ALL
- 디버그 로깅을 활성화 할 때는 디버그 로깅이 대량의 정보를 생성할 수 있으므로 가능한 가장 좁은 범위에서 활성화 하는것이 좋다.
- JULI 에서 사용하는 구성은 일반 java.util.logging 에서 지원하는 구성과 동일하지만 몇가지 확장기능을 사용하여 로거와 핸들러를 보다 유연하게 구성할 수 있다.
[ 주요 차이점 ]
1) 핸들러 이름에 접두사 추가
- 동일한 클래스의 핸들러를 여러개 인스턴스화 할 수 있도록 핸들러 이름에 접두사를 추가할 수 있으며, 접두사는 숫자로 시작하고, 마침표(.) 로 끝나는 문자열 이어야 한다.
- 예: 22foobar. 는 유효한 접두사 이다.
2) 시스템 속성 대체
- {systemPropertyName} 과 같은 시스템 속성이 표함된 속성 값은 해당 시스템 속성으로 대체된다.
3) 클래스 로더 속성 대체
- org.apache.juli.WebappProperties 인터페이스를 구현하는 클래스 로더 (Tomcat 의 웹 애플리케이션 클래스로더)를 사용하는 경우, 다음과 같이 속서이 대체된다.
* ${classloader.webappName} → 웹 애플리케이션 이름
* ${classloader.hostName} → 호스트 이름
* ${classloader.serviceName} → 서비스 이름
4) 부모 Logger 위임 기본값
- 기본적으로 로거는 핸들러가 연결되어 있는 경우 부모 로거에게 위임하지 않니는다.
- 이를 변경하려면 로거별로 boolean 값을 받는 loggerName.useParentHandlers 속성을 사용한다.
5) Root Logger 핸들러 정의
- 루트 로거는 .handlers 속성을 사용하여 핸들러 집합을 정의할 수 있다.
6) 로그 파일 유지 기간
- 기본적으로 로그 파일은 파일시스템에 90일간 유지된다.
- 핸들러별로 handlerName.maxDays 속성을 사용하여 이 값을 변경할 수 있다.
- 만약 설정된 값이 0 이거나 (-)음수인경우 영구적으로 유지되며, (+)양수인경우 지정된 최대 일수만큼 유지가 된다.
[ 추가 구성 클래스 ]
- Java 에서 제공하는 기본 클래스와 함께 사용할 수 있는 몇가지 추가 구현클래스가 있는데 주요 클래스는 다음과 같다.
1) org.apache.juli.FileHandler
- 로그 버퍼링을 지원하며 (기본값: 비활성), 핸들러의 bufferSize 속성을 설정하여 사용가능
* 값이 0 이면 시스템 기본 버퍼링 (일반적으로 8K) 사용
* 값이 0보다 작은경우 로그를 기록할 때마다 강제로 작성(flush) 됨
* 값이 0보다 크면 지정된 크기의 BufferedOutputStream 이 사용되며, 시스템 기본 버퍼링도 적용된다.
2) org.apache.juli.AsyncFileHandler
- FileHandler 의 하위 클래스 이며, 로그 메시지를 큐에 추가한뒤 비동기적으로 로그파일에 작성한다.
- 추가 동작은 일부 system properties (링크) 를 설정하여 구성할 수 있다.
[ 예제 logging.properties 파일 ]
- 아래는 $CATALINA_BASE/conf 에 위치한 logging.properties 파일 예제
handlers = 1catalina.org.apache.juli.AsyncFileHandler, \
2localhost.org.apache.juli.AsyncFileHandler, \
3manager.org.apache.juli.AsyncFileHandler, \
java.util.logging.ConsoleHandler
.handlers = 1catalina.org.apache.juli.AsyncFileHandler, java.util.logging.ConsoleHandler
############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################
1catalina.org.apache.juli.AsyncFileHandler.level = ALL
1catalina.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
1catalina.org.apache.juli.AsyncFileHandler.prefix = catalina.
1catalina.org.apache.juli.AsyncFileHandler.maxDays = 90
1catalina.org.apache.juli.AsyncFileHandler.encoding = UTF-8
2localhost.org.apache.juli.AsyncFileHandler.level = ALL
2localhost.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
2localhost.org.apache.juli.AsyncFileHandler.prefix = localhost.
2localhost.org.apache.juli.AsyncFileHandler.maxDays = 90
2localhost.org.apache.juli.AsyncFileHandler.encoding = UTF-8
3manager.org.apache.juli.AsyncFileHandler.level = ALL
3manager.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
3manager.org.apache.juli.AsyncFileHandler.prefix = manager.
3manager.org.apache.juli.AsyncFileHandler.bufferSize = 16384
3manager.org.apache.juli.AsyncFileHandler.maxDays = 90
3manager.org.apache.juli.AsyncFileHandler.encoding = UTF-8
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.OneLineFormatter
java.util.logging.ConsoleHandler.encoding = UTF-8
############################################################
# Facility specific properties.
# Provides extra control for each logger.
############################################################
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].handlers = \
2localhost.org.apache.juli.AsyncFileHandler
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].handlers = \
3manager.org.apache.juli.AsyncFileHandler
# For example, set the org.apache.catalina.util.LifecycleBase logger to log
# each component that extends LifecycleBase changing state:
#org.apache.catalina.util.LifecycleBase.level = FINE
- 아래 Servlet WEB_Application 예제의 logging.properties 파일은 WEB_Application 내부의 /WEB-INF/classes 안에 위치한다.
handlers = org.apache.juli.AsyncFileHandler, java.util.logging.ConsoleHandler
############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################
org.apache.juli.AsyncFileHandler.level = ALL
org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
org.apache.juli.AsyncFileHandler.prefix = ${classloader.webappName}.
org.apache.juli.AsyncFileHandler.encoding = UTF-8
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.OneLineFormatter
java.util.logging.ConsoleHandler.encoding = UTF-8
1. 레퍼런스 문서
자세한 내용은 다음 리소스 참고
- Apache Tomcat Javadoc for the org.apache.juli package.
- Oracle java 11 Javadoc for the java.util.logging package.
2. (운영서버) 프로덕션을 위한 고려사항
- 아래 사항을 참고할 것을 권장함
1) ConsoleHandler 제거
- (.handlers 설정 덕분에) 기본적으로 로그는 AsyncFileHandler 와 ConsoleHandler 둘다 전송된다.
- ConsoleHandler 의 출력은 일반적으로 catalina.out 같은 파일에 캡쳐가 된다.
- 따라서 동일한 메시지가 두번 기록되어 중복이 발생할 수 있다.
2) 사용하지 않는 Application 에 대한 AsyncFileHandler 제거
- 예를들어 host-manager 에 대한 AsyncFileHandler 를 제거할 수 있다.
3) Access Log 구성 고려
댓글
댓글 쓰기