songining
article thumbnail

MDC ๋ž€?

MDC ๋Š” Mapped Diagnostic Context ์˜ ์•ฝ์ž๋กœ key์™€ value๋ฅผ ์ €์žฅํ•˜๋Š” Map ํ˜•์‹์œผ๋กœ slf4j, logback, log4j ๋“ฑ ๋กœ๊น… ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.

ThreadLocal ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ปจํ…์ŠคํŠธ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฉ€ํ‹ฐ ์“ฐ๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค. (๋ฉ€ํ‹ฐ ์“ฐ๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ๋Š” ๋กœ๊ทธ ๋ฉ”์‹œ์ง€์— ๊ฐ ์“ฐ๋ ˆ๋“œ์— ๋Œ€ํ•œ ๋กœ๊ทธ๊ฐ€ ์„ž์ด๊ธฐ ๋•Œ๋ฌธ)

์š”์ฒญ์ด ์–ด๋–ค ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ๋“ค์–ด์˜จ ๊ฒƒ์ธ์ง€ ์ €์žฅํ•˜๊ฑฐ๋‚˜ ์š”์ฒญ๋ณ„๋กœ ์‹๋ณ„์ž๋ฅผ ๋งŒ๋“ค์–ด ํŠน์ • ์š”์ฒญ์— ๋Œ€ํ•œ ๋กœ๊ทธ๋งŒ ์ถ”์ ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ฐธ๊ณ :

๋กœ๊ทธ์‹œ์Šคํ…œ #4-MDC๋ฅผ ์ด์šฉํ•˜์—ฌ ์“ฐ๋ ˆ๋“œ๋ณ„๋กœ ๋กœ๊ทธ ๋ถ„๋ฅ˜ํ•˜๊ธฐ


MDC ์ ์šฉํ•˜๊ธฐ

Spring Framework๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด AOP๋ฅผ ์ด์šฉํ•ด ์‰ฝ๊ฒŒ ์ ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-aop")

์š”์ฒญ๋ณ„๋กœ ์‹๋ณ„์ž(traceId)๋ฅผ ์ €์žฅํ•˜๊ณ  ์‹๋ณ„์ž๋ฅผ ํ†ตํ•ด ํŠน์ • ์š”์ฒญ์— ๋Œ€ํ•œ ๋กœ๊ทธ๋งŒ ๋ชจ์•„๋ณผ ์ˆ˜ ์žˆ๋‹ค.

์š”์ฒญ์— ๋Œ€ํ•œ ๊ณตํ†ต์  ์ฒ˜๋ฆฌ๋ฅผ ์›ํ•œ๋‹ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด AOP๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ Interceptor, Filter ๋“ฑ์„ ์‚ฌ์šฉํ•ด ๊ตฌํ˜„์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

@Aspect
@Component
class MdcAspect {

    @Around("@annotation(RequestMapping)")
    fun setMdcTraceId(joinPoint: ProceedingJoinPoint): Any? {
        try {
            val traceId = UUID.randomUUID().toString()
            MDC.put("traceId", traceId)
            return joinPoint.proceed()
        } finally {
            MDC.remove("traceId")
        }
    }

์•„๋ž˜์™€ ๊ฐ™์ด ๋กœ๊ทธ ์„ค์ • ํŒŒ์ผ์— MDC์— ์ €์žฅํ•œ key(traceId)๋ฅผ ์ฐ์–ด๋ณผ ์ˆ˜ ์žˆ๋‹ค.

<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} [traceId: %X{traceId}] - %msg%n</pattern>

 

๋น„๋™๊ธฐ ํ™˜๊ฒฝ์—์„œ๋Š” ์–ด๋–ป๊ฒŒ ์ ์šฉํ•  ์ˆ˜ ์žˆ์„๊นŒ?

ํ•˜์ง€๋งŒ ๋น„๋™๊ธฐ ํ™˜๊ฒฝ์ด๋ผ๋ฉด ์กฐ๊ธˆ ๋” ๊นŒ๋‹ค๋กœ์›Œ์ง„๋‹ค.

