skip to content
Q

[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์„ ํ†ตํ•ด ๋ฉ”์‹œ์ง€๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค.

์žฅ์ 

  1. ์ผ๊ด€๋œ ๋กœ๊น… API: commons-logging์„ ํ†ตํ•ด SLF4J, Log4j2, Logback ๋“ฑ ๋‹ค์–‘ํ•œ ๊ตฌํ˜„์ฒด๋กœ ์‰ฝ๊ฒŒ ๊ต์ฒด ๊ฐ€๋Šฅ
  2. ๊ฐ„๋‹จํ•œ ์„ค์ •: application.properties(๋˜๋Š” .yml) ํŒŒ์ผ์„ ํ†ตํ•ด ์†์‰ฝ๊ฒŒ ๋กœ๊น… ๋ ˆ๋ฒจ ๋“ฑ์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Œ

๋กœ๊น… ํ”„๋ ˆ์ž„์›Œํฌ ๊ตฌ์กฐ ์ดํ•ด

์•„๋ž˜ ๊ทธ๋ฆผ์€ ํ”ํžˆ ๋ณผ ์ˆ˜ ์žˆ๋Š” Java ๋กœ๊น… ๊ตฌ์กฐ์ด๋‹ค:

[Logger ํ˜ธ์ถœ๋ถ€] -> [commons-logging or SLF4J] -> [์‹ค์ œ ๋กœ๊น… ๊ตฌํ˜„์ฒด(Logback, Log4j2...)]

์Šคํ”„๋ง ๋ถ€ํŠธ์—์„œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋‹ค์Œ ํ๋ฆ„์„ ๋”ฐ๋ฅธ๋‹ค:

[์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ] -> [Commons Logging] -> [Logback]

์›ํ•œ๋‹ค๋ฉด Log4j2๋กœ ๊ต์ฒดํ•  ์ˆ˜๋„ ์žˆ๋‹ค. SLF4J๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ๋น„์Šทํ•˜๋‹ค.


์Šคํ”„๋ง ๋ถ€ํŠธ ๊ธฐ๋ณธ ๋กœ๊น… ์„ค์ •

์Šคํ”„๋ง ๋ถ€ํŠธ์˜ ๊ธฐ๋ณธ ๋กœ๊น…์€ Logback์„ ์‚ฌ์šฉํ•˜๋ฉฐ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํŠน์ง•์„ ๊ฐ€์ง„๋‹ค:

  1. ๊ธฐ๋ณธ ๋ ˆ๋ฒจ: INFO
  2. ๋กœ๊ทธ ์ถœ๋ ฅ ํฌ๋งท: ์‹œ๊ฐ„, ์Šค๋ ˆ๋“œ, ๋กœ๊ฑฐ ์ด๋ฆ„, ๋กœ๊ทธ ๋ ˆ๋ฒจ, ๋กœ๊ทธ ๋ฉ”์‹œ์ง€ ํ˜•ํƒœ๋กœ ์ฝ˜์†” ์ถœ๋ ฅ
  3. ์ปฌ๋Ÿฌ ์ง€์›: ์ฝ˜์†”์—์„œ ๋กœ๊ทธ ๋ ˆ๋ฒจ๋ณ„๋กœ ๊ตฌ๋ถ„๋˜๋Š” ์ปฌ๋Ÿฌ๋ฅผ ์ง€์›

์Šคํ”„๋ง ๋ถ€ํŠธ๋Š” spring-boot-starter-logging ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ, ์ด๋Š” Logback์— ๋Œ€ํ•œ ๊ธฐ๋ณธ ์„ค์ • ํŒŒ์ผ(logback.xml)์„ ๋‚ด์žฅํ•˜๊ณ  ์žˆ๋‹ค.


๋กœ๊น… ๋ ˆ๋ฒจ(Log Levels)

์Šคํ”„๋ง ๋ถ€ํŠธ์—์„œ ์ง€์›ํ•˜๋Š” ๋Œ€ํ‘œ์ ์ธ ๋กœ๊น… ๋ ˆ๋ฒจ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค:

  1. TRACE: ๊ฐ€์žฅ ์ƒ์„ธํ•œ ๋กœ๊ทธ ๋ ˆ๋ฒจ. ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ์ฃผ๋กœ ์‚ฌ์šฉ
  2. DEBUG: ๋””๋ฒ„๊น…์„ ์œ„ํ•œ ์ •๋ณด
  3. INFO: ์ผ๋ฐ˜ ์ •๋ณด์„ฑ ๋ฉ”์‹œ์ง€
  4. WARN: ์ž ์žฌ์ ์ธ ๋ฌธ์ œ
  5. ERROR: ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ๋ฉ”์‹œ์ง€
  6. FATAL (Log4j2 ๋“ฑ ์ผ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์‚ฌ์šฉ): ์น˜๋ช…์ ์ธ ์˜ค๋ฅ˜

๊ธฐ๋ณธ๊ฐ’์€ INFO์ด๋ฉฐ, ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋Š” DEBUG๋‚˜ TRACE๋กœ ์„ธ๋ถ€ ์ •๋ณด ๋กœ๊ทธ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.


์„ค์ • ํŒŒ์ผ(application.properties) ์˜ˆ์‹œ

์•„๋ž˜๋Š” application.properties์—์„œ ๋กœ๊น… ๋ ˆ๋ฒจ์„ ์„ค์ •ํ•˜๋Š” ์˜ˆ์‹œ์ด๋‹ค:

# ์Šคํ”„๋ง ๋กœ๊น… ๋ ˆ๋ฒจ
logging.level.org.springframework=debug
logging.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 ํŒŒ์ผ์„ ํ†ตํ•ด ๊ฐ„๋‹จํ•˜๊ฒŒ ๋กœ๊น… ํŒŒ์ผ์˜ ์ด๋ฆ„๊ณผ ๊ฒฝ๋กœ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋Š” ๊ฐ€์žฅ ์†์‰ฌ์šด ๋ฐฉ๋ฒ•์œผ๋กœ, ๋ณ„๋„์˜ ์„ค์ • ํŒŒ์ผ์„ ์ž‘์„ฑํ•  ํ•„์š” ์—†์ด ๊ธฐ๋ณธ์ ์ธ ๋กœ๊น… ์„ค์ •์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

application.properties
# ๋กœ๊น… ํŒŒ์ผ ์ด๋ฆ„ ์„ค์ •
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 ์„ค์ • ์˜ˆ์‹œ

src/main/resources/logback-spring.xml
<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 ์˜ต์…˜์„ ํ†ตํ•ด ๋กœ๊น… ํŒŒ์ผ์˜ ์ด๋ฆ„๊ณผ ๊ฒฝ๋กœ๋ฅผ ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ • ์˜ˆ์‹œ

Terminal window
export LOG_FILE=/var/log/myapp/custom.log
export LOG_PATH=/var/log/myapp
java -jar myapp.jar

JVM ์˜ต์…˜ ์„ค์ • ์˜ˆ์‹œ

Terminal window
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")
@Slf4j
public 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;
@Service
public 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๊ณผ ๊ฐ™์€ ์„ค์ • ํŒŒ์ผ์„ ํ†ตํ•ด ์„ธ๋ฐ€ํ•˜๊ฒŒ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ์–ด ๋‹ค์–‘ํ•œ ์š”๊ตฌ์‚ฌํ•ญ์„ ์ถฉ์กฑํ•  ์ˆ˜ ์žˆ๋‹ค.

์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ์—์„œ๋Š” ๋กœ๊น…์„ ์ ์ ˆํ•œ ๋ ˆ๋ฒจ๋กœ ์‚ฌ์šฉํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ƒํƒœ๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ์ถ”์ ํ•˜๊ณ , ๋ฌธ์ œ ๋ฐœ์ƒ ์‹œ ์‹ ์†ํ•˜๊ฒŒ ์›์ธ์„ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค. ํŠนํžˆ ๋Œ€๊ทœ๋ชจ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ๋กœ๊น… ์ถœ๋ ฅ ์œ„์น˜, ๋กค๋ง ์ •์ฑ…, ๋ ˆ๋ฒจ ์„ค์ • ๋“ฑ์„ ๋ฉด๋ฐ€ํžˆ ๊ฒ€ํ† ํ•ด์•ผ ํ•œ๋‹ค. ์ƒ์‚ฐ ํ™˜๊ฒฝ์—์„œ ๋กœ๊ทธ๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์œผ๋ฉด ๋””์Šคํฌ๋‚˜ ๋„คํŠธ์›Œํฌ ์ž์›์„ ๋น ๋ฅด๊ฒŒ ์†Œ๋น„ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ์ ์ ˆํ•œ ๋กœ๊ทธ ๋ ˆ๋ฒจ ๋ฐ ๋กœ๊ทธ ๋กœํ…Œ์ด์…˜ ์ •์ฑ…์„ ์ˆ˜๋ฆฝํ•˜๋Š” ๊ฒƒ์ด ํ•„์ˆ˜์ ์ด๋‹ค.


์ฐธ๊ณ ์ž๋ฃŒ ๐Ÿ“š