[Spring๐ฑ] ๋ก๊น (Logging) ์๋ฒฝ ๊ฐ์ด๋ ๐
/ 18 min read
Table of Contents
์คํ๋ง ๋ถํธ ๋ก๊น (Spring Boot Logging) ์๋ฒฝ ๊ฐ์ด๋ ๐
์คํ๋ง ๋ถํธ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ก๊น (Logging) ๊ธฐ๋ฅ์ ํตํด ์ ํ๋ฆฌ์ผ์ด์ ๋์์ ์ถ์ ํ๊ณ ๋ฌธ์ ๋ฅผ ์ง๋จํ ์ ์๋ค. ์ด๋ฒ ํฌ์คํ ์์๋ Spring Boot ๋ก๊น ์ ๊ธฐ๋ณธ ๋์ ๋ฐฉ์๊ณผ ์ฃผ์ ์ค์ ๋ฐฉ๋ฒ์ ์ดํด๋ณด๊ณ , ์ค์ ์ ํ๋ฆฌ์ผ์ด์ ์ฝ๋์์ ๋ก๊น ์ ์ด๋ป๊ฒ ํ์ฉํ๋์ง์ ๋ํด ์์ ์ฝ๋๋ฅผ ํตํด ์์ธํ ์์๋ณธ๋ค.
์คํ๋ง ๋ถํธ ๋ก๊น ์ ๊ธฐ๋ณธ ์๋ฆฌ
์คํ๋ง ๋ถํธ ์ ํ๋ฆฌ์ผ์ด์
์ ๊ธฐ๋ณธ์ ์ผ๋ก Spring Framework์์ ์ ๊ณตํ๋ commons-logging
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ค. ํ์ง๋ง ์ค์ ๋ก๊น
๊ตฌํ์ฒด๋ก๋ Logback์ด ๊ธฐ๋ณธ ์ฑํ๋์ด ์๋ค.
- Commons Logging (JCL): ์ถ์ํ ๋ ์ด์ด ์ญํ
- Logback: ์ค์ ๋ก๊ฑฐ(Logger) ๊ตฌํ์ฒด
์ฆ, ์คํ๋ง ๋ถํธ๋ commons-logging
์ผ๋ก๋ถํฐ ๋ก๊น
ํธ์ถ์ ๋ฐ์ผ๋ฉด, ๋ด๋ถ์ ์ผ๋ก Logback์ ํตํด ๋ฉ์์ง๋ฅผ ์ถ๋ ฅํ๋ค.
์ฅ์
- ์ผ๊ด๋ ๋ก๊น
API:
commons-logging
์ ํตํด SLF4J, Log4j2, Logback ๋ฑ ๋ค์ํ ๊ตฌํ์ฒด๋ก ์ฝ๊ฒ ๊ต์ฒด ๊ฐ๋ฅ - ๊ฐ๋จํ ์ค์ :
application.properties
(๋๋.yml
) ํ์ผ์ ํตํด ์์ฝ๊ฒ ๋ก๊น ๋ ๋ฒจ ๋ฑ์ ๋ณ๊ฒฝํ ์ ์์
๋ก๊น ํ๋ ์์ํฌ ๊ตฌ์กฐ ์ดํด
์๋ ๊ทธ๋ฆผ์ ํํ ๋ณผ ์ ์๋ Java ๋ก๊น ๊ตฌ์กฐ์ด๋ค:
[Logger ํธ์ถ๋ถ] -> [commons-logging or SLF4J] -> [์ค์ ๋ก๊น
๊ตฌํ์ฒด(Logback, Log4j2...)]
์คํ๋ง ๋ถํธ์์๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ค์ ํ๋ฆ์ ๋ฐ๋ฅธ๋ค:
[์ ํ๋ฆฌ์ผ์ด์
์ฝ๋] -> [Commons Logging] -> [Logback]
์ํ๋ค๋ฉด Log4j2๋ก ๊ต์ฒดํ ์๋ ์๋ค. SLF4J๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ๋ ๋น์ทํ๋ค.
์คํ๋ง ๋ถํธ ๊ธฐ๋ณธ ๋ก๊น ์ค์
์คํ๋ง ๋ถํธ์ ๊ธฐ๋ณธ ๋ก๊น ์ Logback์ ์ฌ์ฉํ๋ฉฐ, ๋ค์๊ณผ ๊ฐ์ ํน์ง์ ๊ฐ์ง๋ค:
- ๊ธฐ๋ณธ ๋ ๋ฒจ:
INFO
- ๋ก๊ทธ ์ถ๋ ฅ ํฌ๋งท: ์๊ฐ, ์ค๋ ๋, ๋ก๊ฑฐ ์ด๋ฆ, ๋ก๊ทธ ๋ ๋ฒจ, ๋ก๊ทธ ๋ฉ์์ง ํํ๋ก ์ฝ์ ์ถ๋ ฅ
- ์ปฌ๋ฌ ์ง์: ์ฝ์์์ ๋ก๊ทธ ๋ ๋ฒจ๋ณ๋ก ๊ตฌ๋ถ๋๋ ์ปฌ๋ฌ๋ฅผ ์ง์
์คํ๋ง ๋ถํธ๋ spring-boot-starter-logging
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉฐ, ์ด๋ Logback์ ๋ํ ๊ธฐ๋ณธ ์ค์ ํ์ผ(logback.xml
)์ ๋ด์ฅํ๊ณ ์๋ค.
๋ก๊น ๋ ๋ฒจ(Log Levels)
์คํ๋ง ๋ถํธ์์ ์ง์ํ๋ ๋ํ์ ์ธ ๋ก๊น ๋ ๋ฒจ์ ๋ค์๊ณผ ๊ฐ๋ค:
- TRACE: ๊ฐ์ฅ ์์ธํ ๋ก๊ทธ ๋ ๋ฒจ. ๊ฐ๋ฐ ํ๊ฒฝ์์ ์ฃผ๋ก ์ฌ์ฉ
- DEBUG: ๋๋ฒ๊น ์ ์ํ ์ ๋ณด
- INFO: ์ผ๋ฐ ์ ๋ณด์ฑ ๋ฉ์์ง
- WARN: ์ ์ฌ์ ์ธ ๋ฌธ์
- ERROR: ์ค๋ฅ ๋ฐ์ ์ ๋ฉ์์ง
- FATAL (Log4j2 ๋ฑ ์ผ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ฌ์ฉ): ์น๋ช ์ ์ธ ์ค๋ฅ
๊ธฐ๋ณธ๊ฐ์ INFO
์ด๋ฉฐ, ๊ฐ๋ฐ ํ๊ฒฝ์์๋ DEBUG
๋ TRACE
๋ก ์ธ๋ถ ์ ๋ณด ๋ก๊ทธ๋ฅผ ํ์ธํ ์ ์๋ค.
์ค์ ํ์ผ(application.properties) ์์
์๋๋ application.properties
์์ ๋ก๊น
๋ ๋ฒจ์ ์ค์ ํ๋ ์์์ด๋ค:
# ์คํ๋ง ๋ก๊น
๋ ๋ฒจlogging.level.org.springframework=debuglogging.level.com.hippoo=trace
# ์ฝ์ ์ถ๋ ฅ ์, ์ปฌ๋ฌ ํ์ฑํ ์ฌ๋ถspring.output.ansi.enabled=ALWAYS
# ๋ก๊ทธ ํจํด ๋ณ๊ฒฝlogging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
logging.level.<ํจํค์ง๋ช >
: ํน์ ํจํค์ง๋ ํด๋์ค์ ๋ํ ๋ก๊น ๋ ๋ฒจ์ ์ง์ ํ๋ค.spring.output.ansi.enabled
: ์ฝ์ ๋ก๊ทธ์์ ANSI ์ปฌ๋ฌ ์ฝ๋๋ฅผ ํ์ฑํํ๋ค.logging.pattern.console
: ์ฝ์๋ก ์ถ๋ ฅ๋๋ ๋ก๊ทธ์ ํจํด์ ์ ์ํ๋ค.
Logback & Log4j2 ์ค์ ์ปค์คํฐ๋ง์ด์ง
Logback ์ค์
์คํ๋ง ๋ถํธ์์ Logback ์ค์ ์ ์ปค์คํฐ๋ง์ด์งํ๋ ค๋ฉด ํ๋ก์ ํธ ๋ฃจํธ ๊ฒฝ๋ก์ logback-spring.xml
๋๋ logback.xml
ํ์ผ์ ์์ฑํ๋ค.
<configuration> <property name="LOG_PATH" value="logs"/> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{36} - %msg%n</pattern> </encoder> </appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_PATH}/myapp.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_PATH}/myapp-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>10MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <maxHistory>30</maxHistory> </rollingPolicy> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{36} - %msg%n</pattern> </encoder> </appender>
<logger name="com.hippoo" level="DEBUG" additivity="false"> <appender-ref ref="FILE"/> </logger>
<root level="INFO"> <appender-ref ref="CONSOLE"/> <appender-ref ref="FILE"/> </root></configuration>
<logger>
ํ๊ทธ๋ฅผ ํตํด ํน์ ํจํค์ง๋ ํด๋์ค์ ๋ก๊น ๋ ๋ฒจ์ ์ค์ ํ ์ ์๋ค.<root>
ํ๊ทธ๋ ์ ์ฒด ๋ก๊ฑฐ์ ๋ํ ๋ ๋ฒจ์ ์ค์ ํ๋ค.- ํ์ผ ์ฑ๋ ๋(
FILE
)๋ฅผ ์ถ๊ฐํ์ฌ ๋ก๊ทธ๋ฅผ ํ์ผ์๋ ์ถ๋ ฅํ๋๋ก ์ค์ ํ๋ค.
Log4j2 ์ค์
Log4j2๋ฅผ ์ฌ์ฉํ๊ณ ์ถ๋ค๋ฉด **spring-boot-starter-logging
**์ ์ ๊ฑฐํ๊ณ , **spring-boot-starter-log4j2
**๋ฅผ ์ถ๊ฐํ๋ค. ๊ทธ๋ฆฌ๊ณ log4j2-spring.xml
ํ์ผ์ ์์ฑํ์ฌ ์ค์ ํ๋ฉด ๋๋ค.
<Configuration status="WARN"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{36} - %msg%n"/> </Console> <File name="File" fileName="logs/myapp.log"> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{36} - %msg%n"/> <Policies> <TimeBasedTriggeringPolicy /> <SizeBasedTriggeringPolicy size="10MB"/> </Policies> <DefaultRolloverStrategy max="30"/> </File> </Appenders>
<Loggers> <Logger name="com.hippoo" level="debug" additivity="false"> <AppenderRef ref="File"/> </Logger> <Root level="info"> <AppenderRef ref="Console"/> <AppenderRef ref="File"/> </Root> </Loggers></Configuration>
SLF4J์์ Logging ํ์ผ ์ด๋ฆ๊ณผ ๊ฒฝ๋ก ์ค์ ํ๊ธฐ ๐
**SLF4J (Simple Logging Facade for Java)**๋ ์๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ค์ํ ๋ก๊น ํ๋ ์์ํฌ(Logback, Log4j2 ๋ฑ)๋ฅผ ์ถ์ํํ์ฌ ์ผ๊ด๋ ๋ก๊น API๋ฅผ ์ ๊ณตํ๋ ํ์ด์(facade)์ด๋ค. ์คํ๋ง ๋ถํธ์์๋ SLF4J๋ฅผ ๊ธฐ๋ณธ ๋ก๊น API๋ก ์ฌ์ฉํ๋ฉฐ, ๊ธฐ๋ณธ ๊ตฌํ์ฒด๋ก Logback์ ์ฑํํ๊ณ ์๋ค. SLF4J๋ฅผ ํตํด ๋ก๊น ํ์ผ์ ์ด๋ฆ๊ณผ ๊ฒฝ๋ก๋ฅผ ์ค์ ํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์.
1. application.properties
๋ฅผ ํตํ ์ค์
์คํ๋ง ๋ถํธ์์๋ application.properties
๋๋ application.yml
ํ์ผ์ ํตํด ๊ฐ๋จํ๊ฒ ๋ก๊น
ํ์ผ์ ์ด๋ฆ๊ณผ ๊ฒฝ๋ก๋ฅผ ์ค์ ํ ์ ์๋ค. ์ด๋ ๊ฐ์ฅ ์์ฌ์ด ๋ฐฉ๋ฒ์ผ๋ก, ๋ณ๋์ ์ค์ ํ์ผ์ ์์ฑํ ํ์ ์์ด ๊ธฐ๋ณธ์ ์ธ ๋ก๊น
์ค์ ์ ๊ด๋ฆฌํ ์ ์๋ค.
# ๋ก๊น
ํ์ผ ์ด๋ฆ ์ค์ logging.file.name=logs/myapp.log
# ๋ก๊น
ํ์ผ ๊ฒฝ๋ก ์ค์ logging.file.path=/var/log/myapp
# ๋๋ ๋ก๊น
ํ์ผ์ ์ ์ฒด ๊ฒฝ๋ก๋ฅผ ์ค์ ํ ์๋ ์๋คlogging.file=/var/log/myapp/myapp.log
logging.file.name
: ๋ก๊น ํ์ผ์ ์ด๋ฆ์ ์ค์ ํ๋ค. ์๋ฅผ ๋ค์ด,logs/myapp.log
๋ ํ์ฌ ๋๋ ํ ๋ฆฌ์logs
ํด๋์myapp.log
ํ์ผ์ ์์ฑํ๋ค.logging.file.path
: ๋ก๊น ํ์ผ์ด ์ ์ฅ๋ ๋๋ ํ ๋ฆฌ์ ๊ฒฝ๋ก๋ฅผ ์ค์ ํ๋ค. ์ด ๊ฒฝ์ฐ,logging.file.name
๊ณผ ํจ๊ป ์ฌ์ฉํ์ฌ ํ์ผ ์ด๋ฆ์ ์ง์ ํ ์ ์๋ค.logging.file
: ๋ก๊น ํ์ผ์ ์ ์ฒด ๊ฒฝ๋ก๋ฅผ ์ค์ ํ๋ค. ํ์ผ ์ด๋ฆ๊ณผ ๊ฒฝ๋ก๋ฅผ ํ ๋ฒ์ ์ง์ ํ ๋ ์ ์ฉํ๋ค.
2. Logback ์ค์ ํ์ผ(logback-spring.xml
)์ ํตํ ์ค์
๋ณด๋ค ์ธ๋ฐํ ๋ก๊น
์ค์ ์ด ํ์ํ๋ค๋ฉด, logback-spring.xml
ํ์ผ์ ์ฌ์ฉํ์ฌ SLF4J์ Logback์ ์ปค์คํฐ๋ง์ด์งํ ์ ์๋ค. ์ด ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋ฉด ๋ก๊ทธ ํ์ผ์ ํ์, ๋กค๋ง ์ ์ฑ
, ๋ก๊ทธ ๋ ๋ฒจ ๋ฑ์ ์์ธํ๊ฒ ์ ์ดํ ์ ์๋ค.
Logback ์ค์ ์์
<configuration>
<!-- ํ์ผ ๊ฒฝ๋ก์ ์ด๋ฆ์ ํ๋กํผํฐ๋ก ์ค์ --> <property name="LOG_PATH" value="${LOG_PATH:-logs}"/> <property name="LOG_FILE" value="${LOG_FILE:-myapp.log}"/>
<!-- ์ฝ์ ๋ก๊ทธ ์ค์ --> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{36} - %msg%n</pattern> </encoder> </appender>
<!-- ํ์ผ ๋ก๊ทธ ์ค์ --> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_PATH}/${LOG_FILE}</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_PATH}/${LOG_FILE}.%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>10MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <maxHistory>30</maxHistory> </rollingPolicy> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{36} - %msg%n</pattern> </encoder> </appender>
<!-- ํน์ ํจํค์ง์ ๋ํ ๋ก๊น
๋ ๋ฒจ ์ค์ --> <logger name="com.hippoo" level="DEBUG" additivity="false"> <appender-ref ref="FILE"/> </logger>
<!-- ๋ฃจํธ ๋ก๊ฑฐ ์ค์ --> <root level="INFO"> <appender-ref ref="CONSOLE"/> <appender-ref ref="FILE"/> </root>
</configuration>
- ํ๋กํผํฐ ์ค์ :
LOG_PATH
์LOG_FILE
ํ๋กํผํฐ๋ฅผ ์ฌ์ฉํ์ฌ ๋ก๊ทธ ํ์ผ์ ๊ฒฝ๋ก์ ์ด๋ฆ์ ์ ์ฐํ๊ฒ ๊ด๋ฆฌํ ์ ์๋ค. ํ๊ฒฝ ๋ณ์๋ ์์คํ ํ๋กํผํฐ๋ฅผ ํตํด ๊ฐ์ ๋์ ์ผ๋ก ์ค์ ํ ์ ์๋ค. - ์ฝ์ ๋ฐ ํ์ผ ์ฑ๋ ๋: ๋ก๊ทธ๋ฅผ ์ฝ์๊ณผ ํ์ผ ๋ชจ๋์ ์ถ๋ ฅํ๋๋ก ์ค์ ํ์๋ค. ํ์ผ ๋ก๊ทธ๋ ๋กค๋ง ์ ์ฑ ์ ํตํด ์ผ์ ํฌ๊ธฐ๋ ๊ธฐ๊ฐ๋ง๋ค ์๋ก์ด ๋ก๊ทธ ํ์ผ๋ก ๋ถ๋ฆฌ๋๋ค.
- ๋ก๊ฑฐ ์ค์ : ํน์ ํจํค์ง(
com.hippoo
)์ ๋ํด ๋ณ๋์ ๋ก๊น ๋ ๋ฒจ์ ์ค์ ํ ์ ์๋ค. - ๋ฃจํธ ๋ก๊ฑฐ: ๊ธฐ๋ณธ ๋ก๊น ๋ ๋ฒจ๊ณผ ์ฑ๋ ๋๋ฅผ ์ค์ ํ๋ค. ๋ชจ๋ ๋ก๊ทธ๋ ์ฝ์๊ณผ ํ์ผ์ ์ถ๋ ฅ๋๋ค.
3. ํ๊ฒฝ ๋ณ์ ๋๋ JVM ์ต์ ์ ํตํ ์ค์
์คํ๋ง ๋ถํธ ์ ํ๋ฆฌ์ผ์ด์ ์ ์คํํ ๋, ํ๊ฒฝ ๋ณ์๋ JVM ์ต์ ์ ํตํด ๋ก๊น ํ์ผ์ ์ด๋ฆ๊ณผ ๊ฒฝ๋ก๋ฅผ ์ค์ ํ ์๋ ์๋ค.
ํ๊ฒฝ ๋ณ์ ์ค์ ์์
export LOG_FILE=/var/log/myapp/custom.logexport LOG_PATH=/var/log/myappjava -jar myapp.jar
JVM ์ต์ ์ค์ ์์
java -Dlogging.file.name=/var/log/myapp/custom.log -Dlogging.file.path=/var/log/myapp -jar myapp.jar
์ด ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋ฉด, ์ฝ๋๋ ์ค์ ํ์ผ์ ์์ ํ์ง ์๊ณ ๋ ๋ก๊ทธ ํ์ผ์ ์์น์ ์ด๋ฆ์ ๋ณ๊ฒฝํ ์ ์๋ค.
4. ์ฝ๋ ๋ด์์ ๋ก๊น ์ค์ ๋ณ๊ฒฝ
์ฝ๋ ๋ด์์ ์ง์ ๋ก๊น ์ค์ ์ ๋ณ๊ฒฝํ๋ ๊ฒ์ ๊ถ์ฅ๋์ง ์์ง๋ง, ํ์ํ ๊ฒฝ์ฐ SLF4J API๋ฅผ ์ฌ์ฉํ์ฌ ๋ก๊น ๋ ๋ฒจ์ ๋์ ์ผ๋ก ๋ณ๊ฒฝํ ์ ์๋ค. ๋ค๋ง, ์ด๋ ๋ฐํ์ ๋์๋ง ์ ํจํ๋ฉฐ, ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฌ์์ํ๋ฉด ์ด๊ธฐ ์ค์ ์ผ๋ก ๋์๊ฐ๋ค.
import org.slf4j.LoggerFactory;import ch.qos.logback.classic.Logger;import ch.qos.logback.classic.Level;
public void changeLogLevel(String loggerName, String levelStr) { Logger logger = (Logger) LoggerFactory.getLogger(loggerName); Level level = Level.toLevel(levelStr, Level.INFO); logger.setLevel(level);}
์ค์ ์ ํ๋ฆฌ์ผ์ด์ ์ฝ๋์์ ๋ก๊น ํ์ฉํ๊ธฐ ๐ ๏ธ
์ค์ ์ ํ๋ฆฌ์ผ์ด์ ์ฝ๋์์ ๋ก๊น ์ ํ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์ ์ฝ๋๋ฅผ ํตํด ์ดํด๋ณด์. SLF4J์ Logback์ ์ฌ์ฉํ์ฌ ๋ค์ํ ๋ก๊น ๋ ๋ฒจ๋ก ๋ฉ์์ง๋ฅผ ์ถ๋ ฅํ๋ ๋ฐฉ๋ฒ์ ์ค๋ช ํ๋ค.
1. SLF4J์ Logback์ ์ด์ฉํ ๋ก๊น ์ค์
์คํ๋ง ๋ถํธ ํ๋ก์ ํธ์์๋ ๊ธฐ๋ณธ์ ์ผ๋ก SLF4J์ Logback์ด ์ค์ ๋์ด ์๋ค. Lombok์ ์ฌ์ฉํ๋ฉด ๋ก๊ฑฐ ์ค์ ์ด ๋์ฑ ๊ฐํธํด์ง๋ค. Lombok์ @Slf4j
์ ๋ํ
์ด์
์ ์ฌ์ฉํ์ฌ ๋ก๊ฑฐ๋ฅผ ์๋์ผ๋ก ์์ฑํ ์ ์๋ค.
Lombok์ ์ฌ์ฉํ ๋ก๊น ์์
package com.hippoo.learnspringboot.controller;
import com.hippoo.learnspringboot.service.CourseService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;
@RestController@RequestMapping("/courses")@Slf4jpublic class CourseController {
@Autowired private CourseService courseService;
@GetMapping("/{id}") public Course getCourseById(@PathVariable Long id) { log.info("Fetching course with id: {}", id); Course course = courseService.findById(id); if (course == null) { log.warn("Course with id: {} not found", id); } else { log.debug("Course details: {}", course); } return course; }
@PostMapping public Course createCourse(@RequestBody Course course) { log.info("Creating new course: {}", course.getName()); Course createdCourse = courseService.save(course); log.info("Created course with id: {}", createdCourse.getId()); return createdCourse; }
@DeleteMapping("/{id}") public void deleteCourse(@PathVariable Long id) { log.info("Deleting course with id: {}", id); courseService.deleteById(id); log.info("Deleted course with id: {}", id); }}
@Slf4j
: Lombok ์ ๋ํ ์ด์ ์ผ๋ก, ํด๋์ค ๋ด์log
๋ณ์๋ฅผ ์๋์ผ๋ก ์์ฑํด์ค๋ค.- ๋ก๊น
๋ฉ์๋:
log.info()
,log.warn()
,log.debug()
,log.error()
๋ฑ์ ์ฌ์ฉํ์ฌ ๋ค์ํ ๋ก๊น ๋ ๋ฒจ๋ก ๋ฉ์์ง๋ฅผ ์ถ๋ ฅํ ์ ์๋ค. - ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ:
{}
๋ฅผ ์ฌ์ฉํ์ฌ ๋ฉ์์ง์ ๋ณ์๋ฅผ ์ฝ์ ํ ์ ์์ผ๋ฉฐ, ์ด๋ ์ฑ๋ฅ์ ์ด์ ์ ์ ๊ณตํ๋ค.
2. ์ง์ ๋ก๊ฑฐ ์์ฑํ๊ธฐ
Lombok์ ์ฌ์ฉํ์ง ์๋ ๊ฒฝ์ฐ, SLF4J์ LoggerFactory
๋ฅผ ์ด์ฉํ์ฌ ๋ก๊ฑฐ๋ฅผ ์ง์ ์์ฑํ ์ ์๋ค.
์ง์ ๋ก๊ฑฐ๋ฅผ ์์ฑํ ๋ก๊น ์์
package com.hippoo.learnspringboot.service;
import com.hippoo.learnspringboot.repository.CourseRepository;import com.hippoo.learnspringboot.model.Course;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;
@Servicepublic class CourseService {
private static final Logger logger = LoggerFactory.getLogger(CourseService.class);
@Autowired private CourseRepository courseRepository;
public Course findById(Long id) { logger.info("Service: Fetching course with id: {}", id); try { Course course = courseRepository.findById(id); if (course == null) { logger.warn("Service: Course with id: {} not found", id); } else { logger.debug("Service: Found course: {}", course); } return course; } catch (Exception e) { logger.error("Service: Error fetching course with id: {}", id, e); throw e; } }
public Course save(Course course) { logger.info("Service: Saving new course: {}", course.getName()); try { Course savedCourse = courseRepository.save(course); logger.info("Service: Saved course with id: {}", savedCourse.getId()); return savedCourse; } catch (Exception e) { logger.error("Service: Error saving course: {}", course.getName(), e); throw e; } }
public void deleteById(Long id) { logger.info("Service: Deleting course with id: {}", id); try { courseRepository.deleteById(id); logger.info("Service: Deleted course with id: {}", id); } catch (Exception e) { logger.error("Service: Error deleting course with id: {}", id, e); throw e; } }}
- ๋ก๊ฑฐ ์์ฑ:
LoggerFactory.getLogger(CourseService.class)
๋ฅผ ํตํด ๋ก๊ฑฐ๋ฅผ ์์ฑํ๋ค. - ๋ก๊น
๋ฉ์๋: ํ์ํ ๊ณณ์์
logger.info()
,logger.warn()
,logger.debug()
,logger.error()
๋ฑ์ ์ฌ์ฉํ์ฌ ๋ฉ์์ง๋ฅผ ๊ธฐ๋กํ๋ค. - ์์ธ ๋ก๊น
: ์์ธ ๋ฐ์ ์
logger.error()
๋ฅผ ์ฌ์ฉํ์ฌ ์คํ ํธ๋ ์ด์ค๋ฅผ ํฌํจํ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ๊ธฐ๋กํ ์ ์๋ค.
3. ๋ก๊น ๋ ๋ฒจ๋ณ ์ฌ์ฉ ์ฌ๋ก
-
TRACE: ๋งค์ฐ ์์ธํ ์ ๋ณด, ์ฃผ๋ก ๊ฐ๋ฐ ๋จ๊ณ์์ ์ฌ์ฉ
log.trace("Trace log: Detailed debug information for tracing execution."); -
DEBUG: ๋๋ฒ๊น ์ ์ํ ์ ๋ณด
log.debug("Debug log: Variables state - userId={}, userName={}", userId, userName); -
INFO: ์ผ๋ฐ ์ ๋ณด์ฑ ๋ฉ์์ง
log.info("Info log: Application started successfully."); -
WARN: ์ ์ฌ์ ์ธ ๋ฌธ์
log.warn("Warn log: Deprecated API usage detected."); -
ERROR: ์ค๋ฅ ๋ฐ์ ์ ๋ฉ์์ง
log.error("Error log: Failed to process request.", exception);
๋ก๊น ์ถ๋ ฅ ์์น์ ๋กค๋ง ํ์ผ ์ค์
- ์ถ๋ ฅ ์์น: ๊ธฐ๋ณธ์ ์ผ๋ก ์ฝ์(ํ์ค ์ถ๋ ฅ)์ ๋ก๊ทธ๋ฅผ ์ถ๋ ฅํ๋ค. ํ์ผ์ด๋ ์๊ฒฉ ๋ก๊น ์๋ฒ ๋ฑ ๋ค์ํ ์์น๋ก๋ ์ ์กํ ์ ์๋ค.
- ๋กค๋ง ํ์ผ ์ค์ : ๋ก๊ทธ ํ์ผ์ ํฌ๊ธฐ๊ฐ ์ปค์ง๋ฉด ์๋์ผ๋ก ํ์ผ์ ๋ถํ (๋กค๋ง)ํ์ฌ ์ ์ฅํ ์ ์๋ค.
์๋ฅผ ๋ค์ด, Logback์<rollingPolicy>
๋ Log4j2์<RollingFile>
๋ฑ์ ์ค์ ํด์ ๊ตฌํํ๋ค.
Logback ๋กค๋ง ํ์ผ ์ค์ ์์
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>logs/myapp.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>logs/myapp-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>10MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <maxHistory>30</maxHistory> </rollingPolicy> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{36} - %msg%n</pattern> </encoder></appender>
<file>
: ๊ธฐ๋ณธ ๋ก๊ทธ ํ์ผ์ ์์น์ ์ด๋ฆ์ ์ค์ ํ๋ค.<rollingPolicy>
: ๋กค๋ง ์ ์ฑ ์ ์ค์ ํ๋ค. ์ฌ๊ธฐ์๋ ์๊ฐ ๊ธฐ๋ฐ ๋กค๋ง๊ณผ ํฌ๊ธฐ ๊ธฐ๋ฐ ๋กค๋ง์ ๊ฒฐํฉํ์ฌ, ๋งค์ผ ๋ก๊ทธ ํ์ผ์ ๋ถ๋ฆฌํ๊ณ ๊ฐ ํ์ผ์ ์ต๋ ํฌ๊ธฐ๋ฅผ 10MB๋ก ์ ํํ๋ค.<maxHistory>
: ์ ์งํ ๋ก๊ทธ ํ์ผ์ ๊ฐ์๋ฅผ ์ค์ ํ๋ค. ์ฌ๊ธฐ์๋ 30์ผ์น ๋ก๊ทธ ํ์ผ์ ์ ์งํ๋ค.<pattern>
: ๋ก๊ทธ ๋ฉ์์ง์ ํฌ๋งท์ ์ค์ ํ๋ค.
๊ฒฐ๋ก ๐ฏ
Spring Boot Logging์ ๊ฐ๋ ฅํ ์ถ์ํ ๋๋ถ์ ์ ํ๋ฆฌ์ผ์ด์
๊ฐ๋ฐ์๊ฐ ๋ณต์กํ ๋ก๊น
์ค์ ์ ํฌ๊ฒ ์ ๊ฒฝ ์ฐ์ง ์์๋ ๋๋๋ก ๋์์ค๋ค. ์ํฉ์ ๋ฐ๋ผ Logback, Log4j2 ๋ฑ ๊ตฌํ์ฒด๋ฅผ ์์ ๋กญ๊ฒ ์ ํํ ์ ์๊ณ , application.properties
๋ง์ผ๋ก๋ ์ถฉ๋ถํ ๊ฐ๋จํ ์ค์ ์ด ๊ฐ๋ฅํ๋ค.
SLF4J๋ฅผ ํตํด ๋ก๊น
API๋ฅผ ์ผ๊ด๋๊ฒ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ, ๋ก๊น
ํ์ผ์ ์ด๋ฆ๊ณผ ๊ฒฝ๋ก๋ฅผ ์ ์ฐํ๊ฒ ์ค์ ํ ์ ์๋ค. ๊ธฐ๋ณธ ์ค์ ์ ํ์ฉํ๊ฑฐ๋, logback-spring.xml
๊ณผ ๊ฐ์ ์ค์ ํ์ผ์ ํตํด ์ธ๋ฐํ๊ฒ ์กฐ์ ํ ์ ์์ด ๋ค์ํ ์๊ตฌ์ฌํญ์ ์ถฉ์กฑํ ์ ์๋ค.
์ค์ ์ ํ๋ฆฌ์ผ์ด์ ์ฝ๋์์๋ ๋ก๊น ์ ์ ์ ํ ๋ ๋ฒจ๋ก ์ฌ์ฉํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ์ถ์ ํ๊ณ , ๋ฌธ์ ๋ฐ์ ์ ์ ์ํ๊ฒ ์์ธ์ ํ์ ํ ์ ์๋๋ก ํ๋ ๊ฒ์ด ์ค์ํ๋ค. ํนํ ๋๊ท๋ชจ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ๋ก๊น ์ถ๋ ฅ ์์น, ๋กค๋ง ์ ์ฑ , ๋ ๋ฒจ ์ค์ ๋ฑ์ ๋ฉด๋ฐํ ๊ฒํ ํด์ผ ํ๋ค. ์์ฐ ํ๊ฒฝ์์ ๋ก๊ทธ๊ฐ ๋๋ฌด ๋ง์ผ๋ฉด ๋์คํฌ๋ ๋คํธ์ํฌ ์์์ ๋น ๋ฅด๊ฒ ์๋นํ ์ ์์ผ๋ฏ๋ก, ์ ์ ํ ๋ก๊ทธ ๋ ๋ฒจ ๋ฐ ๋ก๊ทธ ๋กํ ์ด์ ์ ์ฑ ์ ์๋ฆฝํ๋ ๊ฒ์ด ํ์์ ์ด๋ค.