1. Logging
시스템을 작동할 때 시스템 작동 상태의 기록과 보존, 이용자의 습성 조사 및 시스템 동작의 분석 등을 하기 위해 작동 중의 각종 정보를 기록해둘 필요가 있다. 이 기록을 만드는 것을 로깅이라 한다. 즉, 로그 시스템의 사용에 관계된 일련의 사건(이벤트)을 시간의 경과에 따라 기록하는 것이다.
- 프로그램 개발 중 또는 완료 후에 발생할 수 있는 오류에 대해 디버깅하거나, 운영 중인 프로그램 상태를 모니터링 하기 위해 필요한 정보를 기록하는 것
- 버그 발생 시 실제 운영중인 애플리케이션에서 어떤 일이 일어났는지 파악하기 어렵기 때문에 로그를 통해서 API 호출 흐름을 파악하고 에러 발생 지점을 찾아내 버그 픽스를 진행할 수 있다.
System.out.println()을 사용하면 동기화로 인한 오버헤드가 발생하여 성능 저하가 발생할 수 있다. 운영상에서는 절대 사용해서는 안 된다.
Java Logging Framework
- java.util.logging
- Apache Commons logging(ACL)
- Log4J
- Logback
- Slf4J(Simple Logging Facade For Java)
- 보통 Logback과 Log4J2가 가장 많이 사용된다고 한다.
SLF4J
- SLF4J는 사용자가 원하는 로깅 프레임워크를 쉽게 사용할 수 있도록 로그를 사용하는 방법을 추상화한 프레임워크이다.
- 개발자는 SLF4J API를 통해서 로그 구현체가 변경되는 것에 신경쓰지 않고 동일한 방법으로 로그를 찍을 수 있다.
- 기존에 ACL이 런타임에 클래스 로더에 의존하여 로그 구현체를 선택하던 것에 반해 SLF4J는 클래스 로더에 의존하지 않고 컴파일 타임에 하나의 특정 로깅 프레임워크만 사용하도록 하드 와이어링 된다.
출처: SLF4J
Log Level
trace
debug
info
warn
error
- 필요에 따라 로깅 레벨을 조정하여 원하는 만큼 로그를 남길 수 있다.
- 개발시에는
trace, debug로 시스템이 우리가 원하는대로 동작하는지 찍어볼 수 있을 것이다. 반대로 운영시에는 로깅 레벨을 warn으로 설정하여 중대한 정보만을 기록하도록 조정할 수 있을 것이다.Logger
Logger는 LoggerFactory를 통해서 만들 수 있다. 이 때, 메서드 단위에서 만들지 않고 클래스 레벨에서 생성한다.
Logger는 이름 기반으로 생성되며 일반적으로 패키지를 포함한 클래스명으로 주게 된다.
클래스명.class를 LoggerFactory.getLogger()에 인수로 전달하면 패키지명이 포함된 클래스명이 로거의 이름이 된다.
private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
- 인스턴스마다 개별 로거를 가지는 것이 아닌 해당 클래스 인스턴스가 모두가 로거를 공유하기 위해
static으로 선언한다.
- 로거는 한 번 만들어지면 객체가 만들어지고 소멸시까지 절대 바뀌어서는 안 된다.
- 로거의 사용 방법은 다음과 같이
로거.로깅레벨("내용")으로 사용한다.
logger.info("Created new order. orderId is {}", order.getId());
- 인수로 전달하는 문자열에 중괄호({})를 사용하면 뒤에 들어오는 인수의 순서에 따라 자동으로 해당 자리의 문자열로 치환된다.
2. Logback
- 로그백 설정을 통해서 우리는 출력되는 로그의 형태를 지정하거나 출력되길 원하는 로깅 레벨을 지정할 수 있다.
- 로그백은 다양한 설정 파일을 읽을 수 있고 각 파일의 우선수위는 다음과 같다.
logback-test.xml
logback.groovy
logback.xml
- 기본 설정 전략을 따름
- 이러한 로그백 설정 파일은
resources 디렉터리 아래에 배치한다.
- 기본적인 설정은 로그백 공식문서 Chapter 3: Configuration 에서 찾을 수 있다.
src/main/resources/logback.xml
<configuration>
<import class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"/>
<import class="ch.qos.logback.core.ConsoleAppender"/>
<appender name="STDOUT" class="ConsoleAppender">
<encoder class="PatternLayoutEncoder">
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp- %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
- 해당 설정에서 모든 로깅 레벨은
debug로 설정되어 있다.
STDOUT이라는 이름을 가진 appender를 통해 로그 출력을 지정하고 있다.
appender는 어디에 어떤 포맷으로 로그를 남길 수 있는 지 설정한다.
- 로그는 콘솔에 남길 수도 있고 파일, DB 또는 Kibana를 이용할 수도 있다.
pattern을 이용해 어떠한 포맷으로 로그를 남길지 지정하고 있다.
- logger
- 로거 생성시 전달한 이름을 이용하면 특정 패키지 하위의 로깅 레벨에 대해서도 지정할 수 있다.
- 다음은
com.example.hello 패키지 하위의 로거 레벨을 debug로 지정, additivity 설정을 통해 중복 출력이 되지 않도록 지정, STDOUT이라는 appender로 출력되도록 지정한 것이다.
<logger name="com.example.hello" level="debug" additivity="false">
<appender-ref ref="STDOUT" />
</logger>
property
- 동일한 패턴을 변수처럼 여러 블럭에서 사용하기 위해
property 블럭을 사용할 수도 있다.
- 마치
key=value처럼 name과 그에 해당하는 value를 지정할 수 있다.
<property name="LOG_PATTERN" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
- 이렇게 정의한
property는 여러 appender에서 출력 패턴을 지정하기 위해서 사용할 수 있다.
<appender name="STDOUT" class="ConsoleAppender">
<encoder class="PatternLayoutEncoder">
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
- 이러한 패턴의 정의 방법은 Logback 공식문서 Chapter 6: Layouts 에서 찾아볼 수 있다.
- 로깅 프레임워크 내부에서 만들어진 로깅 이벤트가 패턴을 정의하며 지정한 conversion word에 의해서 문자열로 변환되어 우리가 원하는 출력문이 만들어진다.
appender
- 우리는 다양한
appender를 추가하여 로그의 출력을 콘솔, 파일, DB 다양한 지정이 가능하다.
- 다음은 로그의 파일 출력을 위한
appender이다.
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/testFile.log</file>
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} -%kvp- %msg%n</pattern>
</encoder>
</appender>
- 출처: Chapter 4: Appenders
- 이를 추가하여 애플리케이션을 실행시키면
logs/testFile.log 로그 파일이 생성된 것을 확인할 수가 있다.
- 계속해서 애플리케이션을 실행시키면 동일한 로그 파일에 로그가 추가되는 것을 확인할 수 있는데 이는
appender에 <append>false</append>를 추가하거나 타임스탬프를 파일 이름 뒤에 추가하는 방식으로 해결할 수 있다.
- 이 외에 파일로 로그를 남기는 경우
RollingFileAppender를 많이 사용한다.
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logFile.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- daily rollover -->
<fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- keep 30 days' worth of history capped at 3GB total size -->
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} -%kvp- %msg%n</pattern>
</encoder>
</appender>
RollingFileAppender와 TimeBasedRollingPolicy를 이용하면 매일 새로운 로그 파일이 생성되어 해당 파일에 로그가 기록된다.
- 날짜가 변경되는 시점에 새로운
logFile.log가 생성되고 기존의 파일은 logFile.2023-06-23.log라는 이름으로 변경된다.
3. 스프링 부트
로깅
- 스프링 부트는 우리가 지금까지
logback.xml로 설정한 다양한 로깅 설정을 편리하게 지원한다.
- 기본적으로 다음과 같은 로깅 포맷이 지정되어 있다.
2023-06-23T18:15:22.625+09:00 INFO 119272 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2023-06-23T18:15:22.631+09:00 INFO 119272 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2023-06-23T18:15:22.632+09:00 INFO 119272 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.8]
2023-06-23T18:15:22.695+09:00 INFO 119272 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2023-06-23T18:15:22.695+09:00 INFO 119272 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization
- 또한
application.properties를 통해서 편리하게 로그 레벨을 변경할 수 있다.
logging:
level:
root: info
스프링 부트 스타터
- 예전에는 하나의 애플리케이션을 구성하기 위해 다양한 라이브러리에 대한 의존성을 일일히 추가하여야 했다.
- 스프링 부트가 제공하는
starter 의존성은 관련된 의존성을 모아둔 세트이다. 이를 통해 우리는 굉장히 빠르게 하나의 프로젝트를 구성하여 개발에만 집중할 수 있다.
SpringBootApplication
@SpringBootApplication 하위에 컴포넌트 스캔을 비롯한 설정 정보가 포함되어 있다.
- 이를 통해 기본적인 것들이 설정되고 특별히 설정이 필요한 부분에 대해서만 신경써서 진행할 수 있다.
실행 가능 Jar
- 스프링 부트는 특별한 구조의 jar를 제공함으로써 별도의 라이브러리를 설정해주지 않고도 jar 하나만으로 애플리케이션을 기동시킬 수 있는 편리함을 제공한다.