ThreadLocal์—์„œ ๋™์ž‘ํ•˜๋Š” MDC ํŠน์„ฑ์ƒ ํ•˜๋‚˜์˜ ์“ฐ๋ ˆ๋“œ์—์„œ ์—ฌ๋Ÿฌ๊ฐœ์˜ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๋น„๋™๊ธฐ ํ™˜๊ฒฝ์ด๋ผ๋ฉด MDC ์ •๋ณด๊ฐ€ ์œ ์ง€๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค.

์ฆ‰, ํ•˜๋‚˜์˜ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ๋•Œ ์“ฐ๋ ˆ๋“œ๊ฐ€ ๋ฐ”๋€Œ๊ฒŒ ๋˜๋ฉด MDC ์ •๋ณด๊ฐ€ ์œ ์ง€๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค.

ํ˜„์žฌ ํŒ€๋‚ด์—์„œ Spring WebFlux์™€ Armeria, Coroutine์„ ์‚ฌ์šฉํ•ด ๋น„๋™๊ธฐ ํ™˜๊ฒฝ์˜ ์„œ๋ฒ„๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ œ๋Œ€๋กœ ๋œ ๋กœ๊ทธ ์ถ”์ ์„ ์œ„ํ•ด์„œ๋Š” ์ด์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ–ˆ๋‹ค.

์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„ ๋‚ด๋ถ€์˜ ์Šค๋ ˆ๋“œ์—๋„ MDC ์ •๋ณด๋ฅผ ๋„˜๊ฒจ์ฃผ์–ด์•ผ ํ–ˆ๊ณ  ๋‹คํ–‰ํžˆ๋„ ์ฝ”ํ‹€๋ฆฐ์€ MDCContext๋ผ๋Š” ๋กœ๊น…์„ ์œ„ํ•œ ์ฝ”๋ฃจํ‹ด MDC ์ปจํ…์ŠคํŠธ๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์žˆ๋‹ค.

MDCContext

์ฝ”๋ฃจํ‹ด ์ปจํ…์ŠคํŠธ๊ฐ€ ์ „ํ™˜๋˜๋Š” ๋ถ€๋ถ„์— ์•„๋ž˜์™€ ๊ฐ™์ด MDCContext๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์ฝ”๋ฃจํ‹ด์œผ๋กœ ์ „๋‹ฌ๋˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.

withContext(Dispatchers.IO + MDCContext()){
	...
}

๋‚ด๋ถ€๋ฅผ ๋“ค์—ฌ๋‹ค๋ณด๋ฉด contextMap ์ด๋ผ๋Š” Map ํƒ€์ž…์˜ ์ž๋ฃŒํ˜• ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. ์ด ๋ณ€์ˆ˜๊ฐ€ ์ดˆ๊ธฐํ™”๋  ๋•Œ ์™ธ๋ถ€์˜ MDC ์ปจํ…์ŠคํŠธ๋ฅผ ๊ฐ€์ ธ์™€ ์ €์žฅํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

ThreadContextElement ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ฝ”๋ฃจํ‹ด ์ปจํ…์ŠคํŠธ๋กœ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋ฉฐ ThreadContext๊ฐ€ ๋ฐ”๋€”๋•Œ๋งˆ๋‹ค MDC ์ปจํ…์ŠคํŠธ๋ฅผ ๋ณต์‚ฌํ•ด์ฃผ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๋งŒ์•ฝ ์ฝ”๋ฃจํ‹ด ๊ธฐ๋ฐ˜์ด ์•„๋‹Œ Mono์™€ Flux ๊ธฐ๋ฐ˜ ๋ฆฌ์•กํ‹ฐ๋ธŒ ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด Reactor์—์„œ ์ œ๊ณตํ•˜๋Š” Context๋ฅผ ์‚ฌ์šฉํ•ด MDC๋ฅผ ๋ณต์‚ฌํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์กด์žฌํ•œ๋‹ค.

์ฐธ๊ณ :

How can the MDC context be used in the reactive Spring applications | Novatec


Armeria ServiceRequestContext ์ด์šฉํ•˜๊ธฐ

