[Spring] ThreadLocal๋ก ์ฑ๊ธํค ํจํด์ ๋์์ฑ ๋ฌธ์ ํด๊ฒฐ
by rlaehddnd0422์ด ์ ํฌ์คํ ์์ ํ๋ผ๋ฏธํฐ ๋๊ธฐํ๋ก ํ๋์ HTTP ์์ฒญ์ ๋ํด์ ๊ณ ์ ์ TraceId๋ฅผ ์ฌ์ฉํ๋๋ก ๊ณ ์ณ๋ณด์์ต๋๋ค.
๋๊ธฐํ์๋ ์ฑ๊ณตํ์ง๋ง, ์ฌ์ ํ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค.
- ๋ก๊ทธ๋ฅผ ์ถ๋ ฅํ๋ ๋ชจ๋ ๋ฉ์๋์ TraceId ํ๋ผ๋ฏธํฐ๋ฅผ ์ถ๊ฐํด์ฃผ์ด์ผ ํ๋ ๊ป๋๋ฌ์(ํ์ฅ, ์ ์ง๋ณด์์ ๋ํ ๋ถํธํจ)์ด ์์์ต๋๋ค.
- ๋ฟ๋ง ์๋๋ผ ๊ฐ ๊ณ์ธต์ try/catch๋ฌธ์ ์ฌ์ฉํ๊ฒ ๋์ด ์ฝ๋์ ๊ฐ๋ ์ฑ๊น์ง ๋จ์ด์ก์ด์.
- ์ด ์ธ์๋ ์ฒซ ์์ฒญ์ ๋ํด์๋ง begin์ ํธ์ถํ๊ณ , ์ด์ด๋ฐ์ ๊ณ์ธต์์๋ beginSync๋ฅผ ์คํํ๋๋ก ์ค์ ํด์ผ ํ๊ธฐ ๋๋ฌธ์, ๋น์ทํ ๊ธฐ๋ฅ์ ์ฝ๋์ ํ์ฅ๊น์ง ์ด๋ฃจ์ด์ ธ ์ข์ ์ฝ๋๋ผ๊ณ ๋ณด๊ธฐ ์ด๋ ต์ต๋๋ค.
์ด๋ฒ์๋ ๋ค๋ฅธ๋ฐฉ๋ฒ์ ๋์
ํด๋ณด๋๋ก ํฉ์๋ค. ๊ฒฐ๋ก ์ ์ผ๋ก ๋งํ์๋ฉด ThreadLocal์ ์ฌ์ฉํฉ๋๋ค.
ํ์ง๋ง ์ฒ์๋ถํฐ ThreadLocal์ ๋์
ํ์ง ์๊ณ ,
- TraceId๋ฅผ ํด๋์ค ๋ด์ ์ธ์คํด์ค์ ํ๋์ ๋ด์์ ์ฌ์ฉํ๋ ํ๋ ๋ก๊ทธ ์ถ์ ๊ธฐ๋ฅผ ๋ง๋ค์ด ๋ณด๊ณ ,
- ์ด ๋ ๋ฐ์ํ๋ ๋ฌธ์ ๋ฅผ ํ์ธํด๋ณด๊ณ ,
- ThreadLocal์ ์ ์ฉ์์ผ ๋ฐ์ํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
๊ฐ์ ๊ธฐ๋ฅ์ ํ๋ ๋ ๊ฐ์ง(Thread๋ฅผ ์ฌ์ฉํ ๋ก๊ทธ ์ถ์ ๊ธฐ, ThreadLocal์ ์ฌ์ฉํ ๋ก๊ทธ ์ถ์ ๊ธฐ) ๋ก๊ทธ ์ถ์ ๊ธฐ๋ฅผ ๋์ ํ ์์ ์ด๋ฏ๋ก, ๋จผ์ ํ๋กํ ํ์ ์ผ๋ก ์ธํฐํ์ด์ค๋ถํฐ ์์ฑํ๋๋ก ํฉ์๋ค.
public interface LogTrace {
TraceStatus begin(String msg);
void end(TraceStatus status);
void exception(TraceStatus status, Exception e);
}
- ์ด ์ ๋ก๊ทธ์ถ์ ๊ธฐ์ ๋ค๋ฅด๊ฒ, beginSync์ begin์ ๊ตฌ๋ถ์ง์ง ์๊ณ begin ๋ด๋ถ์์ ์ฒซ ํธ์ถ์ธ์ง ๊ฒ์ฌํ ์์
์ธ์คํด์ค์ ํ๋์ ๋๊ธฐํ - FieldLogTrace
@Slf4j
public class FieldLogTrace implements LogTrace {
private static final String START_PREFIX = "-->";
private static final String COMPLETE_PREFIX = "<--";
private static final String EX_PREFIX = "<X-";
private TraceId traceIdHolder; // ์ถ๊ฐ
์ด ์ ๋ก๊ทธ ์ถ์ ๊ธฐ์ ๋ฌ๋ฆฌ TraceId๋ฅผ ๋ด์ traceHolder๋ฅผ ์ถ๊ฐํ์์ต๋๋ค. ์ด๋ ๊ฒ ํ๊ฒ ๋๋ฉด ํ๋ผ๋ฏธํฐ๋ก ๋๊ธธ ํ์๊ฐ ์์ด์ง๋๋ค.
์๋์์ ์ฝ๋๋ฅผ ์ดํด๋ณด๊ธด ํ๊ฒ ์ง๋ง, ๋จผ์ ์ค๋ช
ํ์๋ฉด ์ด๋ ์ต๋๋ค.
- ๋ก๊ทธ ์ถ์ ๊ธฐ์ ์์์ธ ๊ฒฝ์ฐ ์๋ก์ด TraceId๋ฅผ ์์ฑ,
- ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ traceHolder์์ createNextId()๋ก ๋๊ธฐํ๋ TraceId๋ฅผ ์ฌ์ฉ
FieldLogTrace.begin
@Override
public TraceStatus begin(String message) {
syncTraceId();
TraceId traceId = traceIdHolder;
Long startTimeMills = currentTimeMillis();
log.info("[{}] {}{}", traceId.getId(), addSpace(START_PREFIX, traceId.getLevel()), message);
return new TraceStatus(traceId, startTimeMills, message);
}
private void syncTraceId() {
if (traceIdHolder == null) {
traceIdHolder = new TraceId();
} else {
traceIdHolder = traceIdHolder.createNextId();
}
}
์์์ ํ๋ ์ค๋ช
๊ทธ๋๋ก์
๋๋ค. ์ค์ํ ๋ถ๋ถ์ synTraceId()์
๋๋ค.
syncTraceId
- ๋ก๊ทธ ์ถ์ ๊ธฐ์ ์์์ธ ๊ฒฝ์ฐ ์๋ก์ด TraceId๋ฅผ ์์ฑ,
- ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ traceHolder์์ createNextId()๋ก ๋๊ธฐํ๋ TraceId๋ฅผ ์ฌ์ฉ
์ฌ๊ธฐ์ ์์ฑ๋๊ฑฐ๋ ๋๊ธฐํ๋ TraceId๋ฅผ ์ฌ์ฉํจ์ผ๋ก์จ ๋๊ธฐํ ๋ฌธ์ ๋ฟ๋ง ์๋๋ผ ๋ถํ์ํ ์ฝ๋ํ์ฅ ( begin(), beginSync() ๊ตฌ๋ถ ) ๊น์ง ํด๊ฒฐํ์ต๋๋ค.
FieldLogTrace - end, exception
public void end(TraceStatus status) {
complete(status, null);
}
public void exception(TraceStatus status, Exception e) {
complete(status, e);
}
์ด์ ๊ณผ ๋์ผํ์ง๋ง complete ์ดํ ์ฒ๋ฆฌ๊ฐ ์กฐ๊ธ ์์ ๋์์ต๋๋ค.
private void complete(TraceStatus status, Exception e) {
Long stopTimeMs = currentTimeMillis();
long resultTimeMs = stopTimeMs - status.getStartTimeMs();
TraceId traceId = status.getTraceId();
if (e == null) {
log.info("[{}] {}{} time={}ms", traceId.getId(),
addSpace(COMPLETE_PREFIX, traceId.getLevel()), status.getMessage(),
resultTimeMs);
} else {
log.info("[{}] {}{} time={}ms ex={}", traceId.getId(),
addSpace(EX_PREFIX, traceId.getLevel()), status.getMessage(), resultTimeMs,
e.toString());
}
releaseTraceId();
}
private void releaseTraceId() {
if (traceIdHolder.isFirstLevel()) {
traceIdHolder = null; // 1 -> destroy
} else {
traceIdHolder = traceIdHolder.createPreviousId(); // 1 -> 2 -> 3(complete) -> 2 -> 1
}
}
releaseTraceId()๊ฐ ์ถ๊ฐ๋์์ต๋๋ค.
releaseTraceId()
- ์ธ์คํด์ค ํ๋๋ก ์ฒ๋ฆฌํด์ฃผ์๊ธฐ ๋๋ฌธ์ ์์ฒญ์ด ๋๋๊ฒ ๋๋ฉด ์ด ์ ๊ณ์ธต์๊ฒ traceHolder์ ํ์ฌ TraceId์ ์ด์ ๊ฐ์ ๋๊ฒจ์ฃผ์ด์ผ ํฉ๋๋ค. ( level์ ํ๋ ๊ฐ์ )
- ๋ง์ฝ ์ต์ด ํธ์ถ(level=0)์ด๋ฉด ๋ด๋ถ์์ ๊ด๋ฆฌํ๋ traceId๋ฅผ ์ ๊ฑฐ
- syncTraceId()์ ์ญ๋ก์ง
ํ
์คํธ ์ฝ๋๋ฅผ ๋ง๋ค์ด ํ
์คํธ ํด๋ณด๋ฉด ํธ๋์ญ์
ID๋ ์ผ์น, level์ ํตํ ๊น์ด ํํ๋ ์ ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
๋ก์ง์ ๋์ , ๋์์ฑ ๋ฌธ์ ํ์ธ
์ฐ์ ์์์ ๋ง๋ FieldLogTrace๋ฅผ ๋น์ผ๋ก ๋ฑ๋กํด์ค์๋ค.
์ ์๋์ผ๋ก ๋ฑ๋กํ์๊น ?
: ์ด ์ถ์ ๊ธฐ ๋ง๊ณ ๋ ์ถํ์ ThreadLocal์ ์ฌ์ฉํ ๋ก๊ทธ ์ถ์ ๊ธฐ๋ ๊ตฌํํ ์์ ์ธ๋ฐ ๊ฐ์ ๋ผ์ฐ๊ธฐ ์ฝ๊ฒ ํ๊ธฐ ์ํจ.
/ ํ๋กํ ํ์ ์ด ์ ์ธ๋๊ณ ๊ตฌํ์ฒด๊ฐ ์ฌ๋ฌ ๊ฐ๋ผ๋ฉด ์๋ ๋น์ผ๋ก ๋ฑ๋กํ๋ ๊ฒ์ด ์ข์ ๊ฒ ๊ฐ๋ค.
@Configuration
public class LogTraceConfig {
@Bean
public LogTrace logTrace() {
return new ThreadLocalLogTrace();
}
}
Controller, Service, Repository ์ชฝ์ ์ด ์ ๋ก๊ทธ ์ถ์ ๊ธฐ (ver2)์์ ์ถ์ ๊ธฐ๋ง ๊ฐ์๋ผ์์ฃผ๋ฉด ๋๊ธฐ ๋๋ฌธ์ ๋ฐ๋ก ์ฝ๋๋ฅผ ์ธ๊ธํ์ง ์๊ฒ ์ต๋๋ค.
// private final HelloTraceV2 trace;
private final LogTrace trace;
์ด์ HTTP ์์ฒญ์ ํตํด ํ
์คํธ๋ฅผ ํด๋ณด๊ฒ ์ต๋๋ค.
http://localhost:8080/v3/request?itemId=3
- ๋์์ ์ฌ๋ฌ ์ฌ์ฉ์๊ฐ ์์ฒญํ๋ฉด ์ฌ๋ฌ ์ฐ๋ ๋๊ฐ ๋์์ ์ ํ๋ฆฌ์ผ์ด์ ๋ก์ง์ ํธ์ถํ๊ฒ ๋ฉ๋๋ค.
- ์ด๋ฅผ ํ ์คํธ ํด๋ณด๊ธฐ ์ํด ์์ฒญ์ ๋น ๋ฅด๊ฒ ์ฌ๋ฌ๋ฒ ๋ณด๋ด๋ด ์๋ค.
ํธ๋์ญ์
ID๋ ๋์ผํ๋ฐ Level๋ ์ด์ํฉ๋๋ค. ํ
์คํธ ์ฝ๋์์๋ ๋ฌธ์ ๊ฐ ์์๋๋ฐ ๋ญ๊ฐ ๋ฌธ์ ์ผ๊น์.
์ด ๋ฌธ์ ๋ ๋์์ฑ ๋ฌธ์ ์
๋๋ค.
๋์์ฑ ๋ฌธ์
- ์ฑ๊ธํค ๋น์์ ๋ฐ์ํ๋ ๋ฌธ์ ๋ก, ์ฌ๋ฌ ์ฐ๋ ๋๊ฐ ํ๋์ ์ฑ๊ธํค ๋น์ ๋์์ ์ ๊ทผํ๋ ๊ฒฝ์ฐ ๋ฐ์ํฉ๋๋ค.
- ์ด์ฒ๋ผ 1)์ฌ๋ฌ ์ฐ๋ ๋๊ฐ ๋์์ ๊ฐ์ ์ธ์คํด์ค์ ํ๋ ๊ฐ์ ๋ณ๊ฒฝํ๊ฑฐ๋, 2)ํธ์ถ์ด ์ฒ๋ฆฌ๋๋ ๋์ค ๋ค๋ฅธ ํธ์ถ์ด ์์ฒญ๋ ๋ ๊ฐ์ ์ธ์คํด์ค์ ํ๋๊ฐ์ ์ฐธ์กฐํ ๋ ๋ฐ์ํ๋ ๋ฌธ์ ๋ฅผ ๋์์ฑ ๋ฌธ์ ๋ผ๊ณ ํฉ๋๋ค.
- ์ด๋ฐ ๋์์ฑ ๋ฌธ์ ๋ ์ฌ๋ฌ ์ฐ๋ ๋๊ฐ ๊ฐ์ ์ธ์คํด์ค์ ํ๋์ ์ ๊ทผํด์ผ ํ๊ธฐ ๋๋ฌธ์ ํธ๋ํฝ์ด ์ ์ ์ํฉ์์๋ ํ๋ฅ ์ ์ ๋ํ๋์ง ์๊ณ , ํธ๋ํฝ์ด ์ ์ ๋ง์์ง ์ ๋ก ์์ฃผ ๋ฐ์ํฉ๋๋ค.
- ํนํ ์คํ๋ง ๋น ์ฒ๋ผ ์ฑ๊ธํค ๊ฐ์ฒด์ ํ๋๋ฅผ ๋ณ๊ฒฝํ๋ฉฐ ์ฌ์ฉํ ๋ ์ด๋ฌํ ๋์์ฑ ๋ฌธ์ ๋ฅผ ์กฐ์ฌํด์ผ ํฉ๋๋ค.
ThreadLocal ๋์ ํ์ฌ ๋์์ฑ ๋ฌธ์ ํด๊ฒฐ
๋์์ฑ ๋ฌธ์ ๋ ๊ฐ์ ์ธ์คํด์ค์ ํ๋( ์ฃผ๋ก ์ฑ๊ธํค์์ ์์ฃผ ๋ฐ์ )๋ static ๊ฐ์ ๊ณต์ฉ ํ๋์ ์ ๊ทผํ ๋ ๋ฐ์ํฉ๋๋ค.
๊ฐ์ ์ฝ๊ธฐ๋ง ํ๋ฉด ๋ฌธ์ ๊ฐ ์์ง๋ง, ๊ฐ์ ๋ณ๊ฒฝํ ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค.
์ฑ๊ธํค ๊ฐ์ฒด์ ํ๋๋ฅผ ์ฌ์ฉํ๋ฉด์ ๋์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํ ๋ฐฉ๋ฒ์ผ๋ก๋ ThreadLocal์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค.
ThreadLocal์ด๋?
- ํด๋น ์ฐ๋ ๋๋ง ์ ๊ทผํ ์ ์๋ ํน๋ณํ ์ ์ฅ์์ ๋๋ค.
- ์ฐ๋ ๋ ๋ก์ปฌ์ ์ฌ์ฉํ๋ฉด ๊ฐ ์ฐ๋ ๋๋ง๋ค ๋ณ๋์ ๋ด๋ถ ์ ์ฅ์๋ฅผ ์ ๊ณตํ๋ฏ๋ก, ๊ฐ์ ์ธ์คํด์ค์ ์ฐ๋ ๋ ๋ก์ปฌ ํ๋์ ์ ๊ทผํด๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์์ต๋๋ค.
ThreadLocal ์ฌ์ฉ๋ฒ
- ๊ฐ ์ ์ฅ : ThreadLocal.set()
- ๊ฐ ์กฐํ : ThreadLocal.get()
- ๊ฐ ์ ๊ฑฐ : ThreadLocal.remove() ( ์ฃผ์ : ์ฐ๋ ๋๊ฐ ์ข ๋ฃ๋๊ธฐ ์ง์ ๋ฐ๋์ ํธ์ถ๋์ด์ผ ํจ ! )
ThreadLocalLogTrace ์์ฑ
@Slf4j
public class ThreadLocalLogTrace implements LogTrace{
private static final String START_PREFIX = "-->";
private static final String COMPLETE_PREFIX = "<--";
private static final String EX_PREFIX = "<X-";
private ThreadLocal<TraceId> traceIdHolder = new ThreadLocal<>(); // traceId ๋๊ธฐํ, ๋์์ฑ ์ด์ X
- ํ๋ ๋ก๊ทธ ์ถ์ ๊ธฐ์์ ์ธ์คํด์ค ํ๋๋ฅผ ์ฌ์ฉํ ๊ฒ๊ณผ ๋ฌ๋ฆฌ, ThreadLocal์ ๋์ ํ์์ต๋๋ค.
- ์ฐธ๊ณ ๋ก ThreadLocal์ ThreadLocal<T> ์ด๋ฏ๋ก ์ด๋ค ํ์ ์ด๋์ง ๋ค์ด์ฌ ์ ์์ต๋๋ค.
๊ทธ ์ธ ๋ณ๊ฒฝ์
@Override
public TraceStatus begin(String message) {
syncTraceId();
TraceId traceId = traceIdHolder.get();
private void syncTraceId() {
TraceId traceId = traceIdHolder.get();
if (traceId == null) {
traceIdHolder.set(new TraceId());
} else {
traceIdHolder.set(traceId.createNextId());
}
}
private void releaseTraceId() {
TraceId traceId = traceIdHolder.get();
if (traceId.isFirstLevel()) {
traceIdHolder.remove();
} else {
traceIdHolder.set(traceId.createPreviousId());
}
}
์กฐํํ ๋์๋ get(), ์ ์ฅํ ๋์๋ set()์ ์ฌ์ฉํฉ๋๋ค. ์ฃผ์ํด์ผ ํ ์ ์ ์๊น ์ธ๊ธํ๋ฏ์ด ์ฐ๋ ๋ ์ข
๋ฃ ์์ (traceId.isFirstLevel) ์๋ remove()๋ฅผ ํธ์ถํ์ฌ ์ฐ๋ ๋ ๋ก์ปฌ์ ์ ์ฅ๋ ๊ฐ์ ์ ๊ฑฐํด์ฃผ์ด์ผ ํฉ๋๋ค.
ํ
์คํธ๋ฅผ ํด๋ณด๋ฉด
์ ๊ฒฐ๊ณผ์ ํธ๋์ญ์
์์ด๋๋ฅผ ๊ตฌ๋ถ์ง์ด์ ํ์ธํด๋ณด๋ฉด ๋์์ฑ ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
ThreadLocal ์ฌ์ฉ ์ ์ฐ๋ ๋ ์ข ๋ฃ ์์ ์ remove()๋ฅผ ํธ์ถํด์ฃผ์ด์ผ ํ๋ ์ด์
์ฐ๋ ๋ ๋ก์ปฌ์ ๊ฐ์ ์ฌ์ฉ ํ ์ ๊ฑฐํ์ง ์๊ณ ๊ทธ๋ฅ ๋๋ฉด WAS(ํฐ์บฃ)์ฒ๋ผ ์ฐ๋ ๋ ํ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์ ์ฌ๊ฐํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
CASE)
- A๋ผ๋ ์ ์ ๊ฐ ์ฐ๋ ๋ ๋ก์ปฌ์์ ์ฐ๋ ๋๋ฅผ ์ฌ์ฉํด ๊ฐ์ ์ ์ฅํ ์ดํ remove()๋ฅผ ํธ์ถํ์ง ์์ ์ํฉ
- B๋ผ๋ ์ ์ ๊ฐ ๊ฐ์ ์์ฒญ(๊ฐ ์กฐํ๋ผ๊ณ ๊ฐ์ )์ ํจ.
- B ์ ์ ๊ฐ ์ ์ ๊ฐ A๊ฐ ์ฌ์ฉํ๋ ์ฐ๋ ๋๋ฅผ ํ ๋น๋ฐ์์.
- B๋ A๊ฐ ์ฐ๋ ๋ ๋ก์ปฌ์ ์ง์ฐ์ง ์๊ณ ๋จ๊ฒจ๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐ๊ฒ ๋จ.
์ฐธ๊ณ ๋ก ์ฐ๋ ๋๋ ์ข ๋ฃ์์ ์ ์ฐ๋ ๋ ํ์ ๋ค์ ๋ฐํ๋ฉ๋๋ค.
์ด ๋ ๋์์ฑ ๋ฌธ์ ๊ฐ ์ด์ ๊ณผ ๋์ผํ๊ฒ ๋ฐ์ํ๊ฒ ๋ฉ๋๋ค. ์ด๋ฌ๋ฉด ThreadLocal์ ๋์
ํ๊ฒ ๋ง์งฑ ๋๋ฃจ๋ฌต
์ฐ๋ ๋ ๋ก์ปฌ์ ์ฌ์ฉํ ๋๋ ๋ฐ๋์ ๋ฐ๋์ ๊ผญ ์ข
๋ฃ์์ ์ remove()๋ฅผ ํธ์ถํ๋๋ก ํฉ์๋ค.
<์ฐธ๊ณ ์๋ฃ>
'๐ Backend' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
๋ธ๋ก๊ทธ์ ์ ๋ณด
Study Repository
rlaehddnd0422