slf4j, logback, log4j2 ๋ฑ์ ๋ก๊ทธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ ๊ณตํ๋ Map ํ์์ผ๋ก ํด๋ผ์ด์ธํธ์ ํน์ง์ ์ธ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ธฐ ์ํ ๋งค์ปค๋์ฆ์ด๋ค. MDC๋ key / value ์ ์ฅ์๋ฅผ ์ง์ํ๊ณ ์์ผ๋ฉฐ, ์ด ์ ์ฅ์๋ ThreadContext์ ์์กดํ๋ค.
MDC๊ฐ ๋์ ๋ ์ด์
์ฌ๋ฌ ์ค๋ ๋๊ฐ ์คํ๋๋ ์๋ฒ ํ๊ฒฝ์์ ๋ก๊ทธ๋ฅผ ๋จ๊ธธ ๋, ์์๊ฐ ๋ณด์ฅ๋์ง ์๋๋ค. ์๋ฅผ ๋ค์ด ๋์์ 3๋ช ์ ํด๋ผ์ด์ธํธ๊ฐ ์์ฒญํ๋ค๊ณ ๊ฐ์ ํ๋ฉด, ์ด๋ฌํ ์ค๋ ๋์ ์์ฒญ ์ฒ๋ฆฌ๋ ์์ฐจ์ ์ผ๋ก ์ด๋ค์ง์ง ์์ผ๋ฉฐ ์ฌ์ง์ด๋ ์ธ๋ถ API ํธ์ถ ๊ฐ์ ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ์ํด ๋ค๋ฅธ ์ค๋ ๋๋ฅผ ์ฌ์ฉํ๊ธฐ๋ ํ๋ค. ๊ทธ๋์ ํน์ ์์ฒญ์ ๋ํ ๋ก๊ทธ๋ฅผ ์ถ์ ํ๊ธฐ๊ฐ ์ด๋ ต๋ค.
์๋ฅผ ๋ค์ด ํ์ฌ ๋ฐ์ธ๊ถ ํ๋ก์ ํธ์์ Spring Batch Job์ ๋ํ ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ๊ณ ์๋๋ฐ, ์ด ์ฌ์ด์ ํด๋ผ์ด์ธํธ ์์ฒญ์ด ์ค๋ฉด ๋ก๊ทธ ์์๊ฐ ๋ณด์ฅ๋์ง ์๊ธฐ ๋๋ฌธ์ Spring Batch์ ๊ด๋ จ๋ ๋ก๊ทธ๋ฅผ ์ ๋ง์ ๋ก๊ทธ๋ค ์ฌ์ด์์ ์ฐพ์์ผํ๋ค.
@Slf4j
@Component
public class CourseSyncJobListener implements JobExecutionListener {
@Override
public void beforeJob(JobExecution jobExecution) {
JobParameters params = jobExecution.getJobParameters();
String runId = params.getString("run.id");
log.info("[CourseSyncJob ์์] run-id: {} | startTime: {}", runId, jobExecution.getStartTime());
}
@Override
public void afterJob(JobExecution jobExecution) {
JobParameters params = jobExecution.getJobParameters();
String runId = params.getString("run.id");
BatchStatus status = jobExecution.getStatus();
Duration duration = Duration.between(jobExecution.getStartTime(), jobExecution.getEndTime());
log.info("[CourseSyncJob ์ข
๋ฃ] run-id: {} | endTime: {}", runId, jobExecution.getEndTime());
log.info("์ํ: {}", status);
log.info("์ฒ๋ฆฌ ์๊ฐ: {}์๊ฐ {}๋ถ {}์ด", duration.toHoursPart(), duration.toMinutesPart(), duration.toSecondsPart());
jobExecution.getStepExecutions().forEach(stepExecution ->
log.info("Step [{}]: Read={}, Write={}, Skip={}",
stepExecution.getStepName(),
stepExecution.getReadCount(),
stepExecution.getWriteCount(),
stepExecution.getSkipCount()
));
}
}
๊ทธ๋์ MDC๋ผ๋ ๊ฐ๋
์ด ๋์๊ณ , ์์ฒญ๋ง๋ค ๊ณ ์ ๊ฐ์ ํ ๋นํ๊ณ , ์ด๋ฅผ ํตํด ๋ก๊ทธ๋ฅผ ๊ด๋ฆฌํ๊ณ ์ ๋ฑ์ฅํ ๊ฒ์ด๋ค. ์ฆ, ๋ก๊ทธ์ ๋งฅ๋ฝ์ ๋ถ์ฌํ๊ธฐ ์ํจ์ด๋ค!
๊ทธ๋ ๋ค๋ฉด, ํ๋ก์ ํธ์์๋?
์์ ๋ฐ์ธ๊ถ ์ฝ๋ ์์์์๋ MDC๋ฅผ ์ฌ์ฉํ๋ ์ด์ ๊ฐ ์กฐ๊ธ ํ๋ ค์ง๋ค. ์๋๋ฉด, ํด๋น Batch Job์ ๋จ์ผ ์ค๋ ๋๋ก ์คํ๋๊ณ ๋งค์ผ ์๋ฒฝ 2์์ ๋ฑ ํ ๋ฒ ์คํ๋๋ค. ๋๊ตฐ๋ค๋ `CourseSyncJobListener`๋ฅผ ์์ํ๋ `CourseSyncJobListener`๊ฐ ๋ด๋ถ์ ์ผ๋ก Batch Job Id๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
@Slf4j
@Component
public class CourseSyncJobListener implements JobExecutionListener {
@Override
public void beforeJob(JobExecution jobExecution) {
JobParameters params = jobExecution.getJobParameters();
String runId = params.getString("run.id");
log.info("[CourseSyncJob ์์] run-id: {} | startTime: {}", runId, jobExecution.getStartTime());
}
@Override
public void afterJob(JobExecution jobExecution) {
JobParameters params = jobExecution.getJobParameters();
String runId = params.getString("run.id");
BatchStatus status = jobExecution.getStatus();
Duration duration = Duration.between(jobExecution.getStartTime(), jobExecution.getEndTime());
log.info("[CourseSyncJob ์ข
๋ฃ] run-id: {} | endTime: {}", runId, jobExecution.getEndTime());
log.info("[run-id: {}] ์ํ: {}", runId, status);
log.info("[run-id: {}] ์ฒ๋ฆฌ ์๊ฐ: {}์๊ฐ {}๋ถ {}์ด", runId, duration.toHoursPart(), duration.toMinutesPart(), duration.toSecondsPart());
jobExecution.getStepExecutions().forEach(stepExecution ->
log.info("[run-id: {}] Step [{}]: Read={}, Write={}, Skip={}",
runId,
stepExecution.getStepName(),
stepExecution.getReadCount(),
stepExecution.getWriteCount(),
stepExecution.getSkipCount()
));
}
}
์ฝ๋๋ก ๋ํ๋ด๋ฉด ์ด๋ฐ ๋ฐฉ์์ผ๋ก ๋ก๊ทธ์ ๋ํ ๊ณ ์ ๊ฐ์ ๊ด๋ฆฌํ ์ ์๋ ๊ฒ ์๋๊ฐ? ํ์ง๋ง, ์ฌ๊ธฐ์ ๋ด๊ฐ ๊ฐ๊ณผํ๊ฒ ์์๋ค. ํ์ฌ ํ๋ก์ ํธ์์๋ Batch Job์ ์๋ ์คํํ๋ ๋ฐฉ๋ฒ๋ฟ ์๋๋ผ ์๋ ํธ์ถ ํ ์ ์๋ API๊ฐ ์๋ค๋ ์ ์ด๋ค. ๊ทธ๋ฆฌ๊ณ ํด๋น API๋ ๋ด๋ถ์ ์ผ๋ก `CourseSyncService`๋ฅผ ํธ์ถํ๋ค.
@Slf4j
@Component
@RequiredArgsConstructor
public class CourseSyncService {
private final JobLauncher jobLauncher;
private final Job courseSyncJob;
@Scheduled(cron = "0 0 2 * * *", zone = "Asia/Seoul")
public void runScheduledCourseSyncJob() {
try {
log.info("CourseSyncJob ์๋ ์์");
JobParameters jobParameters = new JobParametersBuilder()
.addString("run.id", "scheduled-" + System.currentTimeMillis())
.toJobParameters();
jobLauncher.run(courseSyncJob, jobParameters);
} catch (Exception e) {
log.warn("[EXCEPTION] CourseSyncJob ์๋ ์คํ ์ค ์์ธ ๋ฐ์", LogContent.exception(e));
}
}
@Async("asyncExecutor")
public void runCourseSyncJob() {
try {
log.info("CourseSyncJob ์๋ ์์");
JobParameters jobParameters = new JobParametersBuilder()
.addString("run.id", "manual-" + System.currentTimeMillis())
.toJobParameters();
jobLauncher.run(courseSyncJob, jobParameters);
} catch (Exception e) {
log.warn("[EXCEPTION] CourseSyncJob ์๋ ์คํ ์ค ์์ธ ๋ฐ์", LogContent.exception(e));
}
}
}
์ฌ๊ธฐ์ ์๋ ์์๊ณผ ์๋ ์์์ ๊ตฌ๋ถํ๊ธฐ ์ํด ์ง์ ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ๊ณ ์๋ค. ํ์ง๋ง, `CourseSyncJobListener`๊ฐ ์ง์ ๋ก๊ทธ๋ฅผ ๋ค์ ๋จ๊ธฐ๊ณ ์์ด ์ค๋ณต๋ ๋ก๊ทธ์ผ ๋ฟ ์๋๋ผ ์๋์ธ์ง ์๋์ธ์ง ๋ถ๊ฐํ ์๊ฐ ์๋ค. ๊ทธ๋์ ๊ธฐ์กด Listener์ ์ง์ ๋ก๊ทธ๋ฅผ ์ง์ฐ๊ณ , `CourseSyncService`์์ run.id์ ํจ๊ป ๋จ๊ธธ ํ์๊ฐ ์์๋ค. ์ด๋ฅผ ์ํด run.id์ ๋ค์ด๊ฐ๋ ๋ด์ฉ์ ๋ณ์๋ก ๋ถ๋ฆฌํ๊ณ , Job ํ๋ผ๋ฏธํฐ๋ก ๋๊ฒจ๋ ๋์ง๋ง ๋์ค์ ๋ค๋ฅธ ์ฐ๋๋๋ ๋ถ๋ถ๋ค์ด ์๊ธฐ๋ฉด ๋๊ฐ์ด ํ๋ผ๋ฏธํฐ๋ก ๋๊ฒจ์ค์ผ ํ๋ค! ์ฌ์ง์ด ๋งค๋ฒ ๋ก๊ทธ ์์ run.id๋ฅผ ๋จ๊ฒจ์ผ ํ๋ ๊ท์ฐฎ์๊น์ง ์๋ค. ๊ทธ๋์ ์ฐจ๋ผ๋ฆฌ MDC๋ฅผ ์ด์ฉํ๋๊ฒ ํจ์ฌ ์ข๋ค๊ณ ์๊ฐํ๋ค.
@Slf4j
@Component
@RequiredArgsConstructor
public class CourseSyncService {
private final JobLauncher jobLauncher;
private final Job courseSyncJob;
@Scheduled(cron = "0 0 2 * * *", zone = "Asia/Seoul")
public void runScheduledCourseSyncJob() {
String runId = "scheduled-" + System.currentTimeMillis();
MDC.put("run.id", runId);
try {
log.info("CourseSyncJob ์๋ ์์");
JobParameters jobParameters = new JobParametersBuilder()
.addString("run.id", runId)
.toJobParameters();
jobLauncher.run(courseSyncJob, jobParameters);
} catch (Exception e) {
log.warn("[EXCEPTION] CourseSyncJob ์๋ ์คํ ์ค ์์ธ ๋ฐ์", LogContent.exception(e));
} finally {
MDC.remove("run.id");
}
}
@Async("asyncExecutor")
public void runCourseSyncJob() {
String runId = "manual-" + System.currentTimeMillis();
MDC.put("run.id", runId);
try {
log.info("CourseSyncJob ์๋ ์์");
JobParameters jobParameters = new JobParametersBuilder()
.addString("run.id", runId)
.toJobParameters();
jobLauncher.run(courseSyncJob, jobParameters);
} catch (Exception e) {
log.warn("[EXCEPTION] CourseSyncJob ์๋ ์คํ ์ค ์์ธ ๋ฐ์", LogContent.exception(e));
} finally {
MDC.remove("run.id");
}
}
}
๊ทธ๋์ CourseSyncService๋ฅผ ์ด๋ ๊ฒ ๊ฐ์ ํ๋ค. ์ฌ๊ธฐ์๋ runId๋ฅผ ์์ฑํ๊ณ MDC์ ๋ฃ๋๋ค. ๊ทธ๋ผ ํด๋น ์ค๋ ๋๊ฐ ์คํ๋ ๋ MDC๊ฐ ์์ฑ๋๊ณ ์ด์ run.id๋ฅผ key๋ก ๊ฐ์ง๋ ๋ฐ์ดํฐ๋ฅผ ๋ฃ๋๋ค. ํ์ง๋ง, CourseSyncService์ ์ค๋ ๋์ Batch Job ์คํ ์ค๋ ๋๋ ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ ์ค๋ ๋ ๋ก์ปฌํ MDC๋ฅผ ์ฌ์ฉํ ์๊ฐ ์์๋ค!
์ด๋ด ๋๋ ์ค๋ ๋๊ฐ ์ ํ๋ ๋ MDC๋ฅผ ์ ํ๋๋ ์ค๋ ๋์ ๋๊ฒจ์ฃผ์ด์ผ ํ๋ค. ์ด๋ Spring์์ ์ง์ํ๊ณ ์๋ `TaskDecorator`๋ฅผ ์์ํ์ฌ ํด๊ฒฐํ ์ ์์๋ค.
private static class MdcTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return () -> {
Map<String, String> previous = MDC.getCopyOfContextMap();
try {
if (contextMap != null) {
MDC.setContextMap(contextMap);
} else {
MDC.clear();
}
runnable.run();
} finally {
if (previous != null) {
MDC.setContextMap(previous);
} else {
MDC.clear();
}
}
};
}
}
@Slf4j
@Component
public class CourseSyncJobListener implements JobExecutionListener {
@Override
public void afterJob(JobExecution jobExecution) {
BatchStatus status = jobExecution.getStatus();
Duration duration = Duration.between(jobExecution.getStartTime(), jobExecution.getEndTime());
StringBuilder stepInfo = new StringBuilder();
jobExecution.getStepExecutions().forEach(stepExecution ->
stepInfo.append(String.format(" | Step [%s]: Read=%d, Write=%d, Skip=%d",
stepExecution.getStepName(),
stepExecution.getReadCount(),
stepExecution.getWriteCount(),
stepExecution.getSkipCount()
))
);
log.info("[CourseSyncJob ์ข
๋ฃ] ์ํ: {} | ์ฒ๋ฆฌ ์๊ฐ: {}์๊ฐ {}๋ถ {}์ด{}",
status,
duration.toHoursPart(),
duration.toMinutesPart(),
duration.toSecondsPart(),
stepInfo);
}
}
์ด๋ฅผ ํตํด์ CourseSyncJobListener๋ฅผ ๊ฐ์ ํ ์ ์์๋ค. MDC๋ฅผ ํตํ ๊ฐ์ ์ ํ๋ฉด์ ์ฌ๋ฌ ์ค๋ก ๋๋ ๋ก๊ทธ๋ฅผ ํ๋์ ๋ก๊ทธ๋ก ํฉ์น๊ธฐ๋ ํ๋ค. ์ข ๋ ์ฝ๊ฒ ์ถ์ ํ๊ธฐ ์ํจ์ด์๋ค. ๊ทธ๋ฆฌ๊ณ , ์ด๋ฌํ ๋ชฉ์ ์ ์ข ๋ ์ ๋ํ๋ด๊ธฐ ์ํด์ Logback ์ค์ ์ ํตํด Batch Job์ ๊ฒฝ์ฐ ๊ธฐ์กด ๋ก๊ทธ์ ํจ๊ป ํ์ผ๋ก ์ ์ฅํ๋ ๊ฒ์ด ์๋๋ผ ๋ฐ๋ก batch.log๋ผ๋ ํ์ผ์ ๊ธฐ๋กํ๋๋ก ๋ณ๊ฒฝํ๋ค~!
'๊ฐ๋ฐ > ๋ฐ์ธ๊ถ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| OSRM Match API๋ก ๋ฌ๋ ์ฝ์ค ๋ฐ์ดํฐ ์ขํ ๋ณด์ ํ๊ธฐ (0) | 2025.12.14 |
|---|---|
| ๊ธธ์ฐพ๊ธฐ, ๊ฒฝ๋ก ๋ณด์ ์ ํต์ฌ OSRM ํบ์๋ณด๊ธฐ (0) | 2025.12.08 |
| API ๋ฌธ์ ๊ฐ์ ๊ณผ ๋ฌธ์ํ ํด Stoplight๋ก ๋ณ๊ฒฝํ๊ธฐ (0) | 2025.12.07 |
| ๋๋ฉ์ธ ๊ฐ์ฒด ์์ฑ ๋ก์ง ๊ด๋ จ ๊ณ ๋ฏผ (0) | 2025.10.06 |
| ์๋ฒ์์ ์ง์์ ์ธ OutOfMemory๊ฐ ๋ฐ์ํ๋ ๋ฌธ์ ํด๊ฒฐ (3) | 2025.09.29 |