์ง๋ ๊ธ์์ Logback์ด ๋ญ์ง ๊ฐ ์ก์๋ค๋ฉด, ์ด๋ฒ์๋ ๋๋์ด logback-spring.xml์ ์ง์ ์ค๊ณํ๊ณ ๊ตฌ์กฐํํ๋ ๊ณผ์ ์ ์ ๋ฆฌํด๋ณธ๋ค.
๋ฌดํฑ๋๊ณ xml์ ์ฐ๊ธฐ๋ณด๋ค ๋๊ฐ ๋ณผ ๋ก๊ทธ์ธ์ง, ์ด๋์ ์๋นํ ์ง, ์ด๋ค ํฌ๋งท์ด ๋ง๋์ง, ๊ณตํต ํค(MDC)๋ฅผ ๋ฌด์์ผ๋ก ๊ณ ์ ํ ์ง๋ถํฐ ์ ๋ฆฌํ๋ค.
๋๊ฐ ๋ก๊ทธ๋ฅผ ๋ณผ๊น? โ ๊ฐ๋ฐ์, ์ด์์, ELK ์์คํ
์ด๋์ ๋จ๊ธธ๊น? โ ์ฝ์, ํ
์คํธ, JSON, Kafka
์ด๋ค ํฌ๋งท์ผ๋ก? โ ์ฝ์์ ๊ฐ๋
์ฑ, ํ
์คํธ๋ Key=Value, JSON์ ๊ตฌ์กฐํ
MDC ํ์ค์? โ traceId, spanId, userId, uri, method, clientIp
์ด ๋ค ๊ฐ์ง๊ฐ ์ ๋ฆฌ๋์ง ์์ผ๋ฉด xml ์ค์ ์ ๊ฒฐ๊ตญ โ์ฐํ๋ ๋๋ก ์ฐ๋ ๋ก๊ทธโ๋ก ๋๋๋ฒ๋ฆฐ๋ค.
๋๋ ๊ทธ๋์ ๋จผ์ โ๋ก๊ทธ์ ๋ชฉ์ โ์ ์ ํ๊ณ , ๊ฑฐ๊ธฐ์ ๋ง์ถฐ Appender๋ฅผ ๋๋๋ ๊ฑธ ์ถ๋ฐ์ ์ผ๋ก ์ผ์๋ค.
์ด ์ง๋ฌธ์ ํ๊ณ ๋๋, ์์ฐ์ค๋ฝ๊ฒ ๋ค ๊ฐ๋ ์ถ๊ตฌ๊ฐ ์๋ฆฌ๋ฅผ ์ก์๋ค.
๐ CONSOLE ยท TEXT_FILE ยท JSON_FILE ยท KAFKA_JSON
๊ฒฐ๊ตญ ํต์ฌ์ ๋ ๊ฐ์ง์๋ค.
MDC ํค๋ฅผ ํ์คํํ๋ ๊ฒ, ๊ทธ๋ฆฌ๊ณ Appender๋ฅผ ์ฉ๋๋ณ๋ก ๋ถ๋ฆฌํ๋ ๊ฒ !!!
logback-spring.xml
์ธ๊ฐ ?logback.xml
๋ ๋์์ ํ์ง๋ง, Spring Boot๋ logback-spring.xml
์ ๊ถ์ฅํ๋ค.
์จ๋ณด๋ ๊ทธ ์ด์ ๊ฐ ๋ช
ํํ๋ค.
${LOG_PATH}
, ${KAFKA_BOOTSTRAP_SERVERS}
๊ฐ์ ๊ฐ์ application.yml
์์ ๊ฐ์ ธ์ ์ค์ ๊ณผ ํ๊ฒฝ์ ํ๋ชธ์ฒ๋ผ ๊ด๋ฆฌํ ์ ์๋ค.<springProfile name="dev|prod">
๋ฅผ ํ์ฉํ๋ฉด ํ๊ฒฝ๋ณ Appenderยท๋ ๋ฒจ ๊ตฌ์ฑ์ ์ฝ๊ฒ ๋ถ๋ฆฌํ ์ ์๋ค.์์ฝํ๋ฉด, logback.xml
์ ์์ Logback ์ค์ ์ด๊ณ , logback-spring.xml
์ Spring๊ณผ ํจ๊ป ์์ง์ด๋ฉฐ ํ๊ฒฝ ๋ถ๊ธฐ๋ฅผ ํ์ ์ค์ ์ด๋ค.
๋๋ ์ด๊ฑธ ์์์ ๋, โ์, ๋ก๊ทธ ์ค์ ๋ ์ฝ๋์ฒ๋ผ ํ๊ฒฝ ๋ถ๊ธฐ๋ฅผ ๊ฐ์ ธ๊ฐ์ผ ํ๋๊ตฌ๋โ๋ผ๋ ๊ฑธ ์ ๋๋ก ์ฒด๊ฐํ๋ค.
logback.xml: Spring ํ๋กํผํฐ/ํ๋กํ ๋ถ๊ฐ, ์์ Logback ๊ตฌ๋ฌธ
logback-spring.xml:${}
์ง์,<springProfile>
์ง์, ๋ถํธ์ ์์ฐ์ค๋ฌ์ด ์ฐ๋
Appender๋ฅผ ์ฌ๋ฌ ๊ฐ ๋๋๋ผ๋, ๊ณต์ฉ ํค๊ฐ ์๋ค๋ฉด ๋ก๊ทธ๋ ์ ๊ฐ๊ฐ์ ์ธ์ด๋ก ๋งํ๊ฒ ๋๋ค.
๊ทธ๋์ ์๋น์ค ๊ณณ๊ณณ์์ ๋จ๊ธฐ๋ ๋งฅ๋ฝ์ MDC(Mapped Diagnostic Context)๋ก ํต์ผํ๋ค.
์ต์ข ์ ์ผ๋ก ํ์คํํ ํค๋ ๋ค์๊ณผ ๊ฐ๋ค.
traceId
,spanId
โ ๋ถ์ฐ ์ถ์ ์ ์ค๋ง๋ฆฌ๋ก ์ผ์๋ค.timestamp
โ ์ ํ๋ฆฌ์ผ์ด์ ๊ธฐ์ค ์๊ฐ์ผ๋ก ํต์ผํ๋ค.userId
โ ์ธ์ฆ ์ ๊ตฌ๊ฐ์anon
์ผ๋ก ๊ณ ์ ํ๋ค.uri
,method
,clientIp
,userAgent
โ ์์ฒญ ๋ฉํ๋ฅผ ๊ณตํต ํค๋ก ์ก์๋ค.
์ด ๊ฐ๋ค์ ํํฐ/์ธํฐ์
ํฐ์์ ์ฃผ์
ํ๊ณ , ๋น๋๊ธฐ ํ๋ฆ์๋ ์ ํ๋๋๋ก ๋ณ๋ ์ ํธ์ ๋๋ค.
๋๋ถ์ ์ปจํธ๋กค๋ฌ๋ ๋ฐฐ์น๋ , ๊ฐ์ ์ฌ๊ฑด์ ์ธ์ ๋ ๊ฐ์ ์ด๋ฆํ๋ฅผ ๋ฌ๊ณ ํ๋ฌ๊ฐ๋ค.
๊ทธ๋ฆฌ๊ณ Kibana์์๋ userId=anon
์กฐ๊ฑด์ผ๋ก๋ ๊ฒ์ํ ์ ์๊ณ , Excel์์๋ ์ปฌ๋ผ์ด ๊นจ์ง์ง ์๋๋ค.
๐ ์์ธํ ๋ด์ฉ์ ๋ค์ ๊ธ(Trace ๊ด๋ จ)์์ ๋ค๋ฃฐ ์์ ์ด๋ค.
ํ ๊ฐ์ง ํฌ๋งท์ผ๋ก ์ ๋ถ๋ฅผ ํด๊ฒฐํ๋ ค๋ค ๋ณด๋ฉด ์ด๋์ ๊ฐ๋ ํํ์ด ์๊ฒผ๋ค. ๊ทธ๋์ ์์ ์ถ๊ตฌ๋ฅผ ๋ค ๊ฐ๋๋ก ๋ฝ์๋ค.
์ฝ์์ ๊ฐ๋ฐ์ฉ ๊ฐ๋ ์ฑ, ํ ์คํธ๋ ์ฌ๋์ด ๋ณด๋ ๊ธฐ๋ก, JSON์ ๊ธฐ๊ณ๊ฐ ๋จน๋ ๊ตฌ์กฐํ, Kafka๋ ์ค์ ์์ง ํ์ดํ๋ผ์ธ ์ ์ฉ.
1. CONSOLE โ ๊ฐ๋ฐ์ ๋์ผ๋ก ํ์ธ (๊ฐ๋ฐยท๋๋ฒ๊น
์ฉ)
2. TEXT_FILE โ RestAPI, Excel ํ์ฑ ๋ฑ ์ฌ๋์ฉ ๊ธฐ๋ก
3. JSON_FILE โ Kafka ์ฅ์ ๋๋น, Filebeat Fallback์ฉ ๋จธ์ ์ฉ ๊ตฌ์กฐํ ๋ก๊ทธ
4. KAFKA_JSON โ ์ค์ ์์ง ํ์ดํ๋ผ์ธ ์ ์ก (ELK ์คํ์ฉ)
๊ฐ์ ์ฌ๊ฑด์ด ๋ค ๊ฐ๋๋ก ๋๋์ด๋, MDC ์คํค๋ง๊ฐ ๊ฐ์ผ๋ ์๋ก ์ฐ๊ฒฐํ ์ ์์๋ค.
์ฝ์์์ ๋ณธ traceId๋ฅผ Kibana์์ ์ด์ด์ ์ถ์ ํ๊ณ , ํ
์คํธ ํ์ผ์์ ์ฐพ์ userId๋ก Kafka ํ ํฝ ๋ก๊ทธ๋ฅผ ์ขํ๋ณด๋ ์์ด๋ค.
์ด์ ๋ โ๋ก๊ทธ๋ฅผ ๋ง์ด ์ฐ์โ๊ฐ ์๋๋ผ, โ๋ก๊ทธ๋ฅผ ์ด๋๋ก, ์ด๋ค ํ์ ์ผ๋ก ๋ณด๋ผ์งโ๊ฐ ์ค์ํ ๊ณ ๋ฏผ์ด ๋์๋ค.
๊ฐ๋ฐ ๋จ๊ณ์์ ๊ฐ์ฅ ๋จผ์ ํ์ธํ๋ ๊ฑด ์ธ์ ๋ ์ฝ์์ด๋ค.
๋น ๋ฅด๊ฒ ์์ธ์ ์ฐพ์ผ๋ ค๋ฉด ํ๋์ ๋ค์ด์ค๋ ๊ฐ๋
์ฑ์ด ํ์ํ๋ค.
๊ทธ๋์ ํจํด์๋ traceId, spanId, userId ๊ฐ์ ์ถ์ ์ฉ ํค๋ค์ ์ ๋ถ ๋ฃ์๋ค.
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36}
[traceId:%X{traceId}] [spanId:%X{spanId}] [userId:%X{userId}]
[uri:%X{uri}] [method:%X{method}] [ip:%X{clientIp}] - %msg%n
</pattern>
</encoder>
</appender>
์ด์ ์ค์๋ โ์ฌ๋์ด ์ง์ ๋ก๊ทธ๋ฅผ ์ด์ด๋ณด๋ ์๊ฐโ์ด ๊ฝค ๋ง๋ค.
ํน์ ์์ฒญ์ grep์ผ๋ก ์ถ์ ํ๊ฑฐ๋, Excel๋ก ๋ด๋ ค๋ฐ์ ๋ถ์ํ ๋๊ฐ ๋ํ์ ์ด๋ค.
๊ทธ๋์ TEXT_FILE์ ์ฌ๋์ด ์ฝ๊ธฐ ์ข์ Key=Value ํํ๋ก ํ์คํํ๋ค.
logs/text/
ํด๋์ ๋ ์ง๋ณ๋ก ๋กค๋ง๋๋๋ก ์ค๊ณํด, โ์ด์์๊ฐ ๋น ๋ฅด๊ฒ ๊ฒ์ํ๊ณ ํ์ฑํ ์ ์๋ ํํโ๋ฅผ ๋ณด์ฅํ๋ค.
<appender name="TEXT_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/text/client-requests.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/text/client-requests.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>
timestamp=%X{timestamp}, traceId=%X{traceId}, spanId=%X{spanId}, userId=%X{userId},
uri=%X{uri}, method=%X{method}, clientIp=%X{clientIp}, userAgent=%X{userAgent},
level=%level, logger=%logger{36}, message=%msg%n
</pattern>
</encoder>
</appender>
Kafka๊ฐ ์ฅ์ ๋ฅผ ๋ง๋๋ฉด ๋ก๊ทธ๊ฐ ๋๊ธธ ์ ์๋ค.
๊ทธ๋ด ๋ ์ต์ํ์ ์์ ๋ง์ผ๋ก JSON ํ์ผ์ ๋์๋ค.
์ด Appender๋ Filebeat๊ฐ ๋ฐ๋ก ์ฝ์ ์ ์๋ ๊ตฌ์กฐํ ๋ก๊ทธ๋ฅผ logs/json/
์ ์๋๋ค.
Kafka๊ฐ ์ ์ ๋ฉ์ถ๋๋ผ๋, ์๋น์ค ๊ธฐ๋ก์ ์ฌ์ ํ ๋จ์์์ด โ๋จธ์ ์ด ๋จน์ ์ ์๋ ์๋ณธ ๋ฐ์ดํฐโ๊ฐ ๋ณด์กด๋๋ค.
<appender name="JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/json/app-log.json</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/json/app-log.%d{yyyy-MM-dd}.json</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp /><logLevel /><loggerName /><threadName /><message /><mdc />
</providers>
</encoder>
</appender>
์ด์ ๋ก๊ทธ์ ์ต์ข
๋ชฉ์ ์ง๋ ELK ์คํ ๊ฐ์ ์ค์ ์์ง ์์คํ
์ด๋ค.
๊ทธ๋์ KafkaAppender๋ฅผ ํตํด ๋ก๊ทธ๋ฅผ ๋ฐ๋ก app-logs ํ ํฝ
์ผ๋ก ๋ณด๋๋ค.
์ฌ๊ธฐ์๋ โ์ค์๊ฐ ์คํธ๋ฆผโ์ด ํต์ฌ์ด๋ผ ํ์ผ์ ๊ฑฐ์น์ง ์๊ณ ๋ฐ๋ก ํ ํฝ์ผ๋ก ํ๋ ค๋ณด๋ธ๋ค.
์ฑ๋ฅ ๋ฌธ์ ๋ฅผ ๋ง๊ธฐ ์ํด AsyncAppender๋ก ๊ฐ์ธ ์์ ์ฑ์ ๋์๊ณ , MDC์ ์์ธ ์ ๋ณด๊น์ง JSON์ ๋ด์ ๋ณด๋๋ค.
<appender name="KAFKA_JSON" class="com.github.danielwegener.logback.kafka.KafkaAppender">
<topic>app-logs</topic>
<encoder class="com.github.danielwegener.logback.kafka.encoding.LayoutKafkaMessageEncoder">
<layout class="net.logstash.logback.layout.LogstashLayout">
<includeMdc>true</includeMdc>
<includeException>true</includeException>
</layout>
</encoder>
<producerConfig>bootstrap.servers=${KAFKA_BOOTSTRAP_SERVERS}</producerConfig>
<producerConfig>acks=1</producerConfig>
</appender>
<appender name="ASYNC_KAFKA" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="KAFKA_JSON"/>
<queueSize>10000</queueSize>
<neverBlock>true</neverBlock>
</appender>
<logger name="kafkaLogger" level="INFO" additivity="false">
<appender-ref ref="ASYNC_KAFKA"/>
</logger>
๊ธฐ๋ณธ spring.log
๋ ์ฐ์ง ์์๋ค. ๋ด๊ฐ ์ค๊ณํ text / json ๋ ๊ฐ๋๋ง ์ ์งํ๋ค.
logs/
โโ text/
โ โโ client-requests.2025-09-09.log
โโ json/
โโ app-log.2025-09-09.json
์ฌ๊ธฐ์ ์ค์ํ ๊ฑด <file>
(ํ์ฌ ์ฐ๋ ํ์ผ)๊ณผ <fileNamePattern>
(๋กค๋ง ๊ฒฐ๊ณผ)์ด ๊ฐ์ ๋๋ ํฐ๋ฆฌ์ฌ์ผ ํ๋ค๋ ์ ์ด๋ค.
๋๋ ์ด๊ฑธ ์ค์๋ก ํ๋ฆฐ ์ ์ด ์์๊ณ , ์ด์ ๋ก๊ทธ๊ฐ ๋ด ํด๋์ ๊ฐ๋ฒ๋ ค์ ์์ง๊ธฐ๊ฐ ๊ผฌ์ธ ์ ์ด ์๋ค.
๊ทธ ๋ค๋ก๋ ๋ ๋จํธํ๊ฒ ํด๋๋ฅผ ์ชผ๊ฐฐ๋ค......
๊ฐ๋ฐ ํ๊ฒฝ์์๋ ์ฝ์ ์์ฃผ, ์ด์ ํ๊ฒฝ์์๋ ํ์ผ๊ณผ ์นดํ์นด๊ฐ ์ฃผ๋ ฅ์ผ๋ก ์ผ์๋ค.
์ด ์ฐจ์ด๋ฅผ xml ์์์ ๋ถ๋ฆฌํ ๊ฒ์ด ๋ฐ๋ก logback-spring.xml
์ ์ธ ์๋ฐ์ ์๋ ์ด์ ์๋ค.
<springProfile name="dev">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="TEXT_FILE"/>
</root>
</springProfile>
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="TEXT_FILE"/>
<appender-ref ref="JSON_FILE"/>
</root>
<logger name="kafkaLogger" level="INFO" additivity="false">
<appender-ref ref="ASYNC_KAFKA"/>
</logger>
</springProfile>
์ ๋ฆฌํ์๋ฉด, ๋๋ logback-spring.xml์ ์ ํํ ์ด์ ๋ถํฐ MDC ํ์คํ โ ๋ชฉ์ ๋ณ Appender ๋ถ๋ฆฌ โ ๊ฒฝ๋กยท๋กค๋ง ๋ช ํํ โ ํ๊ฒฝ ๋ถ๊ธฐ๊น์ง, ์ด์์ ์ ์ ๋ก ํ ์ค๊ณ ๊ณผ์ ์ ํ์ด๋๋ค.
๋ก๊ทธ๋ฐฑ ์ค์ ์ ๋จ์ํ โ์ฐ๋ ๋ฒโ์ด ์๋์๋ค.
๐ ๋๊ฐ, ์ด๋์, ๋ฌด์์ ์ํด ๋ก๊ทธ๋ฅผ ๋ณผ ๊ฒ์ธ์ง๊น์ง ๊ณ ๋ฏผํ๋ ๊ณผ์ ์ด์๋ค.