Armeria์—์„œ๋Š” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด Spring AOP์™€ ๊ฐ™์ด ์š”์ฒญ์˜ ์•ž๋’ค๋กœ ๊ณตํ†ต ๊ด€์‹ฌ์‚ฌ๋ฅผ ๋ถ„๋ฆฌํ•ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.

์‚ฌ๋‚ด์—์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ์˜ˆ์ „ ์„œ๋ฒ„๋“ค์€ Spring Framework๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— Armeria Decorator๋ฅผ ์‚ฌ์šฉํ•ด API ์š”์ฒญ์ด ๋“ค์–ด์˜ฌ ๋•Œ MDC์— traceId ์ •๋ณด๋ฅผ ์ €์žฅํ•ด์ฃผ์—ˆ๋‹ค.

Armeria๋Š” RequestContext๋ฅผ ์ œ๊ณตํ•œ๋‹ค. ํ˜ธ์ถœํ•˜๋Š” ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด ๊ณ ์œ ํ•œ ์ปจํ…์ŠคํŠธ๋ฅผ ๊ฐ€์ง€๋ฉฐ ์š”์ฒญ์— ๋Œ€ํ•œ ๋‹ค์–‘ํ•œ ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. ์ด ์ปจํ…์ŠคํŠธ์˜ id๋Š” ๊ณ ์œ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— traceId ๋กœ ์ ํ•ฉํ•˜๋‹ค.

ํ•˜๋‚˜์˜ ์š”์ฒญ์— ๋Œ€ํ•œ ์Šค๋ ˆ๋“œ๊ฐ€ ๋ฐ”๋€Œ๋”๋ผ๋„ ์ด ์ปจํ…์ŠคํŠธ๋Š” ์œ ์ง€๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ถ”์ ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ์žฅ์ ์ด ์กด์žฌํ•œ๋‹ค.

const val TRACE_ID = "traceId"

class MdcDecoratingService(delegate: HttpService) : SimpleDecoratingHttpService(delegate) {
    override fun serve(ctx: ServiceRequestContext, req: HttpRequest): HttpResponse {
        MDC.put(TRACE_ID, ctx.id().toString())

        val delegate = unwrap() as HttpService
        return delegate.serve(ctx, req)
    }
}

 

ํ•˜์ง€๋งŒ Armeria๋Š” MDC์— ์ง์ ‘ ๊ฐ’์„ ๋„ฃ์–ด์ฃผ์ง€ ์•Š์•„๋„ RequestContextExportingAppender ๋ผ๋Š” Logback Appender ๋ฅผ ์ œ๊ณตํ•œ๋‹ค. ์ด appender๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋”ฐ๋กœ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ฃผ์ง€ ์•Š์•„๋„ ์•ˆ์ „ํ•˜๊ฒŒ ๋ฒ”์šฉ์ ์œผ๋กœ ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ ์ •๋ณด๋ฅผ ์ถ”์ ํ•  ์ˆ˜ ์žˆ๋‹ค.

Armeria์—์„œ RequestContext๋Š” ์œ„์—์„œ ๋งํ–ˆ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ ๋น„๋™๊ธฐํ™˜๊ฒฝ์—์„œ๋„ ์š”์ฒญ๋ณ„๋กœ ๊ณ ์œ ํ•œ ์ปจํ…์ŠคํŠธ ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์“ฐ๋ ˆ๋“œ๊ฐ€ ์ˆ˜์‹œ๋กœ ๋ฐ”๋€๋‹ค ํ•˜๋”๋ผ๋„ ๊ทธ๋Œ€๋กœ ์š”์ฒญ ์ •๋ณด๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค. 

ํ˜„์žฌ ์ฒ˜๋ฆฌ์ค‘์ธ ์š”์ฒญ์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋“ค์„ ์ถ”์ถœํ•˜๊ฑฐ๋‚˜ ์ปค์Šคํ…€ํ•ด์„œ ์ •๋ณด๋“ค์„ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” Armeria์—์„œ ์•„์ฃผ ์ค‘์š”ํ•œ ์ปดํฌ๋„ŒํŠธ ์ค‘ ํ•˜๋‚˜๋‹ค.

 

RequestContextExportingAppender ๋Š” ํ˜„์žฌ RequestContext ์ •๋ณด๋ฅผ MDC๋กœ ๋‚ด๋ณด๋‚ด๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•œ appender์ด๋‹ค.

