로깅이란?
프로그램 동작시 발생하는 모든 일을 기록하는 행위이다. 모든 일이란 서비스 동작 상태와 장애로 나눌 수 있는데 서비스 동작 상태에는 시스템 로딩, HTTP 통신, 트렌젝션, DB 요청, 의도를 가진 Exception 등이 있고 장애(exception, error)로는 I/O Exception, NullPointException, 의도하지 않은 Exception 등이 있다.
로깅은 언제 할까?
프로젝트 성격에 맞게, 팀에 맞게 진행한다. 따라서 로깅 시점은 때에 따라 다르다.
로깅을 어떻게 해?
- System.out.println("로깅로깅")
- System.err.println("에러로깅")
- 로깅 프레임워크를 사용
로깅 프레임워크
- SLF4J
- Logback
- Lof4j
- nlog 등
로깅 vs System.out.println()
- 출력 형식을 지정할 수 있음
- 로그 레벨에 따라 남기고 싶은 로그를 별도로 지정할 수 있음
- 콘솔뿐만 아니라 파일이나, 네트워크 등 로그를 별도에 위치에 남길 수 있다.
- log 성능이 System.out 보다도 좋다고 한다.
로그 레벨 관련
Logback 은 5단계의 로그 레벨을 가진다.
심각도 수준은 Error > Warn > Info > Debug > Trace이다.
레벨 | 설명 |
Fatal | 매우 심각한 에러, 프로그램이 종료되는경우가 많아 거의 사용되지 않음(logback 에는 fatal 설정이 아예 존재하지 않고, error 에 맵핑됩니다.) |
Error | 의도하지 않은 에러가 발생한 경우프로그램이 종료되지 않음프로그램 내에서 개발자가 의도하지 않은 예외를 나타날 때 사용 |
Warn | 에러가 될 수 있는 잠재적 가능성이 있는 경우Warn 로그가 발생했을 시, 알람을 통해 개발자가 크리티컬한 에러를 맞닥뜨리기 전에 확인할 수 있는 역할을 겸함 |
Info | 명확한 의도가 있는 정보성 로그요구사항에 따라 시스템 동작을 보여줄 떄 |
Debug | Info 레벨보다 더 자세한 정보가 필요한 경우주로 Develop 환경에서 사용 |
Trace | Debug 레벨보다 더 자세한 내용을 포함Dev 환경에서 버그를 해결하기위해 사용최종 프로덕션이나 커밋에 포함되면 안된다고 합니다. |
회원가입 시, DB에 동일한 email을 가진 회원이 있을 때, DuplicationException을 던진다면 이 이벤트의 로그는 어떤 레벨을 적용할까? → Info
로깅 vs 디버깅
- 프로그래밍의 절반은 디버깅이다.
- 디버깅할 수 없는 상황에서는 로깅이 최선의 선택
- 디버깅을 쓸 수 있다면 디버깅을 최대한 활용
SLF4J
Simple Logging Facade for Java
다양한 로깅 프레임 워크(java.util.logging, logback 및 log4j)에 대한 추상화(인터페이스) 역할을 하는 라이브러리입니다.
인터페이스이기 때문에 단독으로 사용이 불가능합니다.
최종 사용자가 배포 시 원하는 구현체(logback, log4j 등)를 선택하여 사용 가능하다.
SLF4J 동작 과정
개발할 때, SLF4J API를 사용하여 로깅 코드를 작성
배포할 때, 바인딩된 Logging Framework가 실제 로깅 코드를 수행
Bridge
다른 로깅(SLF4J 이외의)의 API로의 Logger 호출을 SLF4J 인터페이스로 연결(redirect)하여 SLF4J API가 대신 처리할 수 있도록 하는 라이브러리이다.
이전의 레거시 로깅 프레임워크를 위한 라이브러리이다.
Bridge는 여러 개를 사용해도 상관없지만, 사용 시 주의점은 Bridge와 Binder에 같은 종류의 프레임워크를 사용하면 안 된다.
SLF4J API(인터페이스)
로깅에 대한 추상 레이어(인터페이스)를 제공한다. 즉 로깅 동작에 대한 역할을 수행할 추상 메서드를 제공한다. 앞서 말했듯이 추상 클래스이기 때문에 이 라이브러리만 단독적으로 쓰일 수 없다. 사용 시 주의점은 반드시 하나의 API에 하나의 Binding을 둬야 한다.
SLF4J Binding(.jar)
SLF4J 인터페이스를 로깅 구현체(Logging Framework)와 연결하는 어댑터 역할을 하는 라이브러리이다. SLF4J API를 구현한 클래스에서 Binding으로 연결될 Logger의 API를 호출해요. SLF4J API의 주의점과 같이 하나에 API에 하나의 Binding을 둬야 한다.
SLF4J 실습
build.gradle에 아래와 같은 의존성을 추가한다.
// slf4j & logback
implementation 'org.slf4j:jcl-over-slf4j'
implementation 'ch.qos.logback:logback-classic'
@slf4j 어노테이션을 사용해도 된다.
slf4j는 기본적으로 debug가 default 값으로 잡혀있어 trace 로그를 띄어주기 위해서는 별도의 설정이 필요하다고 합니다.
logging:
level:
org:
springframework: trace
application.yml 파일에 위의 코드를 추가해주어야 한다.
Logback
logback은 로깅 프레임워크 중 하나로 SLF4J의 구현체 라이브러리이다.
Log4J를 토대로 만든 프레임워크로 현재 Spring framework에서도 Slf4j 와 logback을 기본 라이브러리로 채택하고 있다.
Logback 구조
logback-core
logback-classic, logback-access 두 모듈의 기반 역할을 하는 모듈이며 Appender, Layout 인터페이스가 이 모듈에 속해 있습니다.
logback-classic
logback-core를 가지며 slf4j API를 구현하여, log4j 또는 java.util.logging(JUL)과 같은 다른 로깅 프레임워크 간에 쉽게 전환 가능하도록 하며 Logger 클래스가 이 모듈에 속해 있습니다.
logback-access
Servlet Container와 통합되어 Tomcat 및 jetty 등 Servket 컨테이너에 HTTP Access 로그 기능을 제공합니다. 웹 애플리케이션 레벨이 아닌 컨테이너 레벨에서 설치되어야 합니다.
logback vs logback-spring
스프링 공식문서를 살펴보면 가능하다면 표준보다 "-spring"을 붙여서 사용하는 걸 추천한다고 합니다.
표준 버전 즉, logback.xml을 사용한다면 Spring 이 완벽하게 로그 초기화를 제어하지 못한다고 한다.
아마도, logback.xml 파일은 너무 빨리 로드되기 때문에, 이 파일 안에선 Extensions을 사용할 수 없다고 하는데 이 때문인 것 같습니다.
로그 백 설정 파일은 logback-spring.xml 파일을 src/main/resources/logback-spring.xml 경로에 만들면서 시작합니다.
로그 파일 작성하기
콘솔 로그의 수준을 변경하는 방법은 application.yml과 logback-spring.xml 에서 설정하는 방법이 있다. application.yml 은 설정하는 난이도가 비교적 쉽지만, 실제 제품에 사용하기엔 한계가 있고 세부적인 설정이 불편하기 때문에 logback-spring.xml로 관리하는 편이 더 좋다고 한다.
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 여기애 로그 설정 부분이 들어간다. -->
</configuration>
logback-spring.xml 구성 요소
- appender는 로그가 출력되는 위치를 나타냅니다.
- XXXAppender로 끝나는 클래스들이 존재하며 다양한 로그 출력 위치 및 방법을 제공합니다.
- ConsoleAppender는 콘솔에 System.out 또는 System.err를 이용하여 로그 이벤트를 append 합니다.
- FileAppender는 파일에 로그를 저장한다. 최대 보관 일 수 등을 지정할 수 있다.
- RollingFileAppender : 여러 개의 파일을 롤링, 순회하면서 로그를 찍는다.(FileAppender를 상속받는다. 지정 용량이 넘어간 Log File을 넘버링하여 나누어 저장할 수 있다.)
- SMTPAppender : 로그를 메일로 보낸다.
- DBAppender: DB(데이터베이스)에 로그를 쌓는다.
- layout (encoder)
- logback 버전 0.9.19 이후 에로 로그의 모든 wirte 권한을 제어할 수 있는 encoder 등장하였습니다.
- Encoder는 로그 이벤트를 바이트 배열로 변환하고, 해당 바이트 배열을 OutputStream에 쓰이는 작업을 담당합니다.
- 이전 버전에서 appender는 이벤트 메시지를 문자열로 변환하는데 layout을, write 하는데 java.io.Writer를 사용해 왔습니다.
append name설정은 STDOUT이라는 변수명으로 저장해뒀다고 생각하면 된다.<?xml version="1.0" encoding="UTF-8"?> <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) %cyan(%logger{0}) - %msg%n </pattern> </encoder> </appender> </configuration>
- springProfile
- 스프링 배포 버전 profile에 따라서 로그 설정을 세분화할 수도 있습니다.
이렇게 하면 prod 버전의 배포 환경일 때만, 그 하위 내용들이 적용되며, appender 또한 해당 profile이 아닐 때는 무시됩니다.<?xml version="1.0" encoding="UTF-8"?> <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) %cyan(%logger{0}) - %msg%n </pattern> </encoder> </appender> <property name="LOG_DIR" value="/var/log/was"/> <springProfile name="prod"> <appender class="ch.qos.logback.core.rolling.RollingFileAppender" name="FILE"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) %logger{0} - %msg%n</pattern> </encoder> <file>${LOG_DIR}/app.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>${LOG_DIR}-%d{yyyy-MM-dd}-%i-log.zip</fileNamePattern> <maxFileSize>100MB</maxFileSize> <maxHistory>90</maxHistory> <totalSizeCap>3GB</totalSizeCap> </rollingPolicy> </appender> </springProfile> <root level="info"> <!-- info 레벨 이상에서만 실행한다. --> <springProfile name="dev"> <appender-ref ref="STDOUT"/> </springProfile> <springProfile name="prod"> <appender-ref ref="STDOUT"/> <appender-ref ref="FILE"/> </springProfile> </root> </configuration>
- root 태그를 이용해 만들어둔 appender 들을 조합해서 사용할 수 있습니다. <root level = "off">를 하게 된다면 모든 로거가 무시됩니다.
- <springProfile> 속성을 이용해 배포 버전에 마다 다른 로거 설정을 세분화할 수 있습니다.
- <appneder-ref>를 이용해 만들어둔 appender를 여기서 마치 변수처럼 사용할 수 있습니다.
오류
만약 프로젝트 실행을 했는데 아래와 같은 오류가 발생하면
파일을 저장하는 경로에 value=”/var/log/was”에 value=”./var/log/was”로 수정해주면 정상적으로 작동한다.
Appender 코드
1. ConsoleAppender
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) %cyan(%logger{0}) - %msg%n
</pattern>
</encoder>
</appender>
</configuration>
2. RollingFileAppender
<property name="LOG_DIR" value="/var/log/was"/>
<springProfile name="prod">
<appender class="ch.qos.logback.core.rolling.RollingFileAppender" name="FILE">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) %logger{0} - %msg%n</pattern>
</encoder>
<file>${LOG_DIR}/app.log</file> <!-- 파일을 저장할 경로를 정한다 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch> <!-- 해당 레벨만 기록한다. -->
<onMismatch>DENY</onMismatch> <!-- 다른 수준의 레벨은 기록하지 않는다.(상위 레벨도 기록 안함), 상위 수준의 레벨에 대한 기록을 원하면 ACCEPT 로 하면 기록된다. -->
</filter> <!-- 레벨별 필터링이 필요없을 경우 filter class 관련된 부분을 삭제하면 됨-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIR}-%d{yyyy-MM-dd}-%i-log.zip</fileNamePattern>
<maxFileSize>100MB</maxFileSize> <!-- 한 파일의 최대 용량(분할 용량) -->
<maxHistory>90</maxHistory> <!-- 한 파일의 최대 저장 기한 -->
<totalSizeCap>3GB</totalSizeCap> <!-- 전체 파일의 크기를 제어하며, 전체 크기 제한을 초과하면 가장 오래된파일은 삭제한다. totalSizeCap을 사용하려면 maxHistory가 필수 속성이다. -->
</rollingPolicy>
</appender>
</springProfile>
- maxFileSize: 한 파일의 최대 용량(분할 용량)이다.
- maxHistory: 한 파일의 최대 저장 기한 롤 오버 지정에 따라 maxHistory 기간이 정해진다.
- totalSizeCap: 전체 파일의 크기를 제어하며, 전체 크기 제한을 초과하면 가장 오래된 파일은 삭제한다. totalSizeCap을 사용하려면 maxHistory가 필수 속성이다.
3. SMTPAppender
<appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
<smtpHost>ADDRESS-OF-YOUR-SMTP-HOST</smtpHost>
<to>EMAIL-DESTINATION</to>
<to>ANOTHER_EMAIL_DESTINATION</to> <!-- additional destinations are possible -->
<from>SENDER-EMAIL</from>
<subject>TESTING: %logger{20} - %m</subject>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%date %-5level %logger{35} - %message%n</pattern>
</layout>
</appender>
4. DBAppender
<property resource="application.properties" />
<springProperty name="spring.datasource.driverClassName" source="spring.datasource.driverClassName"/>
<springProperty name="spring.datasource.url" source="spring.datasource.url"/>
<springProperty name="spring.datasource.username" source="spring.datasource.username"/>
<springProperty name="spring.datasource.password" source="spring.datasource.password"/>
<appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
<connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
<driverClass>${spring.datasource.driverClassName}</driverClass>
<url>${spring.datasource.url}</url>
<user>${spring.datasource.username}</user>
<password>${spring.datasource.password}</password>
</connectionSource>
</appender>
DBAppender 사용 시 DB 필요한 Table
Logback에서 DBAppender는 독립적인 형식으로 logging_event, logging_event_property, logging_event_exception 세 개의 데이터 베이스 테이블을 insert 하므로 DBAppender를 사용하기 전에 먼저 테이블을 생성해야 합니다.
DROP TABLE IF EXISTS logging_event_property;
DROP TABLE IF EXISTS logging_event_exception;
DROP TABLE IF EXISTS logging_event;
CREATE TABLE logging_event
(
timestmp BIGINT NOT NULL,
formatted_message TEXT NOT NULL,
logger_name VARCHAR(254) NOT NULL,
level_string VARCHAR(254) NOT NULL,
thread_name VARCHAR(254),
reference_flag SMALLINT,
arg0 VARCHAR(254),
arg1 VARCHAR(254),
arg2 VARCHAR(254),
arg3 VARCHAR(254),
caller_filename VARCHAR(254) NOT NULL,
caller_class VARCHAR(254) NOT NULL,
caller_method VARCHAR(254) NOT NULL,
caller_line CHAR(4) NOT NULL,
event_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY
);
CREATE TABLE logging_event_property
(
event_id BIGINT NOT NULL,
mapped_key VARCHAR(254) NOT NULL,
mapped_value TEXT,
PRIMARY KEY(event_id, mapped_key),
FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
);
CREATE TABLE logging_event_exception
(
event_id BIGINT NOT NULL,
i SMALLINT NOT NULL,
trace_line VARCHAR(254) NOT NULL,
PRIMARY KEY(event_id, i),
FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
);
DBAppender와 테이블을 생성했다면 해당 테이블에 로그가 자동으로 insert 됩니다.
참조
https://tecoble.techcourse.co.kr/post/2021-08-07-logback-tutorial/
https://thalals.tistory.com/373
https://oingdaddy.tistory.com/317
https://reference-m1.tistory.com/349#recentEntries
https://www.youtube.com/watch?v=1MD5xbwznlI
https://www.youtube.com/watch?v=JqZzy7RyudI&t=16s
'개발 > Spring' 카테고리의 다른 글
인텔리제이 디버깅 기능과 사용 방법 (0) | 2022.11.01 |
---|---|
addAttribute VS addFlashAttribute (0) | 2022.07.26 |
HttpServletRequest, 커맨드 객체, @ModelAttribute (0) | 2022.07.19 |