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 하나만으로 애플리케이션을 기동시킬 수 있는 편리함을 제공한다.