์•„๋ž˜์™€ ๊ฐ™์ด ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ด์ค€ ํ›„,

dependencies {
    implementation platform('com.linecorp.armeria:armeria-bom:1.24.3')

    ...
    implementation 'com.linecorp.armeria:armeria-logback'
}

logback.xml์—์„œ ํ•„์š”ํ•œ ์š”์ฒญ ์ •๋ณด๋ฅผ exportํ•ด ๋กœ๊ทธ ๋ฉ”์‹œ์ง€๋กœ ์ถœ๋ ฅํ•  ์ˆ˜ ์žˆ๋‹ค. 

    <appender name="LOG_FILE_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/logback_info.log</file>
        <encoder>
            <pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} %-5level --- [%thread][traceId=%X{traceId}] %logger{36} [%F:%M:%line] : %msg%n</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
    </appender>

 

appender์—์„œ ์š”์ฒญ๋ณ„ ๋กœ๊ทธ ์ •๋ณด ์ถ”์ ์„ ์œ„ํ•ด req.id๋ฅผ exportํ•˜์—ฌ traceId๋กœ ๋กœ๊ทธ ๋ฉ”์‹œ์ง€์— ๊ณตํ†ต์ ์œผ๋กœ ์ถœ๋ ฅํ•ด์ฃผ์—ˆ๋‹ค. 

    <appender name="RCEA" class="com.linecorp.armeria.common.logback.RequestContextExportingAppender">
        <export>traceId=req.id</export>
        <appender-ref ref="STDOUT" />
        <appender-ref ref="LOG_FILE_INFO"/>
    </appender>

 

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ตณ์ด ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋‚˜ AOP๋กœ MDC์— ์ผ์ผ์ด traceId๋ฅผ ์ €์žฅํ•ด์ฃผ์ง€ ์•Š์•„๋„ ๋กœ๊ทธ์— ํ•„์š”ํ•œ MDC ์ •๋ณด๋ฅผ ์ถœ๋ ฅํ•  ์ˆ˜ ์žˆ๋‹ค. 

๋˜ํ•œ ๋น„๋™๊ธฐ ํ™˜๊ฒฝ์—์„œ ๋งค๋ฒˆ ์ปจํ…์ŠคํŠธ๊ฐ€ ๋ฐ”๋€” ๋•Œ MDC๋ฅผ ๊ด€๋ฆฌํ•ด์ค„ ํ•„์š”๋„ ์—†์–ด ์ถ”๊ฐ€์ ์ธ ์ถ”์  ๋น„์šฉ์ด ๋“ค์ง€ ์•Š๋Š”๋‹ค.

 

์ฐธ๊ณ : 

https://easywritten.com/post/using-mdc-with-armeria/

 

์š”์•ฝ

1. armeria ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด RequestContextExportingAppender ๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์ž. 

2. aremria์™€ ๊ฐ™์€ ๋น„๋™๊ธฐ ์ง€์› ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์ง€ ์•Š๋‹ค๋ฉด WebFilter๋ฅผ ์‚ฌ์šฉํ•ด ๋ชจ๋“  ์š”์ฒญ์— MDC ์ •๋ณด๋ฅผ ์œ ์ง€ํ•˜๋„๋ก ์ ์šฉ

 

WebFilter์— ์ ์šฉํ•  ๋กœ์ง์€ ์•„๋ž˜ 2๊ฐ€์ง€ ์ค‘ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค.

  1) Reactor ๊ธฐ๋ฐ˜ : CoreSubscriber  ๋ฅผ ๊ตฌํ˜„ํ•ด onNext ๋ฉ”์„œ๋“œ์— MDC ์œ ์ง€๋˜๋„๋ก ์„ธํŒ…

  2) Coroutine ๊ธฐ๋ฐ˜ : ThreadContextElement ๋ฅผ ๊ตฌํ˜„ํ•ด ์ปจํ…์ŠคํŠธ์— MDC ๋ณต์‚ฌ ๋กœ์ง ์„ธํŒ… OR MDCContext ์‚ฌ์šฉ